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;
}
在AuthenticationManager
的authenticate()
方法中吝羞,用戶可以做三件事情:
- 如果可以確認(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)記脆贵。 - 如果可以確認(rèn)輸入的參數(shù)
authentication
代表一個(gè)非法的用戶身份医清,那么將拋出一個(gè)org.springframework.security.core.AuthenticationException
異常。 - 如果無法判斷輸入的參數(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.AuthenticationProvider
同org.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ǔ)而存在枝誊。
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
組件,ProviderManager
是AuthenticationManager
最常用的一個(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è)都具有特定的功能。圖片如下:
在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)求。
一個(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/**")
...;
}
}
上例中的@Configuration
的bean
將會(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));
}
}