当前位置: 首页 > article >正文

SpringSecurity过滤器链:核心过滤器的执行顺序与职责

在这里插入图片描述

文章目录

    • 引言
    • 一、过滤器链架构概述
    • 二、核心过滤器执行顺序
    • 三、SecurityContextPersistenceFilter与SecurityContextHolder
    • 四、CsrfFilter与跨站请求伪造防护
    • 五、UsernamePasswordAuthenticationFilter与表单登录
    • 六、FilterSecurityInterceptor与授权决策
    • 七、ExceptionTranslationFilter与安全异常处理
    • 总结

引言

Spring Security作为Java生态系统中最流行的安全框架,通过精心设计的过滤器链(Filter Chain)机制实现了Web应用的安全防护。这些过滤器按特定顺序排列,形成一个责任链,每个过滤器负责特定的安全功能。理解这些过滤器的执行顺序和各自职责,对于正确配置Spring Security、解决安全问题和实现自定义安全需求至关重要。本文将深入剖析Spring Security的核心过滤器链结构,详细介绍每个关键过滤器的功能及其在整体安全架构中的作用,帮助开发者全面把握Spring Security的工作原理。

一、过滤器链架构概述

Spring Security的过滤器链是基于Servlet规范的Filter接口实现的,通过DelegatingFilterProxy与Spring的应用上下文集成。在Spring Security 5.4版本之前,过滤器链由FilterChainProxy管理,它包含一个或多个SecurityFilterChain,每个SecurityFilterChain包含多个Spring Security过滤器。从5.4版本开始,Spring Security更加强调通过HttpSecurity构建器来配置安全过滤器链,但底层原理保持不变。这种设计使得过滤器的组织更加模块化,便于扩展和定制。

// 基本的Spring Security配置
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(authorize -> authorize
                .requestMatchers("/public/**").permitAll()
                .requestMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .formLogin(form -> form
                .loginPage("/login")
                .permitAll()
            );
        return http.build();
    }
    
    // 底层转换为过滤器链
    // HttpSecurity -> SecurityFilterChain -> 多个SecurityFilter
}

二、核心过滤器执行顺序

Spring Security的过滤器链按照严格的顺序执行,确保安全逻辑的正确应用。以下是核心过滤器的标准执行顺序,每个过滤器都有特定的位置编号(Order)以保证它们的相对顺序。了解这个顺序对于调试安全问题和插入自定义过滤器至关重要。例如,如果需要在认证前执行某些逻辑,可以将自定义过滤器插入到UsernamePasswordAuthenticationFilter之前。

// Spring Security过滤器顺序常量定义(部分)
public final class FilterOrderRegistration {
    
    public static final int CHANNEL_FILTER = 100;               // 通道安全过滤器
    public static final int SECURITY_CONTEXT_FILTER = 200;      // 安全上下文过滤器
    public static final int CONCURRENT_SESSION_FILTER = 300;    // 会话并发控制过滤器
    public static final int HEADERS_FILTER = 400;               // HTTP头安全过滤器
    public static final int CSRF_FILTER = 500;                  // CSRF防护过滤器
    public static final int LOGOUT_FILTER = 600;                // 注销过滤器
    public static final int FORM_LOGIN_FILTER = 1100;           // 表单登录过滤器
    public static final int BASIC_AUTH_FILTER = 1200;           // HTTP基本认证过滤器
    public static final int AUTHORIZATION_FILTER = 3000;        // 授权过滤器
    public static final int EXCEPTION_TRANSLATION_FILTER = 3100; // 异常转换过滤器
    public static final int FILTER_SECURITY_INTERCEPTOR = 3200; // 过滤器安全拦截器
}

// 注册自定义过滤器的示例
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .addFilterBefore(new CustomFilter(), UsernamePasswordAuthenticationFilter.class)
        // 其他配置...
        ;
    return http.build();
}

三、SecurityContextPersistenceFilter与SecurityContextHolder

