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

1. Introduction(簡介)

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

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

應(yīng)用程序的安全問題或多或少可以歸納為兩個相互獨(dú)立的基本問題:認(rèn)證(Authentication,解決身份識別問題,即識別用戶身份是否合法)和授權(quán)(Authorization ,解決訪問權(quán)限問題,即允許用戶做什么)。有些人使用"access control(訪問控制)"來代替"authorization(授權(quán))",雖然這兩種說法會給用戶帶來一些困惑,但由于"authorization(授權(quán))"這個詞在有些地方被過度的解釋了,這導(dǎo)致"access control(訪問控制)"這種說法更有助于我們理解這種控制用戶的訪問權(quán)限的方式。Spring安全框架的體系結(jié)構(gòu)在設(shè)計(jì)的時候就將認(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 ,而這個接口只有一個方法:

public interface AuthenticationManager {

  Authentication authenticate(Authentication authentication)
    throws AuthenticationException;
}

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

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

org.springframework.security.core.AuthenticationException是一個運(yùn)行時異常。一般情況下,該異常會被應(yīng)用程序使用專門的處理器進(jìn)行處理,而如何處理取決于應(yīng)用程序的形式和用途。換句話說,一般情況下,應(yīng)用程序并不指望開發(fā)者編寫代碼去捕獲和處理這些異常,而是提供默認(rèn)的策略,由應(yīng)用程序自身來處理這個異常,比如,應(yīng)用程序會提供一個界面同時渲染一個頁面來告訴用戶認(rèn)證失敗,同時后臺的HTTP服務(wù)也將會發(fā)送401狀態(tài)碼,也會根據(jù)應(yīng)用的上下文環(huán)境來決定是否攜帶WWW-Authenticate頭部。
最常用的org.springframework.security.authentication.AuthenticationManager實(shí)現(xiàn)類是org.springframework.security.authentication.ProviderManager,該類維護(hù)了一個由接口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)部包含另一個方法允許調(diào)用者測試是否支持傳入的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>,主要是用來測試是否支持傳入authenticate()方法的authentication參數(shù)的類型。由于ProviderManager代理了一個AuthenticationProviders鏈,所以可以在同一個應(yīng)用中支持多種不同的認(rèn)證機(jī)制。一般情況下,ProviderManager會跳過那些自己不支持的Authentication的實(shí)例類型。
一個ProviderManager有一個可選的父級provider,如果所有的providers都返回的是null,也就是說所有的認(rèn)證機(jī)制都無法確定當(dāng)前的用戶身份是合法的,最終將由這個父級(或者全局)的provider來決定,如果不存在這個父級的provider也會返回null,最終會拋出AuthenticationException。
有時候,應(yīng)用將被保護(hù)的資源按照一定的規(guī)則分成邏輯上的分組(比如,所有的web資源都通過路徑來分組,即將資源按照linux目錄樹的形式進(jìn)行分組),并且每一個分組都擁有專屬的AuthenticationManager,而這些AuthenticationManager通常都是一個ProviderManager,這些AuthenticationManager共享一個父級(或者全局)的AuthenticationManager,這個父級的AuthenticationManager作為所有的providers的替補(bǔ)而存在。

圖一. 由ProviderManager組成的AuthenticationManager層級結(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接口的類對象來設(shè)置獲取用戶詳情的方式。
下面的例子展示了如何在一個應(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");
  }

}

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

@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()方法,在這個方法中,AuthenticationManagerBuilder的實(shí)例只是被該方法的調(diào)用者使用來創(chuàng)建一個局部的AuthenticationManager,這個局部的AuthenticationManager是全局AuthenticationManager的孩子。在一個Spring Boot應(yīng)用中,我們可以使用@Autowired注解將全局的AuthenticationManager注入到其他的bean中,但是無法將一個局部的AuthenticationManager注入到其他的bean中,除非我們通過配置@Bean的方式明確的將該局部的AuthenticationManager的實(shí)例作為一個組件發(fā)布出去。
Spring Boot提供了一個默認(rèn)的全局AuthenticationManager,我們可以通過提供自己的AuthenticationManager組件來替換他。這個默認(rèn)的AuthenticationManager組件是足夠安全的,我們無需對他有過多的擔(dān)心,除非你確實(shí)需要一個自定義的AuthenticationManager。如果我們做了一些配置創(chuàng)建了一個AuthenticationManager組件,我們可以將該組件應(yīng)用到局部的受保護(hù)資源上而無需擔(dān)心全局的AuthenticationManager。

