springSecurityFilterChain源码分析

本文深入剖析SpringSecurity中过滤器的创建与注册流程,详细解释了springSecurityFilterChain的生成时机及如何加入过滤器链,揭示了框架底层的运作机制。

主要参考点我跳转,写得模糊可移步,原文写得很好。

只要你是一个javaweb项目,权限框架都不可避免的要用到过滤器,shiro和spring security也不避免,所以首要任务就是要知道spring security他的主要过滤器是什么,什么时候创建,什么时候加入过滤器链。

创建

从官方文档我们可知spring security利用名为springSecurityFilterChain的过滤器实现过滤urls,并且实现AbstractSecurityWebApplicationInitializer接口,用以注册过滤器,而实现的方式取决于是否有spring环境,此篇文章只分析具有spring环境的实现,查看我们使用的enablewebsecuroty注解

@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,
		SpringWebMvcImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {

	/**
	 * Controls debugging support for Spring Security. Default is false.
	 * @return if true, enables debug support with Spring Security
	 */
	boolean debug() default false;
}

引入了两个类,其中springwebmvcimportselector是用以判断是否支持springmvc,并且为springmvc添加security支持,WebSecurityConfiguration看其类注释是利用websecurity创建的filterchainproxy,看代码

/**
	 * Creates the Spring Security Filter Chain
	 * @return
	 * @throws Exception
	 */
	@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
	public Filter springSecurityFilterChain() throws Exception {
		boolean hasConfigurers = webSecurityConfigurers != null
				&& !webSecurityConfigurers.isEmpty();
		if (!hasConfigurers) {
			WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
					.postProcess(new WebSecurityConfigurerAdapter() {
					});
			webSecurity.apply(adapter);
		}
		return webSecurity.build();
	}

创建的对象名就是我们需要的springSecurityFilterChain,直接进入build方法,

	public final O build() throws Exception {
		if (this.building.compareAndSet(false, true)) {
			this.object = doBuild();
			return this.object;
		}
		throw new AlreadyBuiltException("This object has already been built");
	}

再次进入dobuild方法

@Override
	protected final O doBuild() throws Exception {
		synchronized (configurers) {
			buildState = BuildState.INITIALIZING;

			beforeInit();
			init();

			buildState = BuildState.CONFIGURING;

			beforeConfigure();
			configure();

			buildState = BuildState.BUILDING;

			O result = performBuild();

			buildState = BuildState.BUILT;

			return result;
		}
	}

再次进入performbuild方法,我们选择websecurity的实现

@Override
	protected Filter performBuild() throws Exception {
		Assert.state(
				!securityFilterChainBuilders.isEmpty(),
				"At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. More advanced users can invoke "
						+ WebSecurity.class.getSimpleName()
						+ ".addSecurityFilterChainBuilder directly");
		int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
		List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
				chainSize);
		for (RequestMatcher ignoredRequest : ignoredRequests) {
			securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
		}
		for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
			securityFilterChains.add(securityFilterChainBuilder.build());
		}
		FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
		if (httpFirewall != null) {
			filterChainProxy.setFirewall(httpFirewall);
		}
		filterChainProxy.afterPropertiesSet();

		Filter result = filterChainProxy;
		if (debugEnabled) {
			logger.warn("\n\n"
					+ "********************************************************************\n"
					+ "**********        Security debugging is enabled.       *************\n"
					+ "**********    This may include sensitive information.  *************\n"
					+ "**********      Do not use in a production system!     *************\n"
					+ "********************************************************************\n\n");
			result = new DebugFilter(filterChainProxy);
		}
		postBuildAction.run();
		return result;
	}

先看返回的类型是一个filter且类型为filterchainproxy,至此springSecurityFilterChain创建

注册

注册的分析比较麻烦,我们并不容易知道他是如何注册的,先是看一遍上面的链接,才发现官方文档都写好了

下一步是注册springSecurityFilterChain战争。这可以在Java配置中使用Spring的WebApplicationInitializer支持在Servlet 3.0+环境中完成。不出所料,Spring Security提供了一个基类AbstractSecurityWebApplicationInitializer,可以确保springSecurityFilterChain为您注册。我们使用的方式AbstractSecurityWebApplicationInitializer取决于我们是否已经使用Spring,或者Spring Security是否是我们应用程序中唯一的Spring组件。