SecurityContextPersistenceFilter(在Spring Security 5.7后重命名为SecurityContextHolderFilter)是过滤器链中最早执行的核心过滤器之一,主要负责在请求之间维护SecurityContext。它在请求开始时从SecurityContextRepository(默认使用HttpSessionSecurityContextRepository)加载安全上下文,并在请求结束时将其存回。SecurityContextHolder是一个工具类,使用ThreadLocal存储当前请求的安全上下文,包含当前认证用户的信息。这种设计使得应用中的任何组件都能轻松访问当前用户信息,而无需传递参数。

// SecurityContextPersistenceFilter的关键逻辑
public class SecurityContextPersistenceFilter extends GenericFilterBean {
    
    private SecurityContextRepository repo;
    
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        
        // 从存储库获取安全上下文
        SecurityContext contextBeforeChain = repo.loadContext(
                new HttpRequestResponseHolder(request, response));
        
        try {
            // 设置到当前线程
            SecurityContextHolder.setContext(contextBeforeChain);
            chain.doFilter(request, response);
        } finally {
            // 请求完成后获取上下文(可能已被修改)
            SecurityContext contextAfterChain = SecurityContextHolder.getContext();
            
            // 清除线程本地变量
            SecurityContextHolder.clearContext();
            
            // 保存回存储库(通常是会话)
            repo.saveContext(contextAfterChain, request, response);
        }
    }
}

// 在应用代码中访问当前用户
public String getCurrentUsername() {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    if (authentication != null && authentication.isAuthenticated()) {
        return authentication.getName();
    }
    return "anonymous";
}

四、CsrfFilter与跨站请求伪造防护

CsrfFilter是Spring Security防止跨站请求伪造(CSRF)攻击的关键组件。它要求所有修改状态的HTTP请求(如POST、PUT、DELETE)包含一个有效的CSRF令牌,该令牌在服务器端生成并存储在用户会话中。当表单提交或AJAX请求发送时,CsrfFilter会验证请求中的令牌与会话中存储的令牌是否匹配。这种机制有效防止了攻击者利用用户浏览器在受信任网站上执行未授权的操作。CsrfFilter默认启用,但对于某些API场景(如无状态REST API)可能需要禁用。

// CsrfFilter的保护机制
public class CsrfFilter extends OncePerRequestFilter {
    
    private final CsrfTokenRepository tokenRepository;
    private final RequestMatcher requireCsrfProtectionMatcher;
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {
        
        // 生成或获取CSRF令牌
        CsrfToken csrfToken = tokenRepository.loadToken(request);
        if (csrfToken == null) {
            csrfToken = tokenRepository.generateToken(request);
            tokenRepository.saveToken(csrfToken, request, response);
        }
        
        // 将令牌放入请求属性中,供视图层使用
        request.setAttribute(CsrfToken.class.getName(), csrfToken);
        request.setAttribute(csrfToken.getParameterName(), csrfToken);
        
        // 检查请求是否需要CSRF保护
        if (!requireCsrfProtectionMatcher.matches(request)) {
            filterChain.doFilter(request, response);
            return;
        }
        
        // 从请求中获取提交的令牌
        String actualToken = request.getHeader(csrfToken.getHeaderName());
        if (actualToken == null) {
            actualToken = request.getParameter(csrfToken.getParameterName());
        }
        
        // 验证令牌
        if (!csrfToken.getToken().equals(actualToken)) {
            // 令牌无效,拒绝请求
            throw new AccessDeniedException("无效的CSRF令牌");
        }
        
        filterChain.doFilter(request, response);
    }
}

// 在Thymeleaf模板中使用CSRF令牌
// <form action="/process" method="post">
//     <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
//     <!-- 其他表单内容 -->
// </form>

五、UsernamePasswordAuthenticationFilter与表单登录

UsernamePasswordAuthenticationFilter是处理表单登录认证的核心过滤器。当用户提交包含用户名和密码的登录表单时,这个过滤器会拦截请求,从表单中提取凭据,创建一个UsernamePasswordAuthenticationToken对象,并将其传递给AuthenticationManager进行验证。如果认证成功,它会将认证结果存储在SecurityContextHolder中,并通过AuthenticationSuccessHandler处理后续流程(如重定向到之前请求的页面)。如果认证失败,则通过AuthenticationFailureHandler处理错误响应。

