Spring Security基本架構(gòu)與初始化操作流程詳解
Spring Security 是基于web的安全組件,所以一些相關(guān)類會(huì)分散在 spring-security包和web包中。Spring Security通過(guò)自定義Servlet的Filter的方式實(shí)現(xiàn),具體架構(gòu)可參考官網(wǎng)Spring Security: Architecture
這里使用Spring Boot 2.7.4版本,對(duì)應(yīng)Spring Security 5.7.3版本
基本架構(gòu)

首先左側(cè)是Servlet中的Filter組成的FilterChain,Spring Security通過(guò)注冊(cè)一個(gè)DelegatingFilterProxy的Filter,然后在該P(yáng)roxy中內(nèi)置多條Spring Security組織的Security Filter Chain(chain中套娃一個(gè)chain),一個(gè)Security Filter Chain又有多個(gè)Filter,通過(guò)不同的規(guī)則將Request匹配到第一個(gè)滿足條件的Security Filter Chain。
Web源碼
既然Spring Security涉及到Filter,而Filter是Servlet中的組件,這里就存在一個(gè)將Spring Security的頂級(jí)Filter注冊(cè)到Servlet Context的過(guò)程。
首先關(guān)注javax.servlet.ServletContainerInitializer,該類是tomcat-embed-core包中的類:
// 通過(guò)SPI方式導(dǎo)入實(shí)現(xiàn)類:
// META-INF/services/javax.servlet.ServletContainerInitializer
public interface ServletContainerInitializer {
/**
* Receives notification during startup of a web application of the classes within the web application
* that matched the criteria defined via the annotation:
* javax.servlet.annotation.HandlesTypes
*
* 處理javax.servlet.annotation.HandlesTypes注解標(biāo)注類型的實(shí)現(xiàn)類
**/
void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}
該接口實(shí)現(xiàn)類由SPI方式導(dǎo)入,我們來(lái)到spring-web包中:

可以看到spring對(duì) 該接口的實(shí)現(xiàn)類為:org.springframework.web.SpringServletContainerInitializer
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = Collections.emptyList();
...
// 添加
if (webAppInitializerClasses != null) {
initializers = new ArrayList<>(webAppInitializerClasses.size());
for (Class<?> waiClass : webAppInitializerClasses) {
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
}
...
// 排序
AnnotationAwareOrderComparator.sort(initializers);
// 執(zhí)行
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
SpringServletContainerInitializer中調(diào)用了一系列org.springframework.web.WebApplicationInitializer#onStartup
可以看到WebApplicationInitializer 有一系列實(shí)現(xiàn)類:

其中就有Security相關(guān)的。到此,以上均為 Spring Web中的內(nèi)容,Spring Security就是基于以上擴(kuò)展而來(lái)。
接上文,來(lái)看看org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer:
public abstract class AbstractSecurityWebApplicationInitializer implements WebApplicationInitializer {
public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain";
...
@Override
public final void onStartup(ServletContext servletContext) {
beforeSpringSecurityFilterChain(servletContext);
...
insertSpringSecurityFilterChain(servletContext);
afterSpringSecurityFilterChain(servletContext);
}
...
}
但是,經(jīng)過(guò)調(diào)試發(fā)現(xiàn),Spring Security的Filter注冊(cè)過(guò)程并不是上面的步驟。
重要:
Spring Security 注冊(cè)Filter 不是通過(guò)上文的 javax.servlet.ServletContainerInitializer和org.springframework.web.WebApplicationInitializer#onStartup 而是org.springframework.boot.web.servlet.ServletContextInitializer,來(lái)看看ServletContextInitializer的說(shuō)明:
/**
* 不同于WebApplicationInitializer,實(shí)現(xiàn)該接口的類(且沒(méi)有實(shí)現(xiàn)WebApplicationInitializer)
* 不會(huì)被SpringServletContainerInitializer檢測(cè)到,所以不會(huì)由servlet容器自動(dòng)啟動(dòng)。
* 該類的目的和ServletContainerInitializer一樣,但是 其中的Servlet的生命周期由Spring控制而不是Servlet容器。
*/
@FunctionalInterface
public interface ServletContextInitializer {
void onStartup(ServletContext servletContext) throws ServletException;
}
DelegatingFilterProxy
首先來(lái)看自動(dòng)配置類:org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration
@AutoConfiguration(after = SecurityAutoConfiguration.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(SecurityProperties.class)
@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class })
public class SecurityFilterAutoConfiguration {
// DEFAULT_FILETER_NAME = "springSecurityFilterChain"
private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;
// 必須存在名稱為springSecurityFilterChain的bean
// 名稱為springSecurityFilterChain的bean實(shí)際上類型即是 org.springframework.security.web.FilterChainProxy
@Bean
@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
SecurityProperties securityProperties) {
DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
DEFAULT_FILTER_NAME);
registration.setOrder(securityProperties.getFilter().getOrder());
registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
return registration;
}
...
}
可以看到DelegatingFilterProxyRegistrationBean被注入Bean容器,且名稱為"springSecurityFilterChain"的Bean必須存在,而DelegatingFilterProxyRegistrationBean中#getFilter用來(lái)獲取真正的Security Filter代理類DelegatingFilterProxy,需要注意的是,DelegatingFilterProxy實(shí)現(xiàn)了Filter接口。
先來(lái)看看DelegatingFilterProxyRegistrationBean的類圖結(jié)構(gòu):

