SpringSecurity實(shí)現(xiàn)原理及多過(guò)濾器鏈匹配規(guī)則詳解
SpringSecurity的實(shí)現(xiàn)原理
Spring Security能完成各種認(rèn)證和授權(quán)功能其實(shí)是依賴其底層的多個(gè)過(guò)濾器進(jìn)行實(shí)現(xiàn)的,
借用官方的一張?jiān)韴D如下:

但是實(shí)際上這些Filter并不是直接配置在tomcat中,其實(shí)他們都由一個(gè)叫做FilterChainProxy的類進(jìn)行管理

這些Filter包括Spring Security默認(rèn)生成的以及我們自己自定義的,通通都被封裝成成一個(gè)個(gè)FilterChain并且保存在FilterChainProxy的一個(gè)成員變量中,一個(gè)FilterChain包含多個(gè)Filter,
FilterChainProxy包含多個(gè)FilterChain
原理圖和代碼如下:

public class FilterChainProxy extends GenericFilterBean {
private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
private static final String FILTER_APPLIED = FilterChainProxy.class.getName().concat(".APPLIED");
private List<SecurityFilterChain> filterChains; //多個(gè)過(guò)濾器鏈
private FilterChainProxy.FilterChainValidator filterChainValidator;
private HttpFirewall firewall;
}當(dāng)一個(gè)請(qǐng)求過(guò)來(lái)的時(shí)候,Spring Security會(huì)根據(jù)規(guī)則匹配到第一個(gè)過(guò)濾器鏈,并使用它過(guò)濾該請(qǐng)求
這里又引申出一些問(wèn)題,既然存在多過(guò)濾鏈的情況,那么用戶怎么配置多個(gè)過(guò)濾鏈,并且SpringSecurity是怎么根據(jù)請(qǐng)求選擇相應(yīng)的過(guò)濾器鏈
如何配置多個(gè)過(guò)濾器鏈
其實(shí)配置多個(gè)過(guò)濾器鏈并不難,項(xiàng)目中每存在一個(gè)繼承了WebSecurityConfigurerAdapter類的配置類,Spring Securiy就會(huì)生成一個(gè)過(guò)濾器鏈;
繼承ResourceServerConfigurerAdapter類的配置類也會(huì)對(duì)應(yīng)一個(gè)過(guò)濾器鏈,但是與繼承了WebSecurityConfigurerAdapter類的配置類不同,
多個(gè)繼承ResourceServerConfigurerAdapter類的配置類都共存于一個(gè)過(guò)濾器鏈中,也就是無(wú)論配置多少個(gè)ResourceServerConfigurerAdapter也僅僅存在一個(gè)對(duì)應(yīng)的過(guò)濾鏈。
同時(shí)需要注意的是,存在多個(gè)WebSecurityConfigurerAdapter配置類的時(shí)候,需要用@Order進(jìn)行區(qū)分,代表了它們之間的優(yōu)先級(jí)
配置代碼如下
@Configuration
@Order(200)
public class DefaultWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("DefaultWebSecurityConfig "+http);
}
}
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("WebSecurityConfig "+http);
}
}
@Configuration
public class ResourceSecurityConfig {
public static final String RESOURCE_ID = "resource-server";
@Resource
private ResourceServerTokenServices resourceServerTokenServices;
@Configuration
@EnableResourceServer
public class FirstResourceConfig extends ResourceServerConfigurerAdapter{
//配置token服務(wù)以及資源ID
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID)
.tokenServices(resourceServerTokenServices);
}
//配置訪問(wèn)控制
@Override
public void configure(HttpSecurity http) throws Exception {
System.out.println("FirstResourceConfig "+http);
http
.authorizeRequests()
.antMatchers("/**").access("#oauth2.hasScope('server')")
;
}
}
@Configuration
@EnableResourceServer
public class SecondResourceConfig extends ResourceServerConfigurerAdapter{
//配置token服務(wù)以及資源ID
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID)
.tokenServices(resourceServerTokenServices);
}
//配置訪問(wèn)控制
@Override
public void configure(HttpSecurity http) throws Exception {
System.out.println("SecondResourceConfig "+http);
http
.authorizeRequests()
.antMatchers("/**").access("#oauth2.hasScope('server')")
;
}
}
}
上面的代碼就是配置了三個(gè)過(guò)濾器鏈,最后一個(gè)雖然好像配置了兩個(gè)過(guò)濾器鏈,但是前面已經(jīng)說(shuō)過(guò)了ResourceServerConfigurerAdapter的配置類其實(shí)都會(huì)被合并在同一個(gè)過(guò)濾器鏈
那我們?cè)趺粗赖牡拇_確存在三個(gè)過(guò)濾器鏈呢,過(guò)濾器鏈都存在FilterChainProxy的成員變量中,我們只要debug一下看看它的成員變量就知道了,如下圖:

每一個(gè)FilterChain都包含一個(gè)RequestMatcher,如下圖

實(shí)際上過(guò)濾器鏈的RequestMatcher是和我們的配置有關(guān)系,前面我們的配置都是使用了.authorizeRequests()方法,其實(shí)這個(gè)就是代表AnyRequestMatcher這個(gè)類
我們改變一下配置可以看看RequestMatcher會(huì)有什么變化
@Configuration
@Order(200)
public class DefaultWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("DefaultWebSecurityConfig "+http);
http.antMatcher("/user")
.authorizeRequests().anyRequest().permitAll()
.and()
.formLogin();
}
}我們把第一個(gè)配置類的規(guī)則改變了,看看debug會(huì)發(fā)生什么變化

