Spring Security 架構(gòu)

? ? ? ?本指南是Spring Security的入門,它提供了對(duì)該框架的設(shè)計(jì)和基本模塊的洞察。僅介紹了應(yīng)用程序安全性的最基本知識(shí),但是這樣做可以清除開發(fā)人員在使用Spring Security所遇到的一些困惑。為此,來看看使用過濾器(通常使用方法注釋)在Web應(yīng)用程序中應(yīng)用安全性的方式。當(dāng)需要從高層次了解安全應(yīng)用程序的工作方式,如何自定義它,或者僅需要學(xué)習(xí)如何考慮應(yīng)用程序安全性時(shí),請(qǐng)使用本指南。

? ? ? ?本指南不是解決最基本的問題(還有其他來源)的手冊(cè),但對(duì)于初學(xué)者和專家都可能有用。在本文中Spring Boot之所以被廣泛引用,是因?yàn)樗鼮榘踩膽?yīng)用程序提供了一些默認(rèn)行為,并且有助于理解它與整個(gè)體系結(jié)構(gòu)之間的關(guān)系。所有這些原則同樣適用于不使用Spring Boot的應(yīng)用程序。

身份驗(yàn)證和訪問控制

? ? ? ?應(yīng)用程序安全性可以歸結(jié)為兩個(gè)獨(dú)立問題:身份驗(yàn)證(你是誰)和授權(quán)(你可以做什么?)。有時(shí)人們會(huì)說“訪問控制”而不是“授權(quán)”。Spring Security的體系結(jié)構(gòu)旨在將身份驗(yàn)證與授權(quán)分開,并具有策略和擴(kuò)展點(diǎn)。

認(rèn)證方式

身份驗(yàn)證的主要策略接口AuthenticationManager只有一個(gè)方法:

public interface AuthenticationManager {

Authentication authenticate(Authentication authentication) throws AuthenticationException;

}

AuthenticationManager接口在它的authenticate()方法中可以執(zhí)行以下其中之一種情況:

1.如果可以驗(yàn)證輸入代表有效的主體,則返回一個(gè)Authentication(通常為authenticated=true)。

2.如果認(rèn)為輸入代表無效的主體,則拋出一個(gè)AuthenticationException。

3.如果無法決定,則返回null。

AuthenticationException是運(yùn)行時(shí)異常。它通常由應(yīng)用程序以通用方式處理,具體取決于應(yīng)用程序的樣式或目的。換句話說,通常不希望用戶代碼捕獲并處理它。例如,返回一個(gè)頁面顯示身份驗(yàn)證失敗,而后端HTTP服務(wù)將發(fā)送401響應(yīng),有沒有WWW認(rèn)證頭取決于上下文。

最常用的實(shí)現(xiàn)AuthenticationManager是ProviderManager,它委托一系列AuthenticationProvider實(shí)例。一個(gè)?AuthenticationProvider有點(diǎn)像一個(gè)AuthenticationManager,但是它有一個(gè)額外的方法,允許調(diào)用者查詢是否支持給定Authentication類型:

public interface AuthenticationProvider {

Authentication authenticate(Authentication authentication) throws AuthenticationException; boolean supports(Class<?> authentication);

}

supports()方法中的參數(shù)Class<?>實(shí)際上是Class<? extends Authentication>(僅詢問它是否支持將傳遞到authenticate()方法中的參數(shù))。一個(gè)ProviderManager在同一個(gè)應(yīng)用程序通過委托給AuthenticationProviders程序鏈可支持多個(gè)不同的認(rèn)證機(jī)制。如果一個(gè)ProviderManager不能識(shí)別特定的身份驗(yàn)證實(shí)例類型,該類型將被跳過。

