Spring Security Filter架构总结

Spring Security 是一个提供身份验证授权防御常见攻击的框架。它对命令式响应式应用程序的安全提供了一流的支持,是保护基于 Spring 的应用程序的事实标准。

这里先主要讲Servlet应用程序的架构,参考官方文档架构 :: Spring Security --- Architecture :: Spring Security和AI。

Servlet应用程序

Servlet Filter(Servlet 规范)

这是 Java Web 最底层的拦截机制,由 Servlet 容器(Tomcat、Jetty等)直接管理,Spring 本身没有发明新的 Filter 接口

  • 来源javax.servlet.Filter(Jakarta EE 之后包名变为 jakarta.servlet
  • 作用:可以在请求到达 Servlet 之前,或响应返回客户端之前,对请求/响应进行包装、修改或拦截。
  • 生命周期:由容器管理,通过 web.xml@WebFilter 声明。

下图展示了单个 HTTP 请求的处理程序的典型分层结构。

700

Spring 如何把普通 Filter 纳入自己管理?

DelegatingFilterProxy

Spring 提供了一个名为 DelegatingFilterProxy Filter 实现,它允许在 Servlet 容器的生命周期和 Spring 的 ApplicationContext 之间建立桥接。Servlet 容器允许使用其自身的标准注册 Filter 实例,但它并不识别 Spring 定义的 Bean。您可以通过标准的 Servlet 容器机制注册 DelegatingFilterProxy ,但将所有工作委托给实现了 Filter 接口的 Spring Bean。

DelegatingFilterProxyApplicationContext 中查找 Bean 过滤器

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
	Filter delegate = getFilterBean(someBeanName);
	delegate.doFilter(request, response);
}

FilterChainProxy

Spring Security 的 Servlet 支持包含在 FilterChainProxy 中。 FilterChainProxy 是 Spring Security 提供的一种特殊 Filter ,它允许通过 SecurityFilterChain 将请求委托给多个 Filter 实例。由于 FilterChainProxy 是一个 Bean,因此它通常会被包装在 DelegatingFilterProxy 中(即上述代码的delegate)。

SecurityFilterChain

SecurityFilterChainFilterChainProxy 使用,以确定应该为当前请求调用哪些 Spring Security Filter 实例。

相关源码:

	// 获取匹配的filter
	private List<Filter> getFilters(HttpServletRequest request) {
		int count = 0;
		for (SecurityFilterChain chain : this.filterChains) {
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.format("Trying to match request against %s (%d/%d)", chain, ++count,
						this.filterChains.size()));
			}
			if (chain.matches(request)) {
				return chain.getFilters();
			}
		}
		return null;
	}
	
	// VirtualFilterChain
	
		private VirtualFilterChain(FilterChain chain, List<Filter> additionalFilters) {
			this.originalChain = chain;
			this.additionalFilters = additionalFilters;
			this.size = additionalFilters.size();
		}
		
		// 先按照顺序执行匹配的filter,最后再调用原始的filter
		@Override
		public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
			if (this.currentPosition == this.size) {
				this.originalChain.doFilter(request, response);
				return;
			}
			this.currentPosition++;
			Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
			if (logger.isTraceEnabled()) {
				String name = nextFilter.getClass().getSimpleName();
				logger.trace(LogMessage.format("Invoking %s (%d/%d)", name, this.currentPosition, this.size));
			}
			nextFilter.doFilter(request, response, this);
		}

如何添加自定义的Filter

安全过滤器通过 SecurityFilterChain API 插入到 FilterChainProxy 中。 这些过滤器可用于多种不同用途,例如 Spring Security 提供漏洞利用防护 身份验证 授权等功能。过滤器会按照特定的顺序执行,以确保在正确的时间被调用。例如,执行身份验证的 Filter 应该在执行授权的 Filter 之前调用。通常情况下,无需了解 Spring Security Filter 的执行顺序。但是,在某些情况下,了解执行顺序是有益的。如果您想了解具体顺序,可以查看 FilterOrderRegistration 代码

这些安全过滤器通常使用 HttpSecurity 实例声明。这也是代码中常见到的配置方式。

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(Customizer.withDefaults())
            .httpBasic(Customizer.withDefaults())
            .formLogin(Customizer.withDefaults())
            .authorizeHttpRequests((authorize) -> authorize
                .anyRequest().authenticated()
            );

        return http.build();
    }

}

上述配置将产生以下 Filter 排序:

Filter Added by
CsrfFilter HttpSecurity#csrf
BasicAuthenticationFilter HttpSecurity#httpBasic
UsernamePasswordAuthenticationFilter HttpSecurity#formLogin
AuthorizationFilter HttpSecurity#authorizeHttpRequests

