您好,登錄后才能下訂單哦!
這篇文章主要介紹“Spring Security基本架構與初始化操作流程是什么”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“Spring Security基本架構與初始化操作流程是什么”文章能幫助大家解決問題。
Spring Security 是基于web的安全組件,所以一些相關類會分散在 spring-security包和web包中。Spring Security通過自定義Servlet的Filter的方式實現,具體架構可參考官網Spring Security: Architecture
這里使用Spring Boot 2.7.4版本,對應Spring Security 5.7.3版本
首先左側是Servlet中的Filter組成的FilterChain,Spring Security通過注冊一個DelegatingFilterProxy的Filter,然后在該Proxy中內置多條Spring Security組織的Security Filter Chain(chain中套娃一個chain),一個Security Filter Chain又有多個Filter,通過不同的規則將Request匹配到第一個滿足條件的Security Filter Chain。
既然Spring Security涉及到Filter,而Filter是Servlet中的組件,這里就存在一個將Spring Security的頂級Filter注冊到Servlet Context的過程。
首先關注javax.servlet.ServletContainerInitializer
,該類是tomcat-embed-core包中的類:
// 通過SPI方式導入實現類: // 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注解標注類型的實現類 **/ void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException; }
該接口實現類由SPI方式導入,我們來到spring-web
包中:
可以看到spring對 該接口的實現類為: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); // 執行 for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); } } }
SpringServletContainerInitializer
中調用了一系列org.springframework.web.WebApplicationInitializer#onStartup
可以看到WebApplicationInitializer
有一系列實現類:
其中就有Security相關的。到此,以上均為 Spring Web中的內容,Spring Security就是基于以上擴展而來。
接上文,來看看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); } ... }
但是,經過調試發現,Spring Security的Filter注冊過程并不是上面的步驟。
重要:
Spring Security 注冊Filter 不是通過上文的 javax.servlet.ServletContainerInitializer
和org.springframework.web.WebApplicationInitializer#onStartup
而是org.springframework.boot.web.servlet.ServletContextInitializer
,來看看ServletContextInitializer
的說明:
/** * 不同于WebApplicationInitializer,實現該接口的類(且沒有實現WebApplicationInitializer) * 不會被SpringServletContainerInitializer檢測到,所以不會由servlet容器自動啟動。 * 該類的目的和ServletContainerInitializer一樣,但是 其中的Servlet的生命周期由Spring控制而不是Servlet容器。 */ @FunctionalInterface public interface ServletContextInitializer { void onStartup(ServletContext servletContext) throws ServletException; }
首先來看自動配置類: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實際上類型即是 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
用來獲取真正的Security Filter代理類DelegatingFilterProxy
,需要注意的是,DelegatingFilterProxy
實現了Filter接口。
先來看看DelegatingFilterProxyRegistrationBean
的類圖結構:
DelegatingFilterProxyRegistrationBean
負責整合Servlet Filter注冊(主要就是代理類注冊)和Spring生命周期,而真正的代理類DelegatingFilterProxy
通過
DelegatingFilterProxyRegistrationBean#getFilter
獲取。這體現了職責單一的設計原則。
public class DelegatingFilterProxyRegistrationBean ... { ... @Override public DelegatingFilterProxy getFilter() { // 創建真正的代理(匿名子類),并具有延遲加載的能力 return new DelegatingFilterProxy(this.targetBeanName, getWebApplicationContext()) { @Override protected void initFilterBean() throws ServletException { // Don't initialize filter bean on init() } }; } ... }
接下來,DelegatingFilterProxyRegistrationBean
中的DelegatingFilterProxy
需要完成對多個SecurityFilterChain
的代理。而這個代理過程Security又通過一個代理類org.springframework.security.web.FilterChainProxy
完成 。意思是,DelegatingFilterProxy
是整個Security的代理,而FilterChainProxy
是SecurityFilterChain的代理,且DelegatingFilterProxy
是通過FilterChainProxy
來完成代理的(代理一個代理)。
來看看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 // 所以 注冊 DelegatingFilterProxyRegistrationBean 時必須有 @ConditionalOnBean(name="springSecurityFilterChain") Filter delegate = wac.getBean(targetBeanName, Filter.class); ... return delegate; } }
上文說到,在注冊DelegatingFilterProxyRegistrationBean
的自動配置類中 必須要有springSecurityFilterChain
名稱的bean存在,而這個名稱為springSecurityFilterChain
的bean實際上類型即是 org.springframework.security.web.FilterChainProxy
。
整個流程如下:
有點像 道生一,一生二,二生三,三生萬物 的思想,我將它命名為 道德經設計模式,嘿嘿 。
那么FilterChainProxy
又是在哪兒注入的呢?
在配置類org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration
中我們可以發現,這里注入了FilterChainProxy
:
@Configuration(proxyBeanMethods = false) public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware { ... private WebSecurity webSecurity; // 多個SecurityFilterChain private List<SecurityFilterChain> securityFilterChains = Collections.emptyList(); // 多個WebSecurityCustomizer private List<WebSecurityCustomizer> webSecurityCustomizers = Collections.emptyList(); ... // 注入一個Filter,指定名稱為springSecurityFilterChain @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME) public Filter springSecurityFilterChain() throws Exception { ... for (SecurityFilterChain securityFilterChain : this.securityFilterChains) { this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain); // 為每個SecurityFilterChain中的每個Filter添加攔截方法 for (Filter filter : securityFilterChain.getFilters()) { if (filter instanceof FilterSecurityInterceptor) { this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter); break; } } } // 自定義器對每個SecurityFilterChain均生效 for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) { customizer.customize(this.webSecurity); } // 這里build()方法返回 org.springframework.security.web.FilterChainProxy return this.webSecurity.build(); } ... // 自動注入, 通常我們需要自定義的就是這個SecurityFilterChain類型 // 只需要在業務配置類中注冊一個SecurityFilterChain類型的bean就能被注入到這里 @Autowired(required = false) void setFilterChains(List<SecurityFilterChain> securityFilterChains) { this.securityFilterChains = securityFilterChains; } // 自動注入 @Autowired(required = false) void setWebSecurityCustomizers(List<WebSecurityCustomizer> webSecurityCustomizers) { this.webSecurityCustomizers = webSecurityCustomizers; } }
在業務配置類中,我們可以自定義SecurityFilterChain
和WebSecurityCustomizer
的bean,配置如下:
@Configuration public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf().disable(); // 必須顯式注明,配合CorsConfigurationSource的Bean,不然即使在web里面配置了跨域,security這里依然會cors error http.cors(); http.authorizeRequests() .antMatchers(AUTH_WHITELIST).permitAll() .anyRequest().authenticated(); http.formLogin().successHandler(loginSuccessHandler); http.oauth3Login().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,我們再來看看 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 { // 轉化為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會在所有SecurityFilterChain中進行匹配 List<Filter> filters = getFilters(firewallRequest); ... // 轉化為 VirtualFilterChain // VirtualFilterChain是FilterChainProxy內部靜態類 VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters); // 開啟 SecurityFilterChain中所有filter過程 virtualFilterChain.doFilter(firewallRequest, firewallResponse); } private List<Filter> getFilters(HttpServletRequest request) { for (SecurityFilterChain chain : this.filterChains) { // 返回第一個符合規則的SecurityFilterChain if (chain.matches(request)) { return chain.getFilters(); } } return null; } /** * 執行額外的 filters,控制filters執行過程 * 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的數量 private final int size; // 當前filter的位置 private int currentPosition = 0; ... @Override public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { if (this.currentPosition == this.size) { // 執行完畢 // Deactivate path stripping as we exit the security filter chain this.firewalledRequest.reset(); this.originalChain.doFilter(request, response); return; } // 繼續執行filterChain中下一個filter this.currentPosition++; Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1); nextFilter.doFilter(request, response, this); } ... } ... }
按順序排序,Spring Security內置了以下Filter:
ForceEagerSessionCreationFilter
ChannelProcessingFilter
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CorsFilter
CsrfFilter
LogoutFilter
OAuth3AuthorizationRequestRedirectFilter
Saml2WebSsoAuthenticationRequestFilter
X509AuthenticationFilter
AbstractPreAuthenticatedProcessingFilter
CasAuthenticationFilter
OAuth3LoginAuthenticationFilter
Saml2WebSsoAuthenticationFilter
UsernamePasswordAuthenticationFilter
DefaultLoginPageGeneratingFilter
DefaultLogoutPageGeneratingFilter
ConcurrentSessionFilter
DigestAuthenticationFilter
BearerTokenAuthenticationFilter
BasicAuthenticationFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
JaasApiIntegrationFilter
RememberMeAuthenticationFilter
AnonymousAuthenticationFilter
OAuth3AuthorizationCodeGrantFilter
SessionManagementFilter
ExceptionTranslationFilter : allows translation of AccessDeniedException and
AuthenticationException into HTTP responses
FilterSecurityInterceptor (新版本由 AuthorizationFilter 取代,該Interceptor即是做鑒權的)
SwitchUserFilter
關于“Spring Security基本架構與初始化操作流程是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。