Spring Security基本架构与初始化操作流程详解

 更新时间:2023年03月28日 10:42:01   作者:T.Y.Bao  
这篇文章主要介绍了Spring Security基本架构与初始化操作流程,Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架

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.ServletContainerInitializerorg.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;
	}
}

在业务配置类中,我们可以自定义SecurityFilterChainWebSecurityCustomizer的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基本架构内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot中VO/DTO/PO的具体使用

    SpringBoot中VO/DTO/PO的具体使用

    VO/DTO/PO等实体类中字段常常会存在多数相同,根据业务需求少数不同,本文主要介绍了SpringBoot中VO/DTO/PO的具体使用,感兴趣的可以了解一下
    2024-03-03
  • Java中的lambda和stream实现排序

    Java中的lambda和stream实现排序

    这篇文章主要介绍了Java中的lambda和stream实现排序,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-09-09
  • Java集合之CopyOnWriteArrayList详解

    Java集合之CopyOnWriteArrayList详解

    这篇文章主要介绍了Java集合之CopyOnWriteArrayList详解,CopyOnWriteArrayList是ArrayList的线程安全版本,内部也是通过数组实现,每次对数组的修改都完全拷贝一份新的数组来修改,修改完了再替换掉老数组,这样保证了只阻塞写操作,需要的朋友可以参考下
    2023-12-12
  • Java中Executor接口用法总结

    Java中Executor接口用法总结

    这篇文章主要介绍了Java中Executor接口用法,较为详细的总结了Executor接口的定义、创建及用法,需要的朋友可以参考下
    2015-06-06
  • SpringBoot集成Devtools实现热更新

    SpringBoot集成Devtools实现热更新

    DevTools是开发者工具集,主要用于简化开发过程中的热部署问题,热部署是指在开发过程中,当代码发生变化时,无需手动重启应用,系统能够自动检测并重新加载修改后的代码,本文给大家介绍了SpringBoot集成Devtools实现热更新,需要的朋友可以参考下
    2024-08-08
  • SpringBoot2.1.3修改tomcat参数支持请求特殊符号问题

    SpringBoot2.1.3修改tomcat参数支持请求特殊符号问题

    最近遇到一个问题,比如GET请求中,key,value中带有特殊符号,请求会报错。接下来通过本文给大家分享解决SpringBoot2.1.3修改tomcat参数支持请求特殊符号 ,需要的朋友可以参考下
    2019-05-05
  • Java设计模式之状态模式(State模式)介绍

    Java设计模式之状态模式(State模式)介绍

    这篇文章主要介绍了Java设计模式之状态模式(State模式)介绍,本文讲解了何时使用状态模式、如何使用状态模式等内容,需要的朋友可以参考下
    2015-03-03
  • Java实现FIFO、LRU、LFU、OPT页面置换算法

    Java实现FIFO、LRU、LFU、OPT页面置换算法

    本文主要介绍了Java实现FIFO、LRU、LFU、OPT页面置换算法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02
  • 详解Android中的Toast源码

    详解Android中的Toast源码

    这篇文章主要介绍了详解Android中的Toast源码,Toast使用Java语言实现,需要的朋友可以参考下
    2015-07-07
  • Java实现统计文件夹下所有文件的字数

    Java实现统计文件夹下所有文件的字数

    这篇文章主要为大家详细介绍了如何使用Java实现统计文件夹下所有文件的字数,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-03-03

最新评论