添加的位置

HttpSecurity 提供了三种添加过滤器的方法:

  • #addFilterBefore(Filter, Class<?>) 会在另一个过滤器之前添加你的过滤器。
  • #addFilterAfter(Filter, Class<?>) 会在另一个过滤器之后添加你的过滤器。
  • #addFilterAt(Filter, Class<?>) 会将另一个过滤器替换为您的过滤器。

比如我想再用户名密码验证之前添加JWT的过滤器。

.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)

默认顺序还是参考这个类FilterOrderRegistration

	private static final int INITIAL_ORDER = 100;

	private static final int ORDER_STEP = 100;

	private final Map<String, Integer> filterToOrder = new HashMap<>();

	FilterOrderRegistration() {
		Step order = new Step(INITIAL_ORDER, ORDER_STEP);
		put(DisableEncodeUrlFilter.class, order.next());
		put(ForceEagerSessionCreationFilter.class, order.next());
		put(ChannelProcessingFilter.class, order.next());
		order.next(); // gh-8105
		put(WebAsyncManagerIntegrationFilter.class, order.next());
		put(SecurityContextHolderFilter.class, order.next());
		put(SecurityContextPersistenceFilter.class, order.next());
		put(HeaderWriterFilter.class, order.next());
		put(CorsFilter.class, order.next());
		put(CsrfFilter.class, order.next());
		put(LogoutFilter.class, order.next());
		this.filterToOrder.put(
				"org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter",
				order.next());
		this.filterToOrder.put(
				"org.springframework.security.saml2.provider.service.web.Saml2WebSsoAuthenticationRequestFilter",
				order.next());
		put(X509AuthenticationFilter.class, order.next());
		put(AbstractPreAuthenticatedProcessingFilter.class, order.next());
		this.filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter", order.next());
		this.filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter",
				order.next());
		this.filterToOrder.put(
				"org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter",
				order.next());
		put(UsernamePasswordAuthenticationFilter.class, order.next());
		order.next(); // gh-8105
		put(DefaultLoginPageGeneratingFilter.class, order.next());
		put(DefaultLogoutPageGeneratingFilter.class, order.next());
		put(ConcurrentSessionFilter.class, order.next());
		put(DigestAuthenticationFilter.class, order.next());
		this.filterToOrder.put(
				"org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter",
				order.next());
		put(BasicAuthenticationFilter.class, order.next());
		put(RequestCacheAwareFilter.class, order.next());
		put(SecurityContextHolderAwareRequestFilter.class, order.next());
		put(JaasApiIntegrationFilter.class, order.next());
		put(RememberMeAuthenticationFilter.class, order.next());
		put(AnonymousAuthenticationFilter.class, order.next());
		this.filterToOrder.put("org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter",
				order.next());
		put(SessionManagementFilter.class, order.next());
		put(ExceptionTranslationFilter.class, order.next());
		put(FilterSecurityInterceptor.class, order.next());
		put(AuthorizationFilter.class, order.next());
		put(SwitchUserFilter.class, order.next());
	}

当问你如何自定义Filter顺序时,要注意什么?

注意区分问的是Spring Security内部管理的过滤器还是外部的的Servlet Filter,虽然他们都是运行在Servlet Filter层,但是生命周期是由Spring Security 控制。

Spring Security内部管理的过滤器

同上,通过HttpSecurity来设置它的位置

外部Servlet Filter,由Servlet容器管理

按照Order值来确定的:

  • @Order 注解 标注在 Filter 类上(需实现 Filter 且被 Spring 管理)
  • FilterRegistrationBean.setOrder(int) 显式设置
  • 实现 org.springframework.core.Ordered 接口

如springSecurityFilterChain默认值为SecurityProperties.DEFAULT_FILTER_ORDER = -100

如果你想在 Spring Security 之前插入一个自定义 Servlet Filter(比如记录最原始的请求),就把它的 order 设得比 -100 更小,比如 -101

@Bean
public FilterRegistrationBean<MyFilter> myFilter(){
    FilterRegistrationBean<MyFilter> reg = new FilterRegistrationBean<>(new MyFilter());
    reg.setOrder(-101);
    return reg;
}

处理安全异常SecurityException

ExceptionTranslationFilter 允许将 AccessDeniedExceptionAuthenticationException 转换为 HTTP 响应。

ExceptionTranslationFilter 作为安全过滤器之一插入到 FilterChainProxy 中。

其实就是在正常filter上加了一个异常捕获,处理SecurityException。


原文链接:https://blog.dengwei.site/article/spring-security-filter-architecture-summary

posted @ 2026-06-06 13:26  衣来伸手饭来张口  阅读(22)  评论(0)    收藏  举报