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。
Web源码
既然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;
- }
DelegatingFilterProxy
首先来看自动配置类: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
又是在哪儿注入的呢?
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.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,我们再来看看 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);
- }
- …
- }
- …
- }
Filters
按顺序排序,Spring Security内置了以下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即是做鉴权的)
- SwitchUserFilter
到此这篇关于Spring Security基本架构与初始化操作流程详解的文章就介绍到这了,更多相关Spring Security基本架构内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!
发表评论