前文說的全局的provider,是ProviderManager組件,ProviderManagerAuthenticationManager最常用的一個實(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安全框架提供這個接口的三種實(shí)現(xiàn),每一種都可以委托給一個org.springframework.security.access.AccessDecisionVoter<S>鏈,這與ProviderManager委托給AuthenticationProviders有一點(diǎn)類似。
一個AccessDecisionVoter主要用來處理代表用戶身份的Authentication對象和一個由ConfigAttributes描述的安全對象:

boolean supports(ConfigAttribute attribute);

boolean supports(Class<?> clazz);

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

這個安全對象指的是參數(shù)object,其類型就是AccessDecisionVoter<S>的泛型參數(shù)S,他代表任何用戶想要訪問的目標(biāo)(Web資源或Java類的方法是該對象的兩種最常見的形式)。ConfigAttributes指的是參數(shù)attributes,他是org.springframework.security.access.ConfigAttribute的集合,保存了安全對象的元數(shù)據(jù),而這些元數(shù)據(jù)確定了訪問該安全對象所要求的權(quán)限。ConfigAttribute是只有一個方法的接口,而這個方法返回一個String類型的值,這個值通常情況下是一串編碼,表示資源所有者制定的資源訪問規(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á)式,同時還能為他們創(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所扮演的角色對與我們理解Web安全是有幫助的。下面的圖片展示了Http請求處理器的層級結(jié)構(gòu)。

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

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

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

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

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

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

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

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

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

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

一個安全過濾器鏈(等同與一個WebSecurityConfigurerAdapter)持有一個請求匹配器,這個匹配器被用來決定該過濾器鏈?zhǔn)欠襁m用于當(dāng)前的HTTP請求。一旦一個HTTP請求適用與一個特定的過濾器鏈,其他的過濾器鏈則不會被應(yīng)用于這個HTTP請求。但是在一個過濾器鏈內(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安全的時候最容易犯的一個錯誤就是忘記了這些匹配器將應(yīng)用于不同的程序,一個是請求匹配器,將應(yīng)用于整個過濾器鏈,而其他的匹配器僅僅是用來選擇訪問規(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)控功能添加到一個安全的應(yīng)用程序中時,同時會添加一個過濾器鏈,而這個過濾器鏈只會攔截訪問Spring Boot監(jiān)控端點(diǎn)路徑的請求。這個過濾器鏈定義了一個請求匹配器,這個匹配器只匹配監(jiān)控端點(diǎn)路徑,并且具有一個值為ManagementServerProperties.BASIC_AUTH_ORDER的優(yōu)先級,這個優(yōu)先級只比默認(rèn)的SecurityProperties替補(bǔ)過濾器高一點(diǎn)(數(shù)值小5),所以匹配監(jiān)控端點(diǎn)路徑的請求會先到達(dá)這個過濾器鏈。
如果你想要你的應(yīng)用程序的安全規(guī)則應(yīng)用到監(jiān)控功能端點(diǎn)上,你可以添加一個優(yōu)先級高于監(jiān)控端點(diǎn)過濾器鏈的新的過濾器鏈,并且讓這個新過濾器攔截所有訪問監(jiān)控端點(diǎn)的請求。如果你更傾向于對監(jiān)控端點(diǎn)使用默認(rè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ī)則支持。對于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";
  }

}

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

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

5. Working with Threads(工作線程)

Spring安全本身就是一個基本的線程邊界,因?yàn)楫?dāng)前身份被認(rèn)證后,仍然需要被各種下游消費(fèi)者所使用。最基本的構(gòu)造塊是org.springframework.security.core.context.SecurityContext的實(shí)例對象,他包含了一個Authentication對象(并且如果用戶已經(jīng)登陸,那么這個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);

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

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

@AuthenticationPrincipal注解會SecurityContext實(shí)例中抽取當(dāng)前Authentication對象并且調(diào)用其getPrincipal()方法來獲取用戶身份對象,然后將用戶身份對象注入到方法參數(shù)中。Authentication持有的Principal(用戶身份)的類型取決于AuthenticationManager驗(yàn)證認(rèn)證時所使用的類型。
如果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)用安全的方法做一些異步的后臺操作,比如使用@Async注解,那么你需要確保這個上下文是可傳播的。說白了就是將SecurityContext封裝到task(Runnable,Callable等)中,然后交給后面的線程去執(zhí)行。Spring安全提供了一些輔助類來幫助開發(fā)者更方便地完成這個過程。當(dāng)然,為了傳播SecurityContext@Async方法中,開發(fā)者需要提供一個AsyncConfigurer同時要確保Executor的正確類型:

@Configuration
public class ApplicationConfiguration extends AsyncConfigurerSupport {
  @Override
  public Executor getAsyncExecutor() {
    return new DelegatingSecurityContextExecutorService(Executors.newFixedThreadPool(5));
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評論 6 541
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,436評論 2 378

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

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