// UsernamePasswordAuthenticationFilter核心逻辑
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    
    public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
    public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
    
    private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
    private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
    
    // 创建过滤器并设置默认处理URL
    public UsernamePasswordAuthenticationFilter() {
        super(new AntPathRequestMatcher("/login", "POST"));
    }
    
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {
        
        // 获取用户名和密码
        String username = obtainUsername(request);
        String password = obtainPassword(request);
        
        if (username == null) {
            username = "";
        }
        if (password == null) {
            password = "";
        }
        
        // 创建认证令牌
        UsernamePasswordAuthenticationToken authRequest = 
                new UsernamePasswordAuthenticationToken(username, password);
        
        // 将请求详情(如IP地址、会话ID等)添加到认证令牌
        setDetails(request, authRequest);
        
        // 委托给AuthenticationManager进行认证
        return this.getAuthenticationManager().authenticate(authRequest);
    }
    
    // 用于自定义登录表单的字段名
    public void setUsernameParameter(String usernameParameter) {
        this.usernameParameter = usernameParameter;
    }
    
    public void setPasswordParameter(String passwordParameter) {
        this.passwordParameter = passwordParameter;
    }
    
    protected String obtainUsername(HttpServletRequest request) {
        return request.getParameter(usernameParameter);
    }
    
    protected String obtainPassword(HttpServletRequest request) {
        return request.getParameter(passwordParameter);
    }
}

六、FilterSecurityInterceptor与授权决策

FilterSecurityInterceptor是过滤器链中的最后一个重要过滤器,负责最终的授权决策。它检查当前认证用户是否有权限访问请求的资源。该过滤器从SecurityMetadataSource获取当前请求所需的权限,然后将请求与用户权限传递给AccessDecisionManager进行判断。AccessDecisionManager通常使用投票机制,根据一组AccessDecisionVoter的结果决定是否允许访问。这种设计使得授权逻辑与其他安全功能完全分离,便于根据业务需求进行定制。

// FilterSecurityInterceptor的关键逻辑
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
    
    private FilterInvocationSecurityMetadataSource securityMetadataSource;
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        invoke(fi);
    }
    
    public void invoke(FilterInvocation fi) throws IOException, ServletException {
        // 检查是否需要继续过滤
        if ((fi.getRequest() != null) && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
                && observeOncePerRequest) {
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            return;
        }
        
        // 标记过滤器已应用
        if (fi.getRequest() != null && observeOncePerRequest) {
            fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
        }
        
        // 进行安全检查
        InterceptorStatusToken token = super.beforeInvocation(fi);
        
        try {
            // 调用下一个过滤器
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } finally {
            super.finallyInvocation(token);
        }
        
        // 执行后续处理
        super.afterInvocation(token, null);
    }
    
    // 具体的授权判断在AbstractSecurityInterceptor的beforeInvocation方法中
    // 它会从SecurityMetadataSource获取所需权限
    // 然后交给AccessDecisionManager进行授权决策
}

// 配置授权规则
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(authorize -> authorize
            .requestMatchers("/api/public/**").permitAll()
            .requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
            .requestMatchers("/api/admin/**").hasRole("ADMIN")
            .requestMatchers("/api/**").authenticated()
        );
    
    return http.build();
}

七、ExceptionTranslationFilter与安全异常处理

ExceptionTranslationFilter位于FilterSecurityInterceptor之前,负责处理整个过滤器链中抛出的安全异常。它捕获两种主要的异常:AuthenticationException(认证异常)和AccessDeniedException(访问拒绝异常)。对于未认证用户抛出的访问拒绝异常,会启动AuthenticationEntryPoint,引导用户进行认证(如重定向到登录页面)。对于已认证用户抛出的访问拒绝异常,会委托给AccessDeniedHandler处理(如显示403错误页面)。这种机制确保了安全异常的统一处理,提供了良好的用户体验。

// ExceptionTranslationFilter的核心逻辑
public class ExceptionTranslationFilter extends GenericFilterBean {
    
