Spring Security Architecture(Spring安全框架的體系結(jié)構(gòu))

1. Introduction(簡(jiǎn)介)

本篇是關(guān)于Spring安全框架的入門指導(dǎo),主要講解Spring 安全框架的體系結(jié)構(gòu)舔清,設(shè)計(jì)思路和組成模塊丝里。雖然本文只涵蓋了最為基本的應(yīng)用安全知識(shí)曲初,但這些足以幫助開發(fā)者消除在使用Spring 安全框架進(jìn)行開發(fā)時(shí)遇到的一些困惑。為了完成這些工作臼婆,我們來瞧一瞧如何通過Filters(Servlet規(guī)范中一種組件)以及更為常用的方法注解來在Web應(yīng)用中使用安全組件。
如果你需要在更高的層次上理解一個(gè)安全的應(yīng)用是如何工作的械媒,或者你想知道如何定制化應(yīng)用的安全組件目锭,或者你僅僅只是想要了解一下應(yīng)用安全方面的知識(shí),那么纷捞,都可以通過閱讀本篇指導(dǎo)獲取你想要的。但是本篇指導(dǎo)并沒有打算去說明或者解決超出基本安全范圍的問題或者需求(這些工作由其他的指導(dǎo)來完成)被去,但對(duì)于一個(gè)關(guān)于應(yīng)用安全的初學(xué)者來說主儡,這篇指導(dǎo)是非常有用的。這篇指導(dǎo)有很大的篇幅涉及到了Spring Boot惨缆,這是因?yàn)镾pring Boot默認(rèn)為應(yīng)用的安全提供了一些支持糜值,這對(duì)于我們理解Spring安全框架是如何適配整個(gè)Spring體系結(jié)構(gòu)是有幫助的。所有這些適用于Spring Boot應(yīng)用的方式或者方法坯墨,同樣適用于那些使用了Spring框架的其他形式的Web應(yīng)用程序寂汇。

2. Authentication(認(rèn)證) & Access Control(訪問控制)

應(yīng)用程序的安全問題或多或少可以歸納為兩個(gè)相互獨(dú)立的基本問題:認(rèn)證(Authentication,解決身份識(shí)別問題捣染,即識(shí)別用戶身份是否合法)和授權(quán)(Authorization 骄瓣,解決訪問權(quán)限問題,即允許用戶做什么)耍攘。有些人使用"access control(訪問控制)"來代替"authorization(授權(quán))"榕栏,雖然這兩種說法會(huì)給用戶帶來一些困惑,但由于"authorization(授權(quán))"這個(gè)詞在有些地方被過度的解釋了蕾各,這導(dǎo)致"access control(訪問控制)"這種說法更有助于我們理解這種控制用戶的訪問權(quán)限的方式扒磁。Spring安全框架的體系結(jié)構(gòu)在設(shè)計(jì)的時(shí)候就將認(rèn)證(authentication)從授權(quán)(authorization)中分離出來,并且設(shè)計(jì)了一些策略能夠?qū)@兩者進(jìn)行擴(kuò)展式曲。

2.1 Authentication(認(rèn)證)

Spring安全框架中妨托,為認(rèn)證(Authentication)設(shè)計(jì)的主要策略接口是org.springframework.security.authentication.AuthenticationManager ,而這個(gè)接口只有一個(gè)方法:

public interface AuthenticationManager {

  Authentication authenticate(Authentication authentication)
    throws AuthenticationException;
}

AuthenticationManagerauthenticate()方法中吝羞,用戶可以做三件事情:

  1. 如果可以確認(rèn)輸入的參數(shù)authentication(是org.springframework.security.core.Authentication的實(shí)例對(duì)象)代表一個(gè)合法的用戶身份兰伤,那么返回另一個(gè)org.springframework.security.core.Authentication實(shí)例對(duì)象,這個(gè)返回的對(duì)象通常會(huì)帶有一個(gè)authenticated=true的標(biāo)記脆贵。
  2. 如果可以確認(rèn)輸入的參數(shù)authentication代表一個(gè)非法的用戶身份医清,那么將拋出一個(gè)org.springframework.security.core.AuthenticationException異常。
  3. 如果無法判斷輸入的參數(shù)authentication是否是一個(gè)合法的用戶身份卖氨,可以返回null值会烙。