它變成了AntPathRequestMatcher,這里可以看出我們的配置會(huì)直接影響到SpringSecurity為我們選擇合適的過(guò)濾器鏈,如果改成上面的配置,其實(shí)就代表
DefaultWebSecurityConfig對(duì)應(yīng)的過(guò)濾器鏈只匹配/user的請(qǐng)求,而其他兩個(gè)過(guò)濾器鏈就是匹配/**的請(qǐng)求,雖然兩個(gè)過(guò)濾器鏈都是匹配相同的地址,
但是他們的優(yōu)先級(jí)不同,一般ResourceServerConfigurerAdapter類默認(rèn)的優(yōu)先級(jí)是@Order(3),而WebSecurityConfigurerAdapter類默認(rèn)是@Order(100)
優(yōu)先級(jí)的值越低就越先被匹配到,所以ResourceServerConfigurerAdapter會(huì)被先匹配到
如何匹配多個(gè)過(guò)濾器鏈
要知道SpringSecurity是怎樣匹配一個(gè)合適的過(guò)濾鏈,我們先看下面的代碼:
private List<Filter> getFilters(HttpServletRequest request) {
Iterator var2 = this.filterChains.iterator();
SecurityFilterChain chain;
do {
if (!var2.hasNext()) {
return null;
}
chain = (SecurityFilterChain)var2.next();
} while(!chain.matches(request)); //這里就是匹配過(guò)濾器鏈的方法
return chain.getFilters();
}chain.matches()就是匹配一個(gè)過(guò)濾器鏈的方法,我們繼續(xù)進(jìn)入該方法:
public final class DefaultSecurityFilterChain implements SecurityFilterChain {
private static final Log logger = LogFactory.getLog(DefaultSecurityFilterChain.class);
private final RequestMatcher requestMatcher;
private final List<Filter> filters;
public boolean matches(HttpServletRequest request) {
return this.requestMatcher.matches(request);//實(shí)際上是調(diào)用了requestMatcher的matches方法
}
}實(shí)際上是調(diào)用了RequestMatcher的matches方法,所以其實(shí)SpringSecurity在對(duì)一個(gè)請(qǐng)求選擇一個(gè)過(guò)濾器鏈,就是通過(guò)調(diào)用每個(gè)過(guò)濾器鏈的RequestMatcher進(jìn)行匹配
匹配到第一個(gè)成功的就是處理該請(qǐng)求的過(guò)濾器鏈
總結(jié)
所以我們可以得出SpringSecurity在處理一個(gè)請(qǐng)求的流程
1.獲取該Request的URI地址
2.遍歷所有FilterChain,并且用它的RequestMatcher匹配Request的URI地址
3.匹配到第一個(gè)合適的FilterChain,把該請(qǐng)求放到該過(guò)濾器鏈進(jìn)行處理
4.過(guò)濾器鏈中的每一個(gè)過(guò)濾器獲得請(qǐng)求,并對(duì)請(qǐng)求做相應(yīng)的處理
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot防止大量請(qǐng)求攻擊的實(shí)現(xiàn)
在有些特定的時(shí)候需要加上IP訪問(wèn)時(shí)間限制,防止一個(gè)IP多次訪問(wèn)請(qǐng)求,本文主要介紹了SpringBoot防止大量請(qǐng)求攻擊的實(shí)現(xiàn),感興趣的可以了解一下2021-11-11
Java語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單FTP軟件 FTP遠(yuǎn)程文件管理模塊實(shí)現(xiàn)(10)
這篇文章主要為大家詳細(xì)介紹了Java語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單FTP軟件,F(xiàn)TP遠(yuǎn)程文件管理模塊的實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
將JSON字符串?dāng)?shù)組轉(zhuǎn)對(duì)象集合方法步驟
這篇文章主要給大家介紹了關(guān)于將JSON字符串?dāng)?shù)組轉(zhuǎn)對(duì)象集合的方法步驟,文中通過(guò)代碼示例介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08
Java URL自定義私有網(wǎng)絡(luò)協(xié)議
URI與URL的區(qū)別 一.先來(lái)序言一段 二.協(xié)議的自定義的理解 三.自定義協(xié)議與URL的關(guān)系 四.URL自定義私有協(xié)議實(shí)戰(zhàn) 五.后話,自定義mineType解析器2016-04-04
kafka內(nèi)外網(wǎng)訪問(wèn)配置方式
這篇文章主要介紹了kafka內(nèi)外網(wǎng)訪問(wèn)配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09
Java死鎖代碼實(shí)例及產(chǎn)生死鎖必備的四個(gè)條件
這篇文章主要介紹了Java死鎖代碼實(shí)例及產(chǎn)生死鎖必備的四個(gè)條件,Java 發(fā)生死鎖的根本原因是,在申請(qǐng)鎖時(shí)發(fā)生了交叉閉環(huán)申請(qǐng),synchronized在開(kāi)發(fā)中最好不要嵌套使用,容易導(dǎo)致死鎖,需要的朋友可以參考下2024-01-01
使用Mybatis對(duì)數(shù)據(jù)庫(kù)進(jìn)行單表操作的實(shí)現(xiàn)示例
這篇文章主要介紹了使用Mybatis對(duì)數(shù)據(jù)庫(kù)進(jìn)行單表操作的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08

