網站首頁 編程語言 正文
前言
本文跟大家聊聊Spring Security,看似簡單易用的配置,實際上很難理解的配置體系。
配置體系概要
Spring Security的配置體系在我看來,主要分為兩大類:
- 負責構建核心組件的
SecurityBuilder<O>
- 負責對核心組件的Builder進行配置的
SecurityConfigurer<O, B>
核心組件
先大致介紹一下Spring Security的核心組件,如此我們才能更好的理解相關配置。
-
FilterChainProxy
這是個Filter。實際上是SpringSecurity的全權代理。從名字我們也能看出,他是一個FilterChain的“代理”。這里的“代理”類似于明星的經紀人,就是負責接活的人。而FilterChain是干活的。FilterChain代表的是一個基于SpringSecurity的安全過濾器鏈條。很明顯,他是一系列Filter的組合。在請求到來時,FilterChainProxy先遍歷所有的SecurityFilterChain,然后找到匹配的SecurityFilterChain,遍歷其中的Filter并調用doFilter方法,從而實現安全過濾器的邏輯。
-
DefaultSecurityFilterChain
這是FilterChainProxy的助手,同時也是一個完整的安全過濾器鏈條。他為FilterChainProxy提供了滿足某一路徑條件所有Filter。
這兩級結構,為下面這一場景提供了可靠的安全解決方案:
應用中存在多個DispatcherServlet,分別負責不同的請求路徑。然后二者負責的功能,所要求的安全性不同。例如:一個為手機端應用提供服務,面向廣大市民,認證機制使用的是JWT。另一個負責的是后臺管理服務,認證機制使用的是Session,面向企業管理人員。
按照兩級結構,就基于兩個DispatcherServlet的路徑配置不同的SecurityFilterChain,交給FilterChainProxy即可。
核心組件的構造器
Spring Security抽象出一個共同的接口,兩個不同的構造器來分配構造上面兩個核心組件。
- 構造器接口:
public interface SecurityBuilder<O> {
O build() throws Exception;
}
這個也就是開頭提到構造體系的核心組件之一。
- 兩個核心組件的建造者
- FilterChainProxy的構造器:WebSecurity
這個相對簡單一些,不準備細聊,感興趣的可以自己看看源碼。 - DefaultSecurityFilterChain的構造器:HttpSecurity
這個是我們主要配置的重點,也是復雜度爆表的。我們將重點聊這個。
- FilterChainProxy的構造器:WebSecurity
HttpSecurity
為了搞清楚,我們需要先從HttpSecurity的“族譜及社會關系”開始。
UML中的O,是DefaultSecurityFilterChain。
B和H都指代的則是構建者,也就是HttpSecurity 。B的作用之一就是方便你鏈式調用。
PS: 為了不至于上來就勸退,決定由淺入深的啃圖里的類/接口。
AbstractSecurityBuilder
/**
* 基本的SecurityBuilder,主要是確保對象只構建一次。
*/
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
@Override
public final O build() throws Exception {
// 利用AtomicBoolean,確保只構建一次
if (this.building.compareAndSet(false, true)) {
this.object = doBuild();
return this.object;
}
throw new AlreadyBuiltException("This object has already been built");
}
// 真正的構建方法,由繼承者實現。
protected abstract O doBuild() throws Exception;
}
HttpSecurityBuilder
Http請求的安全構建者。這是個接口,定義的是Http請求的安全構建者的行為。雖然目前只有一個實現類HttpSecurity。但是大家一定要注意,從設計上的職責和定義上進行區分。
public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>>
extends SecurityBuilder<DefaultSecurityFilterChain> {
// 管理configurerer:2個方法
<C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C getConfigurer(Class<C> clazz);
<C extends SecurityConfigurer<DefaultSecurityFilterChain, H>> C removeConfigurer(Class<C> clazz);
// 管理sharedObjects:2個方法
<C> void setSharedObject(Class<C> sharedType, C object);
<C> C getSharedObject(Class<C> sharedType);
// 這兩個方法與登錄有關,也是web安全的一部分。
H authenticationProvider(AuthenticationProvider authenticationProvider);
H userDetailsService(UserDetailsService userDetailsService) throws Exception;
// 添加過濾器相關
H addFilterAfter(Filter filter, Class<? extends Filter> afterFilter);
H addFilterBefore(Filter filter, Class<? extends Filter> beforeFilter);
H addFilter(Filter filter);
}
在職責上,繼承了SecurityBuilder,是DefaultSecurityFilterChain的構建者。
在定義上,引入了另一個泛型:H extends HttpSecurityBuilder<H>
。第一眼是不是感覺云里霧里,不明就里?其實,我個人認為,這個HttpSecurityBuilder名字并不是很恰當的。我覺得ConfigurableSecurityBuilder或許更恰當。因為他定義的行為包括三部分:管理SecurityConfigurer、管理共享對象、過濾器相關。而以上行為的本質還是告訴建造者,如何構建DefaultSecurityFilterChain,可以理解為建造者的配置。
而H的作用是,限定行為/接口方法中的SecurityConfigurer的。而SecurityConfigurer本身又帶2個泛型,等會兒我們再細聊。
SecurityConfigurer
主要用于配置SecurityBuilder,幫助SecurityBuilder構建O。
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
void init(B builder) throws Exception;
void configure(B builder) throws Exception;
}
O是建造者B要建造的目標對象。這個類定義的意思是,我可以配置負責構造O的Builder。
init方法,可以初始化自身,也可以初始化builder(例如,提前設置一些屬性)。
configurer方法,就是配置builder的重頭戲了。一般來說,都是往builder里添加過濾器。
SecurityConfigurer有一個抽象類指定配置生產DefaultSecurityFilterChain的HttpSecurityBuilder。
public abstract class AbstractHttpConfigurer<T extends AbstractHttpConfigurer<T, B>, B extends HttpSecurityBuilder<B>>
extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B> {}
對應到SecurityConfigurer的泛型:
O為DefaultSecurityFilterChain, H為B。
B限定于生產DefaultSecurityFilterChain的HttpSecurityBuilder。額,還是覺得挺亂的是吧?從這里開始,復雜度就開始飆升了。系好安全帶,開車!
先用一句話概括:AbstractHttpConfigurer的職責是配置生產DefaultSecurityFilterChain的HttpSecurityBuilder。按照這個定義,我們簡化一下類定義,刪除掉泛型H,改成下面:
public abstract class AbstractHttpConfigurer<B extends HttpSecurityBuilder>
extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B>
雖然定義還是很長,但已經能理解了。此時只有一個泛型B了,而泛型B的上界是HttpSecurityBuilder。
那么原來的T到底是要干嘛呢?從定義看:T extends AbstractHttpConfigurer<T, B>;無非就是指待某個具體的AbstractHttpConfigurer唄。因此,個人認為,只有一個作用,為了方便方法調用:方便返回值或入參的類型自動轉換。
AbstractConfiguredSecurityBuilder
通過SecurityConfigurer進行配置的SecurityBuilder的抽象類。
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
extends AbstractSecurityBuilder<O> {
// 記錄每一個SecurityConfigurer的Class對應的所有SecurityConfigurer注冊實例。
private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<>();
// 共享對象。這里涉及到需要多個過濾器協作的對象。
private final Map<Class<?>, Object> sharedObjects = new HashMap<>();
// 為了支持對象的初始化。
private ObjectPostProcessor<Object> objectPostProcessor;
private <C extends SecurityConfigurer<O, B>> void add(C configurer) {
// 為了節省篇幅,簡單說一下功能:登記/注冊configurer到this.configurers
}
/**
* 核心構建方法:模板模式
*/
@Override
protected final O doBuild() throws Exception {
// 同步鎖
synchronized (this.configurers) {
// 初始化階段
this.buildState = BuildState.INITIALIZING;
// 初始化前:允許子類進行初始化前的預處理。-鉤子方法
beforeInit();
// 初始化:調用所有的configurers的init方法
init();
// 配置階段
this.buildState = BuildState.CONFIGURING;
// 配置前:允許子類進行配置前的預處理。-鉤子方法
beforeConfigure();
// 進行配置:調用所有的configurers的configure方法
configure();
// 構建階段
this.buildState = BuildState.BUILDING;
// 執行構建;與其他方法不同,這是個抽象方法。因為只有具體實現類才知道要new什么對象。
O result = performBuild();
// 構建完成
this.buildState = BuildState.BUILT;
return result;
}
}
}
不知道你們有沒有發現,這個類的定義與前面的SecurityConfigurer在泛型上,是一模一樣的!這樣的安排都是為了使用SecurityConfigurer。
- 小結一下:
作用一:支持通過SecurityConfigurer對構建者進行配置。
作用二:支持對構建對象的IOC初始化過程,例如:afterPropertiesSet()、各種Aware的相關方法的調用。當然也可以通過注冊自定義的ObjectPostProcessor,對目標對象進行修改。
作用三:通過模板模式,統一對象O的構建步驟/流程。基于SecurityConfigurer進行配置的構建流程。
Customizer<T>
沒想到吧?還有一個編外人員。別緊張,他是我們用來修改SecurityConfigurer的。上一篇文章的demo,就是一個Customizer。他的原理/思路也十分簡單,就是我把當前對象丟給你,你想配置什么,自己設置就是了。例如:
httpSecurity.csrf(csrfConfigurer -> csrfConfigurer.ignoringRequestMatchers(“/**/*.do”))`。
這意思就是,我給你傳一個配置HttpSecurity的CsrfConfigurer,HttpSecurity會調用Customizer#customize
方法,然后就能執行到你配置的csrfConfigurer.ignoringRequestMatchers("/**/*.do")
,對路徑/**/*.do不校驗csrfToken。
可能有同學不熟悉lambda表達式。上面csrf方法傳入的參數等價于
// CsrfConfigurer<HttpSecurity>是配置HttpSecurity的Configurer,他會引入CsrfFilter。
new Customizer<CsrfConfigurer<HttpSecurity>>() {
public void customize(CsrfConfigurer csrfConfigurer) {
csrfConfigurer.ignoringRequestMatchers("/**/*.do");
}
}
HttpSecurity的詳解
好了,終于到這位大爺了。
public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
private final RequestMatcherConfigurer requestMatcherConfigurer;
private List<OrderedFilter> filters = new ArrayList<>();
//
private FilterOrderRegistration filterOrders = new FilterOrderRegistration();
private AuthenticationManager authenticationManager;
public HttpSecurity csrf(Customizer<CsrfConfigurer<HttpSecurity>> csrfCustomizer) throws Exception {
ApplicationContext context = getContext();
// 這里調用了3個方法,依次為:
// 1. 創建CsrfConfigurer
// 2. 類似于CAS,如果存在,就直接返回,否則就注冊。
// 3. 調用Customizer.customize方法配置CsrfConfigurer
csrfCustomizer.customize(getOrApply(new CsrfConfigurer<>(context)));
return HttpSecurity.this;
}
private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(C configurer)
throws Exception {
// 從已注冊的configurers中獲取對應的configurer
// 之所以會這樣設計,是因為需要對HttpSecurity進行一些默認配置。后面會說。
// 因此需要確保配置的是同一個對象。
C existingConfig = (C) getConfigurer(configurer.getClass());
if (existingConfig != null) {
return existingConfig;
}
return apply(configurer);
}
public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer) throws Exception {
// 設置configurer的objectPostProcessor。后面configurer在配置時,會通過他對某些對象進行初始化。如此才能完成ObjectPostProcessor的初始化工作。
configurer.addObjectPostProcessor(this.objectPostProcessor);
// 構建者,為了便于configurer獲取共享對象。
configurer.setBuilder((B) this);
// 調用父類的add方法注冊configurer
add(configurer);
return configurer;
}
@Override
protected DefaultSecurityFilterChain performBuild() {
// 對過濾器進行排序
this.filters.sort(OrderComparator.INSTANCE);
List<Filter> sortedFilters = new ArrayList<>(this.filters.size());
for (Filter filter : this.filters) {
sortedFilters.add(((OrderedFilter) filter).filter);
}
// 構建DefaultSecurityFilterChain
return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters);
}
}
由于可以配置的SecurityConfigurer實在是太多了,這里挑了相對簡單的CsrfConfigurer作為典型案例。具體的過程,如摘抄的源碼上的注釋,就不多啰嗦了。
通過SecurityConfigurer引入Filter
不知道大家有沒有這樣一個疑問:我們并沒有看到CsrfFilter是如何添加到HttpSecurity的filters屬性,只看到引入的CsrfConfigurer。而這就是,我們SecurityConfigurer最為重要的存在意義:引入Filter。AbstractConfiguredSecurityBuilder#doBuild
方法,我們知道在真正構建目標對象之前,會先后調用SecurityConfigurer#init
和SecurityConfigurer#configure
方法。而引入Filter的密碼就藏在configure方法中。
public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
extends AbstractHttpConfigurer<CsrfConfigurer<H>, H> {
@Override
public void configure(H http) {
// 創建CsrfFilter
CsrfFilter filter = new CsrfFilter(this.csrfTokenRepository);
RequestMatcher requireCsrfProtectionMatcher = getRequireCsrfProtectionMatcher();
if (requireCsrfProtectionMatcher != null) {
// 指定需要防御CSRF的請求
filter.setRequireCsrfProtectionMatcher(requireCsrfProtectionMatcher);
}
AccessDeniedHandler accessDeniedHandler = createAccessDeniedHandler(http);
if (accessDeniedHandler != null) {
// 指定訪問異常處理器
filter.setAccessDeniedHandler(accessDeniedHandler);
}
LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);
if (logoutConfigurer != null) {
// 登出接口也進行CSRF防御
logoutConfigurer.addLogoutHandler(new CsrfLogoutHandler(this.csrfTokenRepository));
}
SessionManagementConfigurer<H> sessionConfigurer = http.getConfigurer(SessionManagementConfigurer.class);
if (sessionConfigurer != null) {
// 這里涉及Session管理,后面聊認證的時候再細聊
sessionConfigurer.addSessionAuthenticationStrategy(getSessionAuthenticationStrategy());
}
// 通過ObjectPostProcessor完成初始化,或者修改
filter = postProcess(filter);
// 向HttpSecurity中添加CsrfFilter
http.addFilter(filter);
}
}
如源碼所示,其configure方法的核心業務流程為;
- 創建Filter
- 根據相關配置設置Filter
- 通過ObjectPostProcessor完成初始化
- 向HttpSecurity中添加Filter
幾乎所有與Filter相關的SecurityConfigurer都是這樣對HttpSecurity實現配置的。而配置的重點也就是向HttpSecurity中添加Filter。
HttpSecurity默認配置
新版本的默認配置在HttpSecurityConfiguration
class HttpSecurityConfiguration {
@Bean(HTTPSECURITY_BEAN_NAME)
@Scope("prototype")
HttpSecurity httpSecurity() throws Exception {
LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(this.context);
// 認證管理器的建造者,會放到
AuthenticationManagerBuilder authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(
this.objectPostProcessor, passwordEncoder);
authenticationBuilder.parentAuthenticationManager(authenticationManager());
authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher());
// 創建HttpSecurity,同時會將authenticationBuilder放入sharedObjects中
HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
// 異步請求管理器,需要支持獲取當前請求的用戶憑證相關信息
WebAsyncManagerIntegrationFilter webAsyncManagerIntegrationFilter = new WebAsyncManagerIntegrationFilter();
webAsyncManagerIntegrationFilter.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
// @formatter:off
http
// 引入CsrfFilter
.csrf(withDefaults())
.addFilter(webAsyncManagerIntegrationFilter)
// 引入異常處理器,以便處理權限異常
.exceptionHandling(withDefaults())
// 引入HeaderWriterFilter,便于設置默認的安全請求頭
.headers(withDefaults())
// 引入SessionManagementFilter,以便確保session相關功能
.sessionManagement(withDefaults())
// 引入SecurityContextHolderFilter,以便獲取當前用戶
.securityContext(withDefaults())
// 引入RequestCacheAwareFilter,以便恢復認證前的訪問異常請求
.requestCache(withDefaults())
// 引入AnonymousAuthenticationFilter,管理匿名用戶
.anonymous(withDefaults())
// 引入SecurityContextHolderAwareRequestFilter,以便基于servletApi實現認證
.servletApi(withDefaults())
// 引入DefaultLoginPageGeneratingFilter,以便自動生成登錄頁面
.apply(new DefaultLoginPageConfigurer<>());
// 引入LogoutFilter,以便處理默認的登出請求
http.logout(withDefaults());
// @formatter:on
// 引入基于spring的spi機制引入的configurer,如果開發者沒有配置的,目前應該是沒有相關的configurer。
applyDefaultConfigurers(http);
return http;
}
}
總結
-
配置體系主要包括:
SecurityBuilder<O>
、SecurityConfigurer<O, B>
-
FilterChainProxy由WebSecurity構建。
-
DefaultSecurityFilterChain由HttpSecurity構建。
-
SecurityConfigurer<O, B>
負責配置SecurityBuilder,指引SecurityBuilder如何構建。 -
HttpSecurity本質上是通過一系列的
SecurityConfigurer<O, B>
創建Filter,來構建DefaultSecurityFilterChain。 -
我們配置HttpSecurity的原理如下:
HttpSecurity ->
SecurityConfigurer<O, B>
-> Customizer本質上是通過Customizer修改SecurityConfigurer,從而改變生成的Filter的屬性。
-
推薦使用基于Customizer的HttpSecurity方法進行配置。作用清晰:引入某一過濾器,配置明確:寫在Customizer里面了。
后記
本文我們討論了Spring Security的配置體系,希望大家看源碼時不至于在懵逼,琢磨某個Filter時不至于迷失在源碼里。下一次,我們聊聊UsernamePasswordAuthenticationFilter。相信大家期盼已久了吧。
參照
Spring Security的架構
原文鏈接:https://blog.csdn.net/Evan_L/article/details/136707082
- 上一篇:沒有了
- 下一篇:沒有了
相關推薦
- 2024-03-04 JQ實現將div的滾動條滾動到指定子元素所在的位置
- 2022-11-25 ASP.NET?MVC使用異步Action的方法_實用技巧
- 2022-05-03 python讀寫xml文件實例詳解嘛_python
- 2022-03-27 Android實現井字游戲_Android
- 2022-01-03 踩坑解決mongoose對已經存在的集合查詢,查詢條件不起限制作用的問題
- 2022-03-14 Linux磁盤格式化和掛載(linux服務器硬盤掛載步驟)
- 2022-11-27 C語言中花式退出程序的方式總結_C 語言
- 2022-07-06 如何利用python創建、讀取和修改CSV數據文件_python
- 欄目分類
-
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支