org.springframework.security.core.AuthenticationException是一個(gè)運(yùn)行時(shí)異常负懦。一般情況下,該異常會(huì)被應(yīng)用程序使用專門的處理器進(jìn)行處理柏腻,而如何處理取決于應(yīng)用程序的形式和用途纸厉。換句話說,一般情況下五嫂,應(yīng)用程序并不指望開發(fā)者編寫代碼去捕獲和處理這些異常颗品,而是提供默認(rèn)的策略,由應(yīng)用程序自身來處理這個(gè)異常沃缘,比如躯枢,應(yīng)用程序會(huì)提供一個(gè)界面同時(shí)渲染一個(gè)頁面來告訴用戶認(rèn)證失敗,同時(shí)后臺(tái)的HTTP服務(wù)也將會(huì)發(fā)送401狀態(tài)碼槐臀,也會(huì)根據(jù)應(yīng)用的上下文環(huán)境來決定是否攜帶WWW-Authenticate頭部锄蹂。
最常用的org.springframework.security.authentication.AuthenticationManager實(shí)現(xiàn)類是org.springframework.security.authentication.ProviderManager,該類維護(hù)了一個(gè)由接口org.springframework.security.authentication.AuthenticationProvider的實(shí)現(xiàn)類的實(shí)例所組成的列表水慨。而org.springframework.security.authentication.AuthenticationProviderorg.springframework.security.authentication.AuthenticationManager相似得糜,區(qū)別在于org.springframework.security.authentication.AuthenticationProvider內(nèi)部包含另一個(gè)方法允許調(diào)用者測(cè)試是否支持傳入的org.springframework.security.core.Authentication實(shí)現(xiàn)類的類型:

public interface AuthenticationProvider {

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

supports()方法中的Class<?>參數(shù)的真正類型是Class<? extens Authentication>,主要是用來測(cè)試是否支持傳入authenticate()方法的authentication參數(shù)的類型晰洒。由于ProviderManager代理了一個(gè)AuthenticationProviders鏈朝抖,所以可以在同一個(gè)應(yīng)用中支持多種不同的認(rèn)證機(jī)制。一般情況下谍珊,ProviderManager會(huì)跳過那些自己不支持的Authentication的實(shí)例類型治宣。
一個(gè)ProviderManager有一個(gè)可選的父級(jí)provider,如果所有的providers都返回的是null抬驴,也就是說所有的認(rèn)證機(jī)制都無法確定當(dāng)前的用戶身份是合法的炼七,最終將由這個(gè)父級(jí)(或者全局)的provider來決定,如果不存在這個(gè)父級(jí)的provider也會(huì)返回null布持,最終會(huì)拋出AuthenticationException豌拙。
有時(shí)候,應(yīng)用將被保護(hù)的資源按照一定的規(guī)則分成邏輯上的分組(比如题暖,所有的web資源都通過路徑來分組按傅,即將資源按照linux目錄樹的形式進(jìn)行分組),并且每一個(gè)分組都擁有專屬的AuthenticationManager胧卤,而這些AuthenticationManager通常都是一個(gè)ProviderManager唯绍,這些AuthenticationManager共享一個(gè)父級(jí)(或者全局)的AuthenticationManager,這個(gè)父級(jí)的AuthenticationManager作為所有的providers的替補(bǔ)而存在枝誊。

圖一. 由ProviderManager組成的AuthenticationManager層級(jí)結(jié)構(gòu)

2.2 Customizing Authentication Managers(自定義認(rèn)證管理器)

Spring安全框架提供了一些配置的輔助類况芒,能夠在應(yīng)用中快速的創(chuàng)建常用的認(rèn)證管理功能。最常用的輔助類就是org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder叶撒,該類主要用來設(shè)置獲取用戶信息的方式绝骚,有三種方式耐版,分別是in-memory,JDBC或者LDAP,也可以添加實(shí)現(xiàn)了org.springframework.security.core.userdetails.UserDetailsService接口的類對(duì)象來設(shè)置獲取用戶詳情的方式压汪。
下面的例子展示了如何在一個(gè)應(yīng)用中配置全局的AuthenticationManager

@Configuration
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {

   ... // web stuff here

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

}

這是一個(gè)web應(yīng)用的例子粪牲,AuthenticationManagerBuilder的使用方式有很多種,而在這個(gè)例子中AuthenticationManagerBuilder的一個(gè)實(shí)例被應(yīng)用作為參數(shù)通過@Autowired注解傳入initialize()方法中止剖,在這個(gè)方法中將會(huì)創(chuàng)建一個(gè)全局(父級(jí))的AuthenticationManager腺阳。我們可以和另外一種使用方式進(jìn)行對(duì)比:

@Configuration
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {

  @Autowired
  DataSource dataSource;

   ... // web stuff here

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

}

上例中使用@Override注解,覆蓋了父類的configure()方法穿香,在這個(gè)方法中亭引,AuthenticationManagerBuilder的實(shí)例只是被該方法的調(diào)用者使用來創(chuàng)建一個(gè)局部的AuthenticationManager,這個(gè)局部的AuthenticationManager是全局AuthenticationManager的孩子扔水。在一個(gè)Spring Boot應(yīng)用中痛侍,我們可以使用@Autowired注解將全局的AuthenticationManager注入到其他的bean中,但是無法將一個(gè)局部的AuthenticationManager注入到其他的bean中魔市,除非我們通過配置@Bean的方式明確的將該局部的AuthenticationManager的實(shí)例作為一個(gè)組件發(fā)布出去。
Spring Boot提供了一個(gè)默認(rèn)的全局AuthenticationManager赵哲,我們可以通過提供自己的AuthenticationManager組件來替換他待德。這個(gè)默認(rèn)的AuthenticationManager組件是足夠安全的,我們無需對(duì)他有過多的擔(dān)心枫夺,除非你確實(shí)需要一個(gè)自定義的AuthenticationManager将宪。如果我們做了一些配置創(chuàng)建了一個(gè)AuthenticationManager組件,我們可以將該組件應(yīng)用到局部的受保護(hù)資源上而無需擔(dān)心全局的AuthenticationManager橡庞。

前文說的全局的provider较坛,是ProviderManager組件,ProviderManagerAuthenticationManager最常用的一個(gè)實(shí)現(xiàn)類扒最,所以全局的provider就是全局的AuthenticationManager組件丑勤。

2.3 Authorization or Access Control(授權(quán)或訪問控制)

一旦用戶成功通過了認(rèn)證,我們就可以開始關(guān)注授權(quán)問題吧趣,核心的授權(quán)策略由接口org.springframework.security.access.AccessDecisionManager來提供廊镜。Spring安全框架提供這個(gè)接口的三種實(shí)現(xiàn)打厘,每一種都可以委托給一個(gè)org.springframework.security.access.AccessDecisionVoter<S>鏈,這與ProviderManager委托給AuthenticationProviders有一點(diǎn)類似。
一個(gè)AccessDecisionVoter主要用來處理代表用戶身份的Authentication對(duì)象和一個(gè)由ConfigAttributes描述的安全對(duì)象:

boolean supports(ConfigAttribute attribute);

boolean supports(Class<?> clazz);

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

這個(gè)安全對(duì)象指的是參數(shù)object敦姻,其類型就是AccessDecisionVoter<S>的泛型參數(shù)S,他代表任何用戶想要訪問的目標(biāo)(Web資源或Java類的方法是該對(duì)象的兩種最常見的形式)馍忽。ConfigAttributes指的是參數(shù)attributes侨嘀,他是org.springframework.security.access.ConfigAttribute的集合,保存了安全對(duì)象的元數(shù)據(jù)八匠,而這些元數(shù)據(jù)確定了訪問該安全對(duì)象所要求的權(quán)限趴酣。ConfigAttribute是只有一個(gè)方法的接口略水,而這個(gè)方法返回一個(gè)String類型的值,這個(gè)值通常情況下是一串編碼渊涝,表示資源所有者制定的資源訪問規(guī)則慎璧。典型的ConfigAttribute應(yīng)用方式就是返回表示用戶角色的字符串,比如(ROLE_ADMIN或者ROLE_AUDIT)胸私,這些都有統(tǒng)一的格式(比如以ROLE_為前綴),而另外一種應(yīng)用方式是返回能夠用來執(zhí)行的表達(dá)式字符串岁疼。
絕大多開發(fā)者只使用默認(rèn)的AccessDecisionManager組件,默認(rèn)的AccessDecisionManager的機(jī)制是如果得票數(shù)沒有下降缆娃,那么訪問就應(yīng)該被允許捷绒。因此所有的定制化開發(fā)都傾向于發(fā)生在投票者那里贯要,要么是增加新的投票者暖侨,要么改變已有投票者的行為崇渗。
最常用的ConfigAttributes是Spring的EL表達(dá)式,例如isFullyAuthenticated() && hasRole('FOO')宅广。一種AccessDecisionVoter組件就支持Spring的EL表達(dá)式,他不但能夠執(zhí)行這些表達(dá)式俭厚,同時(shí)還能為他們創(chuàng)建上下文環(huán)境。如果想要擴(kuò)展表達(dá)式的語法套腹,可以實(shí)現(xiàn)org.springframework.security.access.expression.SecurityExpressionRoot抽象類或者實(shí)現(xiàn)org.springframework.security.access.expression.SecurityExpressionHandler<T>接口资铡。

3. Web Security(Web安全)

3.1 Web Security基本組件

Spring安全框架在Web層的組件都是基于Servelt規(guī)范中的Filters,所以事先弄明白Filters所扮演的角色對(duì)與我們理解Web安全是有幫助的笤休。下面的圖片展示了Http請(qǐng)求處理器的層級(jí)結(jié)構(gòu)。

客戶端發(fā)送請(qǐng)求到應(yīng)用,容器決定哪些fitlers以及哪一個(gè)servlet可以用來處理這次請(qǐng)求贞铣。絕大數(shù)情況下沮明,一個(gè)servlet只能處理一種請(qǐng)求辕坝,但是這些filters組成了一個(gè)鏈荐健,他們按照一定的順序排列,如果某一個(gè)filter想要處理這個(gè)請(qǐng)求纺酸,那么他可以將這個(gè)請(qǐng)求攔截下來,并且進(jìn)行處理餐蔬。一個(gè)filter也能夠改變r(jià)equest請(qǐng)求或者response回復(fù)。Filter鏈的順序是非常重要的佑附,Spring Boot有兩種機(jī)制在管理filter的順序 樊诺,一種是在使用@Bean注解發(fā)布一個(gè)Filter的時(shí)候音同,同時(shí)使用@Order注解來指定這個(gè)filter的優(yōu)先級(jí)(優(yōu)先級(jí)是由一個(gè)int類型的整數(shù)表示,數(shù)值越大瘟斜,優(yōu)先級(jí)越高)痪寻,或者讓這個(gè)Filter直接實(shí)現(xiàn)org.springframework.core.Ordered接口,通過getOrder()方法返回優(yōu)先級(jí)數(shù)值橡类,另一種方法是使用org.springframework.boot.web.servlet.FilterRegistrationBean注冊(cè)Filter的時(shí)候,使用他的相關(guān)API來指定要注冊(cè)的Filter的優(yōu)先級(jí)顾画。一些標(biāo)準(zhǔn)的filters通過定義一些常量值來確定他們之間的順序(比如Spring Session框架中的SessionRepositoryFilter組件取劫,默認(rèn)的優(yōu)先級(jí)數(shù)值由其自身定義的常量DEFAULT_ORDER來表示,其值為Integer.MIN_VALUE + 50谱邪,這個(gè)值只比int型整數(shù)的最小值大一點(diǎn)庶诡,因此這個(gè)過濾器幾乎是排在過濾器鏈的最下面惦银,要到達(dá)這里,必須先通過其他過濾器)书蚪。
Spring安全的核心組件就是一個(gè)安裝到這個(gè)過濾器鏈中的Filter迅栅,他的具體類型是org.springframework.security.web.FilterChainProxy殊校,稍后我們將會(huì)詳細(xì)說明這個(gè)安全過濾器。在一個(gè)Spring Boot應(yīng)用中读存,安全過濾器(security filter)是ApplicationContext的一個(gè)@Bean为流,一旦開啟了Spring安全功能,就會(huì)默認(rèn)安裝這個(gè)安全過濾器宪萄,并且攔截所有的請(qǐng)求艺谆。安全過濾器在過濾器鏈中的位置由org.springframework.boot.autoconfigure.security.SecurityProperties.DEFAULT_FILTER_ORDER表示的優(yōu)先級(jí)數(shù)值來決定拜英,這個(gè)值位于錨點(diǎn)org.springframework.boot.web.servlet.FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER的下方(這個(gè)值是Spring Boot應(yīng)用中最大的過濾器優(yōu)先級(jí)數(shù)值,因?yàn)镾pring Boot希望請(qǐng)求在通過整個(gè)處理流程之前虫给,先被這個(gè)過濾器包裝一下侠碧,改變一下行為)。從下圖我們可以看到药蜻,Spring安全框架所提供的功能由單個(gè)Filter來提供替饿,但是在這個(gè)Filter中,包含著多個(gè)內(nèi)部filters视卢,并且每一個(gè)都具有特定的功能。圖片如下:

圖2. Spring安全的核心組件是一個(gè)Filter惋砂,他代理了一個(gè)內(nèi)部的過濾器鏈

在Spring應(yīng)用中绳锅,過濾器通常是安裝在類型為org.springframework.web.filter.DelegatingFilterProxy的代理容器中,這種容器并不以Spring的@Bean的形式存在罗标,而是作為原生的Servlet規(guī)范中的Filter組件安裝到Servlet容器中。Spring安全的過濾器組件就是安裝在這種代理容器中闯割,是一個(gè)類型為org.springframework.security.web.FilterChainProxy且具有固定名字springSecurityFilterChain的過濾器,這個(gè)安全過濾器是以Spring@Bean的形式存在的宾尚。而springSecurityFilterChain過濾器又包含了一個(gè)封裝了安全邏輯的有序過濾器鏈谢澈,組成這個(gè)鏈的過濾器都有相同的API(通常是實(shí)現(xiàn)了Servlet規(guī)范中Filter接口)并且每一個(gè)過濾器都可能將請(qǐng)求攔截到自己這一層并進(jìn)行處理。所以锥忿,事實(shí)上的安全層可不止一層敬鬓。
當(dāng)然,springSecurityFilterChain也可能會(huì)管理多個(gè)不同的過濾器鏈钉答,也就是包含一個(gè)過濾器鏈的列表,并且仑性,所有這些過濾器對(duì)容器都是透明的右蹦。并且springSecurityFilterChain會(huì)將請(qǐng)求派發(fā)給第一個(gè)匹配的過濾器鏈。下圖展示了基于請(qǐng)求路徑的派發(fā)過程(這也是使用最多但并不唯一的方式)何陆。這種派發(fā)過程的最重要的特點(diǎn)就是有且只有一個(gè)過濾器鏈來處理這個(gè)請(qǐng)求。