DelegatingFilterProxyRegistrationBean負(fù)責(zé)整合Servlet Filter注冊(cè)(主要就是代理類注冊(cè))和Spring生命周期,而真正的代理類DelegatingFilterProxy通過(guò)
DelegatingFilterProxyRegistrationBean#getFilter獲取。這體現(xiàn)了職責(zé)單一的設(shè)計(jì)原則。
public class DelegatingFilterProxyRegistrationBean ... {
...
@Override
public DelegatingFilterProxy getFilter() {
// 創(chuàng)建真正的代理(匿名子類),并具有延遲加載的能力
return new DelegatingFilterProxy(this.targetBeanName, getWebApplicationContext()) {
@Override
protected void initFilterBean() throws ServletException {
// Don't initialize filter bean on init()
}
};
}
...
}
接下來(lái),DelegatingFilterProxyRegistrationBean中的DelegatingFilterProxy需要完成對(duì)多個(gè)SecurityFilterChain的代理。而這個(gè)代理過(guò)程Security又通過(guò)一個(gè)代理類org.springframework.security.web.FilterChainProxy完成 。意思是,DelegatingFilterProxy是整個(gè)Security的代理,而FilterChainProxy是SecurityFilterChain的代理,且DelegatingFilterProxy是通過(guò)FilterChainProxy來(lái)完成代理的(代理一個(gè)代理)。
來(lái)看看DelegatingFilterProxy:
public class DelegatingFilterProxy extends GenericFilterBean {
// 就是 springSecurityFilterChain,代表FilterChainProxy的beanName
@Nullable
private String targetBeanName;
// 代理的FilterChainProxy
@Nullable
private volatile Filter delegate;
...
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// Lazily initialize the delegate if necessary.
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized (this.delegateMonitor) {
delegateToUse = this.delegate;
if (delegateToUse == null) {
...
// 初始化代理類
delegateToUse = initDelegate(wac);
}
this.delegate = delegateToUse;
}
}
// Let the delegate perform the actual doFilter operation.
invokeDelegate(delegateToUse, request, response, filterChain);
}
...
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
String targetBeanName = getTargetBeanName();
// 容器中獲取名稱為springSecurityFilterChain 類型為Filter的bean
// 即 FilterChainProxy
// 所以 注冊(cè) DelegatingFilterProxyRegistrationBean 時(shí)必須有 @ConditionalOnBean(name="springSecurityFilterChain")
Filter delegate = wac.getBean(targetBeanName, Filter.class);
...
return delegate;
}
}
上文說(shuō)到,在注冊(cè)DelegatingFilterProxyRegistrationBean的自動(dòng)配置類中 必須要有springSecurityFilterChain名稱的bean存在,而這個(gè)名稱為springSecurityFilterChain的bean實(shí)際上類型即是 org.springframework.security.web.FilterChainProxy。
整個(gè)流程如下:

有點(diǎn)像 道生一,一生二,二生三,三生萬(wàn)物 的思想,我將它命名為 道德經(jīng)設(shè)計(jì)模式,嘿嘿 。
那么FilterChainProxy又是在哪兒注入的呢?
FilterChainProxy
在配置類org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration中我們可以發(fā)現(xiàn),這里注入了FilterChainProxy:
@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
...
private WebSecurity webSecurity;
// 多個(gè)SecurityFilterChain
private List<SecurityFilterChain> securityFilterChains = Collections.emptyList();
// 多個(gè)WebSecurityCustomizer
private List<WebSecurityCustomizer> webSecurityCustomizers = Collections.emptyList();
...
// 注入一個(gè)Filter,指定名稱為springSecurityFilterChain
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
...
for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
// 為每個(gè)SecurityFilterChain中的每個(gè)Filter添加攔截方法
for (Filter filter : securityFilterChain.getFilters()) {
if (filter instanceof FilterSecurityInterceptor) {
this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter);
break;
}
}
}
// 自定義器對(duì)每個(gè)SecurityFilterChain均生效
for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
customizer.customize(this.webSecurity);
}
// 這里build()方法返回 org.springframework.security.web.FilterChainProxy
return this.webSecurity.build();
}
...
// 自動(dòng)注入, 通常我們需要自定義的就是這個(gè)SecurityFilterChain類型
// 只需要在業(yè)務(wù)配置類中注冊(cè)一個(gè)SecurityFilterChain類型的bean就能被注入到這里
@Autowired(required = false)
void setFilterChains(List<SecurityFilterChain> securityFilterChains) {
this.securityFilterChains = securityFilterChains;
}
// 自動(dòng)注入
@Autowired(required = false)
void setWebSecurityCustomizers(List<WebSecurityCustomizer> webSecurityCustomizers) {
this.webSecurityCustomizers = webSecurityCustomizers;
}
}在業(yè)務(wù)配置類中,我們可以自定義SecurityFilterChain和WebSecurityCustomizer的bean,配置如下:
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable();
// 必須顯式注明,配合CorsConfigurationSource的Bean,不然即使在web里面配置了跨域,security這里依然會(huì)cors error
http.cors();
http.authorizeRequests()
.antMatchers(AUTH_WHITELIST).permitAll()
.anyRequest().authenticated();
http.formLogin().successHandler(loginSuccessHandler);
http.oauth2Login().successHandler(giteeSuccessHandler);
http.exceptionHandling().accessDeniedHandler(restAccessDeniedHandler);
http.addFilterBefore(bearAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().antMatchers("/ignore1", "/ignore2");
}
}
OK,我們?cè)賮?lái)看看 org.springframework.security.web.FilterChainProxy:
public class FilterChainProxy extends GenericFilterBean {
private List<SecurityFilterChain> filterChains;
private HttpFirewall firewall = new StrictHttpFirewall();
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
...
doFilterInternal(request, response, chain);
...
}
private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 轉(zhuǎn)化為org.springframework.security.web.firewall.FirewalledRequest
// reject potentially dangerous requests and/or wrap them to control their behaviour.
FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);
HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);
// #getFilters會(huì)在所有SecurityFilterChain中進(jìn)行匹配
List<Filter> filters = getFilters(firewallRequest);
...
// 轉(zhuǎn)化為 VirtualFilterChain
// VirtualFilterChain是FilterChainProxy內(nèi)部靜態(tài)類
VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
// 開啟 SecurityFilterChain中所有filter過(guò)程
virtualFilterChain.doFilter(firewallRequest, firewallResponse);
}
private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : this.filterChains) {
// 返回第一個(gè)符合規(guī)則的SecurityFilterChain
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
/**
* 執(zhí)行額外的 filters,控制filters執(zhí)行過(guò)程
* Internal {@code FilterChain} implementation that is used to pass a request through
* the additional internal list of filters which match the request.
*/
private static final class VirtualFilterChain implements FilterChain {
...
private final FilterChain originalChain;
private final List<Filter> additionalFilters;
private final FirewalledRequest firewalledRequest;
// 該SecurityFilterChain中所有filter的數(shù)量
private final int size;
// 當(dāng)前filter的位置
private int currentPosition = 0;
...
@Override
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (this.currentPosition == this.size) {
// 執(zhí)行完畢
// Deactivate path stripping as we exit the security filter chain
this.firewalledRequest.reset();
this.originalChain.doFilter(request, response);
return;
}
// 繼續(xù)執(zhí)行filterChain中下一個(gè)filter
this.currentPosition++;
Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
nextFilter.doFilter(request, response, this);
}
...
}
...
}
Filters
按順序排序,Spring Security內(nèi)置了以下Filter:
- ForceEagerSessionCreationFilter
- ChannelProcessingFilter
- WebAsyncManagerIntegrationFilter
- SecurityContextPersistenceFilter
- HeaderWriterFilter
- CorsFilter
- CsrfFilter
- LogoutFilter
- OAuth2AuthorizationRequestRedirectFilter
- Saml2WebSsoAuthenticationRequestFilter
- X509AuthenticationFilter
- AbstractPreAuthenticatedProcessingFilter
- CasAuthenticationFilter
- OAuth2LoginAuthenticationFilter
- Saml2WebSsoAuthenticationFilter
- UsernamePasswordAuthenticationFilter
- DefaultLoginPageGeneratingFilter
- DefaultLogoutPageGeneratingFilter
- ConcurrentSessionFilter
- DigestAuthenticationFilter
- BearerTokenAuthenticationFilter
- BasicAuthenticationFilter
- RequestCacheAwareFilter
- SecurityContextHolderAwareRequestFilter
- JaasApiIntegrationFilter
- RememberMeAuthenticationFilter
- AnonymousAuthenticationFilter
- OAuth2AuthorizationCodeGrantFilter
- SessionManagementFilter
- ExceptionTranslationFilter : allows translation of AccessDeniedException and
- AuthenticationException into HTTP responses
- FilterSecurityInterceptor (新版本由 AuthorizationFilter 取代,該Interceptor即是做鑒權(quán)的)
- SwitchUserFilter
到此這篇關(guān)于Spring Security基本架構(gòu)與初始化操作流程詳解的文章就介紹到這了,更多相關(guān)Spring Security基本架構(gòu)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot集成Sharding Jdbc使用復(fù)合分片的實(shí)踐
數(shù)據(jù)庫(kù)分庫(kù)分表中間件是采用的 apache sharding。本文主要介紹了SpringBoot集成Sharding Jdbc使用復(fù)合分片的實(shí)踐,具有一定的參考價(jià)值,感興趣的可以了解一下2021-09-09
關(guān)于解決雪花算法生成的ID傳輸前端后精度丟失問(wèn)題
這篇文章主要介紹了關(guān)于解決雪花算法生成的ID傳輸前端后精度丟失問(wèn)題,雪花算法生成的ID傳輸?shù)角岸藭r(shí),會(huì)出現(xiàn)后三位精度丟失,本文提供了解決思路,需要的朋友可以參考下2023-03-03
SpringBoot使用RabbitMQ延時(shí)隊(duì)列(小白必備)
這篇文章主要介紹了SpringBoot使用RabbitMQ延時(shí)隊(duì)列(小白必備),詳細(xì)的介紹延遲隊(duì)列的使用場(chǎng)景及其如何使用,需要的小伙伴可以一起來(lái)了解一下2019-12-12
Mybatis-Plus使用saveOrUpdate及問(wèn)題解決方法
本文主要介紹了Mybatis-Plus使用saveOrUpdate及問(wèn)題解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01
Win11系統(tǒng)下載安裝java的詳細(xì)過(guò)程
這篇文章主要介紹了Win11如何下載安裝java,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-05-05
創(chuàng)建一個(gè)Java的不可變對(duì)象
這篇文章主要介紹了創(chuàng)建一個(gè)Java的不可變對(duì)象,一個(gè)類的對(duì)象在通過(guò)構(gòu)造方法創(chuàng)建后如果狀態(tài)不會(huì)再被改變,那么它就是一個(gè)不可變(immutable)類。它的所有成員變量的賦值僅在構(gòu)造方法中完成,不會(huì)提供任何 setter 方法供外部類去修改,需要的朋友可以參考下2021-11-11