一個(gè)ProviderManager具有可選的父級(jí),它能查詢是否所有providers都返回null。如果父級(jí)不可用,則空身份驗(yàn)證將導(dǎo)致AuthenticationException。有時(shí),一個(gè)應(yīng)用程序具有受保護(hù)資源的邏輯組(例如,與路徑模式匹配的所有Web資源/api/**),并且每個(gè)組可以具有自己專用的AuthenticationManager。通常,每個(gè)都是一個(gè)ProviderManager,并且它們共享一個(gè)父級(jí)。因此,父級(jí)是一種“全局”資源,充當(dāng)所有provider的后備。

具有公共父級(jí)的ProviderManager

自定義Authentication Managers

Spring Security提供了一些配置助手,可以快速獲取在應(yīng)用程序中設(shè)置的通用身份驗(yàn)證管理器功能。最常用的幫助器是AuthenticationManagerBuilder,它非常適合設(shè)置內(nèi)存中的JDBC或LDAP用戶詳細(xì)信息,或添加自定義UserDetailsService。

這是一個(gè)應(yīng)用程序配置示例AuthenticationManager:

@Configuration

public class ApplicationSecurity extends WebSecurityConfigurerAdapter {

... // web stuff here

@Autowired

public void initialize(AuthenticationManagerBuilder builder, DataSource dataSource) {? ? ? builder.jdbcAuthentication().dataSource(dataSource).withUser("dave").password("secret").roles("USER");

}

此示例與Web應(yīng)用程序有關(guān),但是AuthenticationManagerBuilder的用法更為廣泛(有關(guān)如何實(shí)現(xiàn)Web應(yīng)用程序安全性的詳細(xì)信息,請(qǐng)參見下文)。請(qǐng)注意,AuthenticationManagerBuilder是使用@Autowired注解注入到一個(gè)@Bean--這就是它構(gòu)建全局(父)AuthenticationManager的原因。相反,如果我們這樣做的話:

@Configuration

public class ApplicationSecurity extends WebSecurityConfigurerAdapter {

@Autowired

DataSource dataSource; ... // web stuff here

@Override

public void configure(AuthenticationManagerBuilder builder) {? ? ? ? ?builder.jdbcAuthentication().dataSource(dataSource).withUser("dave").password("secret").roles("USER"); }

}


? ? ?(使用@Override注解)AuthenticationManagerBuilder僅用于構(gòu)建“ local”?AuthenticationManager,它是全局變量的子級(jí)。在Spring Boot應(yīng)用程序中,你可以@Autowired一個(gè)全局變量轉(zhuǎn)換為另一個(gè)Bean,但是除非你顯式公開它,否則不能對(duì)本地的Bean進(jìn)行轉(zhuǎn)換。

? ? ? ? Spring Boot提供了一個(gè)默認(rèn)的全局AuthenticationManager(只有一個(gè)用戶),可以自定義全局AuthenticationManager,但默認(rèn)的足夠安全,除非需要,可以不必過多考慮。如果你執(zhí)行任何構(gòu)建AuthenticationManager的配置,則通常可以在本地對(duì)要保護(hù)的資源執(zhí)行該配置,而不必?fù)?dān)心全局默認(rèn)值。

授權(quán)或訪問控制

? ? ? ?認(rèn)證成功后,我們可以繼續(xù)進(jìn)行授權(quán)。這里的核心策略是AccessDecisionManager??蚣芴峁┝巳N實(shí)現(xiàn),所有這三種實(shí)現(xiàn)都委托給AccessDecisionVoter鏈。有點(diǎn)像ProviderManager委托給AuthenticationProviders。

AccessDecisionToverter考慮使用ConfigAttributes裝飾的身份驗(yàn)證(表示主體)和安全對(duì)象:

boolean supports(ConfigAttribute attribute);

boolean supports(Class<?> clazz);

int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes);

? ? ? ?對(duì)象在AccessDecisionManager和AccessDecisionVoter的簽名中是完全通用的-它表示用戶可能想要訪問的任何內(nèi)容(web資源或Java類中的方法是最常見的兩種情況)。ConfigAttributes也是相當(dāng)通用的,它表示帶有一些元數(shù)據(jù)的安全對(duì)象的裝飾,這些元數(shù)據(jù)決定了訪問該對(duì)象所需的權(quán)限級(jí)別。ConfigAttribute是一個(gè)接口,但它只有一個(gè)非常通用的方法,并返回一個(gè)字符串,因此這些字符串以某種方式編碼資源所有者的意圖,表示允許誰訪問它的規(guī)則。典型的ConfigAttribute是用戶角色的名稱(如ROLE_ADMIN或ROLE_AUDIT),它們通常具有特殊格式(如ROLE_前綴)或表示需要求值的表達(dá)式。

大多數(shù)人只使用默認(rèn)的AccessDecisionManager,它是基于確認(rèn)的(如果任何voters確認(rèn),則授予訪問權(quán))。任何自定義都會(huì)在voters中發(fā)生,要么添加新的,要么修改現(xiàn)有的工作方式。使用作為Spring表達(dá)式語言(SpEL)表達(dá)式的ConfigAttributes非常常見,例如isFullyAuthenticated() && hasRole('FOO')。AccessDecisionVoter支持此功能,它可以處理表達(dá)式并為表達(dá)式創(chuàng)建上下文。要擴(kuò)展可以處理的表達(dá)式的范圍,需要SecurityExpressionRoot的自定義實(shí)現(xiàn),有時(shí)還需要SecurityExpressionHandler。

網(wǎng)絡(luò)安全

Web層(用于UI和HTTP后端)中的Spring安全性是基于Servlet過濾器的,因此通常先看看過濾器的角色是有幫助的。下圖顯示了單個(gè)HTTP請(qǐng)求的處理程序的典型分層。


Filter chain delegating to a Servlet

? ? ? ?客戶端向應(yīng)用程序發(fā)送請(qǐng)求,然后容器根據(jù)請(qǐng)求URI的路徑確定對(duì)它應(yīng)用哪些過濾器和哪個(gè)servlet。一個(gè)servlet最多只能處理一個(gè)請(qǐng)求,但是過濾器形成一個(gè)鏈,因此它們是有序的,實(shí)際上,如果過濾器要處理請(qǐng)求本身,則可以否決鏈的其余部分。過濾器還可以修改下游過濾器和Servlet中使用的請(qǐng)求和/或響應(yīng)。過濾器鏈的順序非常重要,Spring Boot通過兩種機(jī)制對(duì)其進(jìn)行管理:一種是@Beans類型Filter可以具有@Order或?qū)崿F(xiàn)Ordered,另一種是它們可以是FilterRegistrationBean的一部分,F(xiàn)ilterRegistrationBean本身有一個(gè)作為其API一部分的命令。一些現(xiàn)成的過濾器定義了自己的常量,以幫助顯示它們的相對(duì)順序(例如.Spring會(huì)話中的SessionRepositoryFilter有一個(gè) Integer.MIN_VALUE + 50的DEFAULT_ORDER,這告訴我們,它在鏈的早期,但不排除其他過濾器之前)。Spring Security作為一個(gè)單一過濾器安裝在鏈條上,其具體類型是FilterChainProxy,原因很快就會(huì)顯現(xiàn)出來。在Spring Boot應(yīng)用程序中,安全過濾器是ApplicationContext中的一個(gè)@Bean,默認(rèn)情況下會(huì)安裝它,以便將其應(yīng)用于每個(gè)請(qǐng)求。它安裝在SecurityProperties.DEFAULT_FILTER_ORDER所定義的位置,該位置又由FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER錨定(Spring Boot應(yīng)用程序在包裝請(qǐng)求并修改其行為時(shí)希望篩選器具有的最大順序)。不過,還有更多的內(nèi)容:從容器的角度來看,Spring Security是一個(gè)單一的過濾器,但是在它內(nèi)部有額外的過濾器,每個(gè)過濾器都扮演著特殊的角色。這是一張圖:


Spring Security Filter

? ? ? ?Spring Security是一個(gè)單獨(dú)的物理過濾器,但是將處理委托給一系列內(nèi)部過濾器.實(shí)際上,在安全過濾器中還有一個(gè)間接層:它通常作為DelegatingFilterProxy安裝在容器中,而不必是Spring@Bean。代理委托給FilterChainProxy,它始終是一個(gè)@Bean,通常固定名稱為springSecurityFilterChain。它是FilterChainProxy,它包含作為過濾器鏈(或鏈)在內(nèi)部排列的所有安全邏輯。所有的過濾器都有相同的API(它們都實(shí)現(xiàn)了來自Servlet規(guī)范的過濾器接口),并且它們都有機(jī)會(huì)否決鏈的其余部分。

? ? ? ? 這里可以有多個(gè)過濾器鏈,所有這些都由Spring Security在同一頂級(jí)FilterChainProxy中管理,并且容器未知。Spring Security過濾器包含一個(gè)過濾器鏈列表,并將請(qǐng)求發(fā)送到與之匹配的第一個(gè)鏈。下圖顯示了基于匹配請(qǐng)求路徑的分派(/foo/**matches before/**)。這很常見,但不是匹配請(qǐng)求的唯一方法。這個(gè)分派過程最重要的特性是只有一個(gè)鏈處理一個(gè)請(qǐng)求。


Security Filter Dispatch

圖3。Spring Security FilterChainProxy將請(qǐng)求發(fā)送到匹配的第一個(gè)鏈。

? ? ? ? 沒有自定義安全配置的普通Spring啟動(dòng)應(yīng)用程序有幾個(gè)(稱為n)過濾器鏈,通常n=6。第一個(gè)(n-1)鏈只是用來忽略靜態(tài)資源模式,如/css/**和/images/**,以及錯(cuò)誤視圖/錯(cuò)誤(路徑可以由用戶通過安全性。忽略從SecurityProperties配置bean)。最后一個(gè)鏈與catch all path/**匹配,并且更為活躍,包含用于身份驗(yàn)證、授權(quán)、異常處理、會(huì)話處理、頭寫入等的邏輯。默認(rèn)情況下,此鏈中總共有11個(gè)篩選器,但通常用戶不必關(guān)心使用的篩選器和使用的時(shí)間。

對(duì)于容器來說,Spring安全性內(nèi)部的所有過濾器都是未知的,這一點(diǎn)很重要,特別是在Spring引導(dǎo)應(yīng)用程序中,默認(rèn)情況下,所有類型為Filter的@bean都會(huì)自動(dòng)注冊(cè)到容器中。因此,如果要將自定義篩選器添加到安全鏈中,則不需要將其設(shè)為@Bean,也不需要將其包裝在顯式禁用容器注冊(cè)的FilterRegistrationBean中。

創(chuàng)建和定制過濾器鏈

Spring Boot應(yīng)用程序(帶有/**請(qǐng)求匹配器的應(yīng)用程序)中的默認(rèn)回退篩選器鏈具有SecurityProperties.BASIC_AUTH_order的預(yù)定義順序。您可以通過設(shè)置security.basic.enabled=false將其完全關(guān)閉,也可以將其用作回退,只需用較低的順序定義其他規(guī)則。為此,只需添加一個(gè)WebSecurityConfigurerAdapter(或WebSecurityConfigurer)類型的@Bean,并用@Order裝飾類。例子:

@Configuration

@Order(SecurityProperties.BASIC_AUTH_ORDER - 10)

public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {

@Override

protected void configure(HttpSecurity http) throws Exception { http.antMatcher("/foo/**") ...;

}

}

這個(gè)bean將導(dǎo)致Spring Security添加一個(gè)新的過濾器鏈,并在回退之前對(duì)其進(jìn)行排序。

許多應(yīng)用程序?qū)σ唤M資源的訪問規(guī)則與另一組完全不同。例如,承載UI和后臺(tái)API的應(yīng)用程序可能支持基于cookie的身份驗(yàn)證,并重定向到UI部分的登錄頁,以及基于令牌的身份驗(yàn)證,并對(duì)API部分的未經(jīng)身份驗(yàn)證的請(qǐng)求作出401響應(yīng)。每一組資源都有自己的WebSecurityConfigurerAdapter,具有唯一的順序和自己的請(qǐng)求匹配器。如果匹配規(guī)則重疊,則最早排序的篩選鏈將獲勝。

發(fā)送和授權(quán)的請(qǐng)求匹配

安全篩選器鏈(或等效于WebSecurityConfigurerAdapter)具有一個(gè)用于決定是否將其應(yīng)用于HTTP請(qǐng)求的請(qǐng)求匹配器。一旦決定應(yīng)用特定的過濾鏈,就不會(huì)應(yīng)用其他的過濾鏈。但是在一個(gè)過濾器鏈中,可以通過在HttpSecurity配置器中設(shè)置額外的匹配器來對(duì)授權(quán)進(jìn)行更細(xì)粒度的控制。例子:

@Configuration

@Order(SecurityProperties.BASIC_AUTH_ORDER - 10)

public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {

@Override

protected void configure(HttpSecurity http) throws Exception {

http.antMatcher("/foo/**") .authorizeRequests() .antMatchers("/foo/bar").hasRole("BAR") .antMatchers("/foo/spam").hasRole("SPAM") .anyRequest().isAuthenticated();

}

}

配置Spring Security最容易犯的錯(cuò)誤之一是忘記這些匹配器應(yīng)用于不同的進(jìn)程,一個(gè)是整個(gè)過濾鏈的請(qǐng)求匹配器,另一個(gè)是只選擇要應(yīng)用的訪問規(guī)則。

將應(yīng)用程序安全規(guī)則與執(zhí)行器規(guī)則相結(jié)合

如果您將Spring Boot執(zhí)行器用于管理端點(diǎn),您可能希望它們是安全的,并且默認(rèn)情況下它們是安全的。事實(shí)上,只要將執(zhí)行器添加到安全應(yīng)用程序中,就會(huì)得到一個(gè)只應(yīng)用于執(zhí)行器端點(diǎn)的附加過濾器鏈。它由一個(gè)只匹配執(zhí)行器端點(diǎn)的請(qǐng)求匹配器定義,其順序?yàn)镸anagementServerProperties.BASIC驗(yàn)證順序它比默認(rèn)的SecurityProperties回退篩選器少5個(gè),因此在回退之前會(huì)先查詢它。

如果希望應(yīng)用程序安全規(guī)則應(yīng)用于執(zhí)行器終結(jié)點(diǎn),可以添加一個(gè)比執(zhí)行器早排序的篩選器鏈,并使用包含所有執(zhí)行器終結(jié)點(diǎn)的請(qǐng)求匹配器。如果您更喜歡執(zhí)行器端點(diǎn)的默認(rèn)安全設(shè)置,那么最簡單的事情是在執(zhí)行器端點(diǎn)之后添加自己的過濾器,但要早于回退(例如. ManagementServerProperties.BASIC驗(yàn)證順序+1)。例子:

@Configuration

@Order(ManagementServerProperties.BASIC_AUTH_ORDER + 1)

public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {

@Override protected void configure(HttpSecurity http) throws Exception { http.antMatcher("/foo/**") ...;

}

}

注意:Web層中的Spring?Security目前與Servlet API綁定在一起,因此它只有在嵌入式或其他方式的Servlet容器中運(yùn)行應(yīng)用程序時(shí)才真正適用。但是,它沒有綁定到Spring MVC或Spring Web堆棧的其他部分,因此可以在任何servlet應(yīng)用程序中使用,例如使用JAX-RS的應(yīng)用程序

方法安全

除了支持保護(hù)web應(yīng)用程序之外,Spring Security還支持將訪問規(guī)則應(yīng)用于Java方法執(zhí)行。對(duì)于Spring安全來說,這只是一種不同類型的“受保護(hù)資源”。對(duì)于用戶,這意味著訪問規(guī)則是使用相同格式的ConfigAttribute字符串(例如角色或表達(dá)式)聲明的,但在代碼中的不同位置。第一步是啟用方法安全性,例如在應(yīng)用程序的頂級(jí)配置中:

@SpringBootApplication

@EnableGlobalMethodSecurity(securedEnabled = true)

public class SampleSecureApplication { }

然后我們可以直接裝飾方法資源,例如。

@Service

public class MyService {

@Secured("ROLE_USER") public String secure()

{ return "Hello Security";

}

}

? ? ? 此示例是具有安全方法的服務(wù)。如果Spring創(chuàng)建了這種類型的@Bean,那么它將被代理,調(diào)用方在實(shí)際執(zhí)行該方法之前必須通過一個(gè)安全攔截器。如果訪問被拒絕,調(diào)用方將獲得AccessDeniedException而不是實(shí)際的方法結(jié)果。

? ? ? ?可以在方法上使用其他注釋來強(qiáng)制安全約束,特別是@PreAuthorize和@PostAuthorize,它們?cè)试S您分別編寫包含對(duì)方法參數(shù)和返回值的引用的表達(dá)式。

小技巧:將Web安全和方法安全結(jié)合起來并不少見。過濾器鏈提供了用戶體驗(yàn)特性,如身份驗(yàn)證和重定向到登錄頁面等,方法安全性提供了更細(xì)粒度的保護(hù)。

使用線程

Spring Security從根本上說是線程綁定的,因?yàn)樗枰巩?dāng)前經(jīng)過身份驗(yàn)證的主體對(duì)各種下游消費(fèi)者可用?;緲?gòu)建塊是SecurityContext,它可能包含一個(gè)身份驗(yàn)證(當(dāng)用戶登錄時(shí),它將是一個(gè)經(jīng)過顯式身份驗(yàn)證的身份驗(yàn)證)。您始終可以通過SecurityContextHolder中的靜態(tài)便利方法訪問和操作SecurityContext,而SecurityContextHolder中的靜態(tài)便利方法又只是操作一個(gè)TheadLocal,例如。

SecurityContext context = SecurityContextHolder.getContext();

Authentication authentication = context.getAuthentication();

assert(authentication.isAuthenticated);

用戶應(yīng)用程序代碼這樣做并不常見,但是如果您需要編寫一個(gè)自定義的身份驗(yàn)證過濾器(盡管即使如此,Spring Security中也有一些基類可以在您不需要使用SecurityContextHolder的地方使用),那么它也會(huì)很有用。

如果需要訪問web端點(diǎn)中當(dāng)前已驗(yàn)證的用戶,可以在@RequestMapping中使用方法參數(shù)。例如。

@RequestMapping("/foo") public String foo(

@AuthenticationPrincipal User user) { .

.. // do stuff with user

}

此批注從SecurityContext中提取當(dāng)前身份驗(yàn)證,并對(duì)其調(diào)用getPrincipal()方法以生成方法參數(shù)。身份驗(yàn)證中主體的類型取決于用于驗(yàn)證身份驗(yàn)證的AuthenticationManager,因此這是一個(gè)有用的小技巧,可用于獲取對(duì)用戶數(shù)據(jù)的類型安全引用。

如果使用的是Spring Security,HttpServletRequest中的主體將是身份驗(yàn)證類型,因此您也可以直接使用它:

@RequestMapping("/foo")

public String foo(Principal principal) {

Authentication authentication = (Authentication) principal; User = (User) authentication.getPrincipal();

... // do stuff with user

}

如果需要編寫在不使用Spring Security的情況下可以工作的代碼(在加載身份驗(yàn)證類時(shí)需要更加防御性),這有時(shí)會(huì)很有用。

異步處理安全方法

由于SecurityContext是線程綁定的,如果要執(zhí)行調(diào)用安全方法的任何后臺(tái)處理,例如使用@Async,則需要確保傳播上下文。這可以歸結(jié)為用在后臺(tái)執(zhí)行的任務(wù)(Runnable、Callable等)包裝SecurityContext。Spring Security提供了一些幫助程序來簡化這個(gè)過程,比如用于Runnable和Callable的包裝器。要將SecurityContext傳播到@Async方法,需要提供AsyncConfigurer并確保執(zhí)行器的類型正確:

@Configuration

public class ApplicationConfiguration extends AsyncConfigurerSupport {

@Override

public Executor getAsyncExecutor() {

return new DelegatingSecurityContextExecutorService(Executors.newFixedThreadPool(5));

}

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容