圖3. Spring安全框架的FilterChainProxy將請(qǐng)求派發(fā)給第一個(gè)匹配請(qǐng)求路徑的過濾器鏈

一個(gè)沒有任何定制化配置的Spring Boot應(yīng)用具有6個(gè)過濾器鏈,前5個(gè)過濾器鏈只會(huì)忽略那些指向靜態(tài)資源的路徑晃洒,例如/css/**/images/**朦乏,以及用來展示錯(cuò)誤信息視圖的路徑/error(這些忽略的路徑可以在SecurityProperties配置bean中,使用security.ignored來控制)呻疹。最后一個(gè)過濾器鏈匹配所有的路徑/**并且也是最活躍的,包含認(rèn)證和授權(quán)的邏輯镊尺,錯(cuò)誤處理,會(huì)話處理语稠,頭部信息處理等弄砍。在這些默認(rèn)的過濾器鏈中有總共11個(gè)過濾器,通常情況下音婶,用戶無需去關(guān)心哪個(gè)過濾器被使用了以及是什么時(shí)候使用的。

注意
Spring安全的所有內(nèi)部過濾器對(duì)于容器來說都是透明的寸士,這很重要瞳收,特別是Spring Boot應(yīng)用,因?yàn)樗?code>Filter類型的@Bean都是由容器自動(dòng)注冊(cè)的螟深。所以如果你想要添加自定義的安全過濾器到Spring安全的過濾器鏈中,那么你最好不要通過配置Filter類型的@Bean的方式來添加凡蜻,因?yàn)檫@樣Spring應(yīng)用會(huì)把過濾器注冊(cè)到容器中而不是添加到Spring安全的過濾器鏈中垢箕,你可以通過將自定義的安全過濾器封裝在FilterRegistrationBean中來達(dá)成目的。

3.2 Creating and Customizing Filter Chains(創(chuàng)建和定制過濾器鏈)

在Spring Boot應(yīng)用程序中忠荞,有一個(gè)默認(rèn)的后備過濾器鏈(該過濾器鏈匹配所有的請(qǐng)求路徑/**)有一個(gè)預(yù)定義的順序值SecurityProperties.BASIC_AUTH_ORDER帅掘,你可以通過設(shè)置security.basic.enabled=false來徹底關(guān)閉它,或者你可以只把這個(gè)過濾器當(dāng)作一個(gè)定義了一些其他規(guī)則且具有一個(gè)低優(yōu)先級(jí)的后備修档。如果想要這樣做的話,只需要添加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/**")
     ...;
  }
}

上例中的@Configurationbean將會(huì)使Spring安全框架添加一個(gè)優(yōu)先級(jí)排在后備過濾器鏈前面的新過濾器鏈。

許多的應(yīng)用程序擁有訪問規(guī)則互不相同的資源組系宜。例如:一個(gè)應(yīng)用程序提供的資源包括用戶UI和后臺(tái)API接口兩個(gè)部分鲫惶,對(duì)于用戶UI,支持基于Cookie的認(rèn)證欠母,而未認(rèn)證的請(qǐng)求會(huì)重定向至登陸頁面;而對(duì)于后臺(tái)API接口踩寇,則支持基于令牌的認(rèn)證六水,未認(rèn)證的請(qǐng)求會(huì)收到攜帶401狀態(tài)碼的回復(fù)。每一個(gè)資源組都有他自己的WebSecurityConfigurerAdapter睛榄,并且具有唯一的優(yōu)先級(jí)以及請(qǐng)求路徑的匹配規(guī)則想帅。如果匹配規(guī)則發(fā)生重疊,那么優(yōu)先級(jí)更高的過濾器鏈將會(huì)勝出港准。

3.3 Request Matching for Dispatch and Authorization(針對(duì)派發(fā)和授權(quán)的請(qǐng)求匹配)

一個(gè)安全過濾器鏈(等同與一個(gè)WebSecurityConfigurerAdapter)持有一個(gè)請(qǐng)求匹配器,這個(gè)匹配器被用來決定該過濾器鏈?zhǔn)欠襁m用于當(dāng)前的HTTP請(qǐng)求轨帜。一旦一個(gè)HTTP請(qǐng)求適用與一個(gè)特定的過濾器鏈衩椒,其他的過濾器鏈則不會(huì)被應(yīng)用于這個(gè)HTTP請(qǐng)求。但是在一個(gè)過濾器鏈內(nèi)部毛萌,你可以使用HttpSecurity來配置額外的匹配器,這樣你就可以擁有更細(xì)粒度的授權(quán)控制。例如:

@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安全的時(shí)候最容易犯的一個(gè)錯(cuò)誤就是忘記了這些匹配器將應(yīng)用于不同的程序冀痕,一個(gè)是請(qǐng)求匹配器,將應(yīng)用于整個(gè)過濾器鏈言蛇,而其他的匹配器僅僅是用來選擇訪問規(guī)則。

3.4 Combining Application Security Rules with Actuator Rules(應(yīng)用程序的安全規(guī)則與監(jiān)控規(guī)則的整合)

如果你在使用Spring Boot Actuator來監(jiān)控應(yīng)用程序的端點(diǎn)(即由path所指向的資源)吨拗,你應(yīng)該希望他們是安全的并且默認(rèn)他們是安全的婿斥。實(shí)際上,當(dāng)你將Spring Boot監(jiān)控功能添加到一個(gè)安全的應(yīng)用程序中時(shí)娇妓,同時(shí)會(huì)添加一個(gè)過濾器鏈活鹰,而這個(gè)過濾器鏈只會(huì)攔截訪問Spring Boot監(jiān)控端點(diǎn)路徑的請(qǐng)求。這個(gè)過濾器鏈定義了一個(gè)請(qǐng)求匹配器志群,這個(gè)匹配器只匹配監(jiān)控端點(diǎn)路徑,并且具有一個(gè)值為ManagementServerProperties.BASIC_AUTH_ORDER的優(yōu)先級(jí)荠医,這個(gè)優(yōu)先級(jí)只比默認(rèn)的SecurityProperties替補(bǔ)過濾器高一點(diǎn)(數(shù)值小5)宾抓,所以匹配監(jiān)控端點(diǎn)路徑的請(qǐng)求會(huì)先到達(dá)這個(gè)過濾器鏈。
如果你想要你的應(yīng)用程序的安全規(guī)則應(yīng)用到監(jiān)控功能端點(diǎn)上石洗,你可以添加一個(gè)優(yōu)先級(jí)高于監(jiān)控端點(diǎn)過濾器鏈的新的過濾器鏈讲衫,并且讓這個(gè)新過濾器攔截所有訪問監(jiān)控端點(diǎn)的請(qǐng)求。如果你更傾向于對(duì)監(jiān)控端點(diǎn)使用默認(rèn)的安全配置涉兽,那么最簡(jiǎn)單的做法就是將你自己的過濾器鏈添加到監(jiān)控接口過濾器的后面和替補(bǔ)過濾器的前面。示例如下:

@Configuration
@Order(ManagementServerProperties.BASIC_AUTH_ORDER + 1)
public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.antMatcher("/foo/**")
     ...;
  }
}

注意
Spring安全框架在Web層與Servlet的API是綁定的别厘,所以現(xiàn)階段拥诡,Spring安全框架只能應(yīng)用于基于Servlet規(guī)范氮发,運(yùn)行在Servlet容器中的應(yīng)用程序冗懦,而無論容器是否是嵌入式的。當(dāng)然披蕉,Spring安全框架并沒有與Spring MVC框架或者Spring的Web技術(shù)棧綁定没讲,所以他能夠應(yīng)用于所有基于Servlet規(guī)范的應(yīng)用程序。

4. Method Security(方法安全)

Spring安全框架不僅僅支持Web應(yīng)用程序食零,也為Java方法的執(zhí)行提供安全的訪問規(guī)則支持。對(duì)于Spring安全來說娜搂,Java方法只是一種其他形式的“受保護(hù)資源”吱抚。這就意味著方法的訪問規(guī)則是與ConfigAttribute形式一樣的字符串(比如角色或者表達(dá)式),只是應(yīng)用在你的代碼不同的地方秘豹。如何引入方法安全功能呢既绕?第一步就是開啟方法安全功能啄刹,例如:

@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SampleSecureApplication {
}

然后我們就可以直接在方法資源上加注解誓军,例如:

@Service
public class MyService {

  @Secured("ROLE_USER")
  public String secure() {
    return "Hello Security";
  }

}

上面的示例是一個(gè)含有安全方法的業(yè)務(wù)類疲扎。如果Spring像上面的例子那樣創(chuàng)建這種類型的@Bean,那么在這些方法真正被執(zhí)行之前椒丧,這個(gè)類會(huì)被代理,同時(shí)調(diào)用者將需要先通過一個(gè)安全的攔截器句柠。如果訪問被拒絕,那么調(diào)用者將會(huì)得到一個(gè)AccessDeniedException而非該方法的正確執(zhí)行結(jié)果溯职。
當(dāng)然,還有其他的注解類型可以應(yīng)用在方法來執(zhí)行安全限制,比如@PreAuthorize@PostAuthorize甚带,這些注解都允許你編寫含有指向方法參數(shù)和方法返回值的引用的表達(dá)式佳头。

提示
將Web安全和方法安全結(jié)合起來使用并非是不常用的。Web安全過濾器鏈提供用戶粒度的安全功能康嘉,例如認(rèn)證和重定向到登陸頁面,而方法安全能夠提供更細(xì)粒度的安全保證敷钾。

5. Working with Threads(工作線程)

Spring安全本身就是一個(gè)基本的線程邊界肄梨,因?yàn)楫?dāng)前身份被認(rèn)證后,仍然需要被各種下游消費(fèi)者所使用侨赡。最基本的構(gòu)造塊是org.springframework.security.core.context.SecurityContext的實(shí)例對(duì)象粱侣,他包含了一個(gè)Authentication對(duì)象(并且如果用戶已經(jīng)登陸,那么這個(gè)Authentication是被明確標(biāo)記為authenticated的)齐婴。你可以通過org.springframework.security.core.context.SecurityContextHolder的靜態(tài)方法方便的訪問和使用保存在ThreadLocal中的SecurityContext實(shí)例。例如:

SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
assert(authentication.isAuthenticated);

雖然對(duì)于用戶應(yīng)用程序來說眨攘,上面的代碼并不常用嚣州,但這并不代表他沒有用處,相反對(duì)于開發(fā)者來說该肴,這段代碼非常有用匀哄,比如在開發(fā)者想要定制化編寫一個(gè)認(rèn)證過濾器的時(shí)候(盡管認(rèn)證過濾器在Spring安全中是最基本的類雏蛮,開發(fā)者編寫該類的時(shí)候應(yīng)該盡量避免使用SecurityContextHolder).
如果你想要在一個(gè)Web接口中訪問當(dāng)前的已認(rèn)證用戶阱州,你可以在@RequestMapping使用方法參數(shù)注解@AuthenticationPrincipal來注入持有用戶信息的對(duì)象。例如:

@RequestMapping("/foo")
public String foo(@AuthenticationPrincipal User user) {
  ... // do stuff with user
}

@AuthenticationPrincipal注解會(huì)SecurityContext實(shí)例中抽取當(dāng)前Authentication對(duì)象并且調(diào)用其getPrincipal()方法來獲取用戶身份對(duì)象苔货,然后將用戶身份對(duì)象注入到方法參數(shù)中。Authentication持有的Principal(用戶身份)的類型取決于AuthenticationManager驗(yàn)證認(rèn)證時(shí)所使用的類型姻灶。
如果Spring安全從HttpServletRequest中獲取的Principal就是Authentication類型的诈茧,那么開發(fā)者可以用這種方式來直接使用:

@RequestMapping("/foo")
public String foo(Principal principal) {
  Authentication authentication = (Authentication) principal;
  User = (User) authentication.getPrincipal();
  ... // do stuff with user
}

5.1 Processing Secure Methods Asynchronously(以異步的方式處理安全的方法)

自從SecurityContext成為線程邊界后,如果你想要調(diào)用安全的方法做一些異步的后臺(tái)操作曾沈,比如使用@Async注解走触,那么你需要確保這個(gè)上下文是可傳播的。說白了就是將SecurityContext封裝到task(Runnable互广,Callable等)中,然后交給后面的線程去執(zhí)行像樊。Spring安全提供了一些輔助類來幫助開發(fā)者更方便地完成這個(gè)過程旅敷。當(dāng)然,為了傳播SecurityContext@Async方法中涂滴,開發(fā)者需要提供一個(gè)AsyncConfigurer同時(shí)要確保Executor的正確類型:

@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)系作者
  • 序言:七十年代末晴音,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子搁料,更是在濱河造成了極大的恐慌,老刑警劉巖郭计,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件昭伸,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡庐杨,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來罩引,“玉大人袁铐,你說我怎么就攤上這事√藿埃” “怎么了?”我有些...
    開封第一講書人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵瑰谜,是天一觀的道長树绩。 經(jīng)常有香客問我,道長饺饭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任鹊杖,我火速辦了婚禮扛芽,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘涯竟。我一直安慰自己,他們只是感情好庐船,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著揩瞪,像睡著了一般篓冲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上壹将,一...
    開封第一講書人閱讀 51,775評(píng)論 1 307
  • 那天诽俯,我揣著相機(jī)與錄音,去河邊找鬼暴区。 笑死,一個(gè)胖子當(dāng)著我的面吹牛房交,可吹牛的內(nèi)容都是我干的伐割。 我是一名探鬼主播,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼负溪,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼济炎!你這毒婦竟也來了川抡?” 一聲冷哼從身側(cè)響起须尚,我...
    開封第一講書人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤耐床,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后撩轰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體昧廷,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡木柬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年淹办,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怜森。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡副硅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出恐疲,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布违诗,位于F島的核電站,受9級(jí)特大地震影響茸炒,放射性物質(zhì)發(fā)生泄漏阵苇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一紊册、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧囊陡,春花似錦掀亥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吮便。三九已至,卻和暖如春凿可,著一層夾襖步出監(jiān)牢的瞬間授账,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來泰國打工白热, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人纳击。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓攻臀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親刨啸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)离例,斷路器,智...
    卡卡羅2017閱讀 134,672評(píng)論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,827評(píng)論 6 342
  • 要加“m”說明是MB艘包,否則就是KB了. -Xms:初始值 -Xmx:最大值 -Xmn:最小值 java -Xms8...
    dadong0505閱讀 4,837評(píng)論 0 53
  • 8.6 Spring Boot集成Spring Security 開發(fā)Web應(yīng)用耀盗,對(duì)頁面的安全控制通常是必須的。比...
    光劍書架上的書閱讀 76,186評(píng)論 5 146
  • Spring Security 是一個(gè)基于 Spring AOP 和 Servlet 過濾器的安全框架磷醋,它提供了安...
    聰明的奇瑞閱讀 17,112評(píng)論 3 38