第6.1.2节“没有现有Spring的AbstractSecurityWebApplicationInitializer” - 如果您还没有使用Spring,请使用这些说明
第6.1.3节“使用Spring MVC的AbstractSecurityWebApplicationInitializer” - 如果您已经使用Spring,请使用这些说明

话不多说直接进入AbstractSecurityWebApplicationInitializer,类注释说,为springSecurityFilterChain创建一个代理对象DelegatingFilterProxy,查找DelegatingFilterProxy类发现在方法insertSpringSecurityFilterChain创建,而insertSpringSecurityFilterChain又在onStartup调用,我们现在知道知道onStartup什么时候调用就可以知道什么创建了DelegatingFilterProxy,看其注释

	 * (non-Javadoc)
	 *
	 * @see org.springframework.web.WebApplicationInitializer#onStartup(javax.servlet.
	 * ServletContext)

此方法是实现了WebApplicationInitializer,再次进入WebApplicationInitializer,类注释的大概意思是这个类的配置可以替换以前在web.xml中的配置,比如注册servlet,且让我们参阅SpringServletContainerInitializer,而且此类只有一个方法,其方法的大概作用是可以为ServletContext配置servlet,filter,listener,那到底是怎么替换web.xml中的内容的,我们只能进入SpringServletContainerInitializer一探究竟,SpringServletContainerInitializer的类注释大概意思是ServletContainerInitializer提供了不依靠web.xml启动方式,只要容器(tomcat之类的)满足servlet规范即可,但是需要l路径需要满足一定规则才会被查找到并加载,源码如下

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

	/**
	 * Delegate the {@code ServletContext} to any {@link WebApplicationInitializer}
	 * implementations present on the application classpath.
	 * <p>Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)},
	 * Servlet 3.0+ containers will automatically scan the classpath for implementations
	 * of Spring's {@code WebApplicationInitializer} interface and provide the set of all
	 * such types to the {@code webAppInitializerClasses} parameter of this method.
	 * <p>If no {@code WebApplicationInitializer} implementations are found on the classpath,
	 * this method is effectively a no-op. An INFO-level log message will be issued notifying
	 * the user that the {@code ServletContainerInitializer} has indeed been invoked but that
	 * no {@code WebApplicationInitializer} implementations were found.
	 * <p>Assuming that one or more {@code WebApplicationInitializer} types are detected,
	 * they will be instantiated (and <em>sorted</em> if the @{@link
	 * org.springframework.core.annotation.Order @Order} annotation is present or
	 * the {@link org.springframework.core.Ordered Ordered} interface has been
	 * implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)}
	 * method will be invoked on each instance, delegating the {@code ServletContext} such
	 * that each instance may register and configure servlets such as Spring's
	 * {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener},
	 * or any other Servlet API componentry such as filters.
	 * @param webAppInitializerClasses all implementations of
	 * {@link WebApplicationInitializer} found on the application classpath
	 * @param servletContext the servlet context to be initialized
	 * @see WebApplicationInitializer#onStartup(ServletContext)
	 * @see AnnotationAwareOrderComparator
	 */
	@Override
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {

		List<WebApplicationInitializer> initializers = new LinkedList<>();

		if (webAppInitializerClasses != null) {
			for (Class<?> waiClass : webAppInitializerClasses) {
				// Be defensive: Some servlet containers provide us with invalid classes,
				// no matter what @HandlesTypes says...
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
						initializers.add((WebApplicationInitializer)
								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
					}
					catch (Throwable ex) {
						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
					}
				}
			}
		}

		if (initializers.isEmpty()) {
			servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
			return;
		}

		servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
		AnnotationAwareOrderComparator.sort(initializers);
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}

}

至此最后一行可知容器在启动时加载了SpringServletContainerInitializer,并调用了onstartup方法,其方法循环调用了WebApplicationInitializer的onstartup,而我们的AbstractSecurityWebApplicationInitializer也正是继承了WebApplicationInitializer得以调用onstartup方法,将springSecurityFilterChain过滤器加入了过滤器链。

结语

至此springsecurity过滤器的创建和注册流程我们已经弄清楚了,由于能力有限并不能分析出源码所用的设计模式,只能推测出他们的一般流程,通过此篇文章我深刻理解到web框架封装的再好也是servlet,filter之类的东西,正如学习jvm不看几遍jvm规范,学习web框架不看servlet规范,只能是雾里看花。