    private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
    private AuthenticationEntryPoint authenticationEntryPoint;
    private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
    private RequestCache requestCache = new HttpSessionRequestCache();
    
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        
        try {
            // 尝试执行过滤器链
            chain.doFilter(request, response);
        } catch (IOException | ServletException | RuntimeException ex) {
            // 提取安全异常
            Throwable cause = ex.getCause();
            if (cause instanceof AuthenticationException) {
                // 处理认证异常
                handleAuthenticationException(request, response, chain, (AuthenticationException) cause);
            } else if (cause instanceof AccessDeniedException) {
                // 处理访问拒绝异常
                handleAccessDeniedException(request, response, chain, (AccessDeniedException) cause);
            } else {
                // 重新抛出其他异常
                throw ex;
            }
        }
    }
    
    private void handleAuthenticationException(HttpServletRequest request, HttpServletResponse response,
            FilterChain chain, AuthenticationException exception) throws IOException, ServletException {
        
        // 保存当前请求,以便认证后重定向回来
        SavedRequest savedRequest = requestCache.saveRequest(request, response);
        
        // 启动认证入口点(如重定向到登录页面)
        authenticationEntryPoint.commence(request, response, exception);
    }
    
    private void handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response,
            FilterChain chain, AccessDeniedException exception) throws IOException, ServletException {
        
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        
        // 检查是否已认证(匿名用户或记住我用户视为未完全认证)
        if (authenticationTrustResolver.isAnonymous(authentication) ||
                authenticationTrustResolver.isRememberMe(authentication)) {
            // 未认证用户,启动认证流程
            handleAuthenticationException(
                    request, response, chain, new InsufficientAuthenticationException(exception.getMessage()));
        } else {
            // 已认证用户,调用访问拒绝处理器
            accessDeniedHandler.handle(request, response, exception);
        }
    }
}

总结

Spring Security的过滤器链是其安全机制的核心,通过一系列按特定顺序排列的过滤器,实现了Web应用的全面安全防护。SecurityContextPersistenceFilter负责维护用户的安全上下文,确保认证信息在整个请求过程中可用。CsrfFilter通过令牌机制防止跨站请求伪造攻击,保护应用免受恶意提交的侵害。UsernamePasswordAuthenticationFilter处理用户的表单登录,将凭据传递给认证管理器进行验证。FilterSecurityInterceptor作为最后一道防线,进行最终的授权决策,确保用户只能访问其有权限的资源。ExceptionTranslationFilter则负责捕获并处理整个过滤器链中抛出的安全异常,提供友好的用户体验。理解这些核心过滤器的执行顺序和职责,对于正确配置Spring Security、解决安全问题和实现自定义安全需求至关重要。开发者可以通过添加、移除或自定义过滤器,根据业务需求调整安全策略,构建既安全又灵活的应用系统。在微服务架构和云原生应用日益普及的今天,Spring Security的过滤器链机制提供了坚实的安全基础,助力开发者应对不断演变的安全挑战。


http://www.kler.cn/a/613696.html

相关文章:

  • spring security设置多个数据源和登录验证码
  • 故障识别 | 基于改进螂优化算法(MSADBO)优化变分模态提取(VME)结合稀疏最大谐波噪声比解卷积(SMHD)进行故障诊断识别,matlab代码
  • MySQL Explain 分析 SQL 执行计划
  • 数据设计(范式、步骤)
  • 2025跳槽学习计划
  • 速卖通历史价格数据获取:API合规调用与爬虫方案风险对比
  • Maven版本统一管理
  • Cursor生成的UI太丑?如何减少UI拉扯?
  • WEB或移动端常用交互元素及组件 | Axure / 元件类型介绍(表单元件、菜单和表格 、流程元件、标记元件)
  • 一文详解k8s体系架构知识
  • 【Kafka】Kafka4.0在windows上启动
  • Android 蓝牙/Wi-Fi通信协议之:经典蓝牙(BT 2.1/3.0+)介绍
  • STM32 IIC通信
  • uWebSockets开发入门
  • ZW3D二次开发_非模板表单_创建
  • C#TCP通讯封装服务器工具类
  • Dify 0.15.3版本 本地部署指南
  • 【Spiffo】光速项目:LVGL v9框架下的MIPI简易相机_Part1
  • Unity中的MaterialPropertyBlock的作用和 Material 的区别
  • 【蓝桥杯】每日练习 Day14 递归