参考链接

https://blog.csdn.net/lqzkcx3/article/details/78507169

https://docs.spring.io/spring-security/site/docs/5.1.0.BUILD-SNAPSHOT/reference/htmlsingle/#tutorial-sample

### Spring Security 源码详细解析 #### 一、Spring Security 的核心架构 Spring Security 是一个强大的安全框架,其设计目标是提供全面的安全解决方案。它的主要功能包括身份验证(Authentication)、授权(Authorization)以及保护应用程序免受各种攻击模式的影响。 Spring Security 并不依赖于 Spring MVC 或其他特定的 Web 技术栈,而是直接与 Servlet 容器集成[^1]。这种集成的核心机制是一个名为 `DelegatingFilterProxy` 的过滤器,该过滤器负责将所有的 HTTP 请求传递给 Spring Security 中定义的一系列过滤器链(Filter Chain)。这些过滤器共同协作完成诸如登录、注销、权限校验等功能。 --- #### 二、Spring Security 过滤器链的工作原理 Spring Security 使用了一种基于过滤器的设计模式来处理安全性需求。整个流程由多个过滤器组成,每个过滤器专注于解决某一方面的安全问题。以下是典型的过滤器及其职责: 1. **ChannelProcessingFilter**: 负责强制执行通道约束(例如 HTTPS),确保某些 URL 只能通过指定协议访问。 2. **SecurityContextPersistenceFilter**: 初始化或恢复当前线程中的 `SecurityContextHolder` 对象,用于存储用户的认证信息。 3. **LogoutFilter**: 处理用户登出逻辑。 4. **UsernamePasswordAuthenticationFilter**: 实现表单登录的身份验证。 5. **BasicAuthenticationFilter**: 提供 Basic Authentication 支持。 6. **ExceptionTranslationFilter**: 将异常转换为相应的 HTTP 响应状态码。 7. **FilterSecurityInterceptor**: 执行方法级和 URL 级别的访问控制决策。 8. **SwitchUserFilter**: 允许管理员切换到另一个用户账户下操作。 以上提到的每一个过滤器都按照既定顺序被加载并串联起来形成完整的 Filter Chain[^1]。 --- #### 三、如何初始化过滤器链? 在 Spring Boot 应用程序中,默认情况下会自动生成一条基础版的过滤器链条——即所谓的 `springSecurityFilterChain` Bean。这个 Bean 是通过 `SpringBootWebSecurityConfiguration` 类配置出来的[^2]。下面展示了一个简化版本的例子说明这一过程是如何发生的: ```java @Configuration(proxyBeanMethods = false) @ConditionalOnDefaultWebSecurity @ConditionalOnWebApplication(type = Type.SERVLET) public class SpringBootWebSecurityConfiguration { @Bean @Order(SecurityProperties.BASIC_AUTH_ORDER) public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() // 配置所有请求均需经过身份验证 .and() .formLogin() // 启用 Form 登录方式 .and() .httpBasic(); // 启用 Http Basic 认证 (较少使用) return http.build(); } } ``` 上述代码片段展示了 Spring Boot 如何利用 Java Config 来构建默认的安全策略:任何未匹配具体路径规则的请求都将受到严格管控,只有已成功登陆过的客户端才允许继续浏览资源[^2]。 --- #### 四、定制化安全设置 尽管存在开箱即用的基础防护措施,但在实际项目开发过程中往往还需要针对业务场景做出进一步调整优化。这通常涉及以下几个方面: - 添加额外的拦截条件; - 修改现有行为以适配特殊需求; - 替换内置组件实例以便引入第三方扩展模块等。 要实现这一点,则需要继承抽象基类 AbstractSecurityConfigurerAdapter 或者直接重写 configure 方法来自定义全局属性设定项。比如我们可以这样覆盖掉原始的 UserDetailsService 接口实现从而支持更复杂的密码加密算法或者外部 OAuth2/OIDC 协议对接服务等等。 --- #### 五、总结 通过对 Spring Security 源码的学习可以看出,它不仅提供了丰富的 API 和工具集帮助开发者快速搭建起一套完善的企业级安全保障体系;而且由于采用了高度灵活可插拔式的结构设计理念,使得即使面对复杂多变的实际应用场景也能够轻松应对自如。无论是初学者还是资深工程师都可以从中受益匪浅! ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值