You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调整Spring Boot Security CsrfFilter顺序以解决SSO CSRF令牌无效问题

问题背景

当前过滤器链(@EnableWebSecurity(debug=true)输出)

Security filter chain: [
  DisableEncodeUrlFilter
  WebAsyncManagerIntegrationFilter
  SecurityContextHolderFilter
  HeaderWriterFilter
  CorsFilter
  CsrfFilter
  LogoutFilter
  NegotiateSecurityFilter
  BasicAuthenticationFilter
  RequestCacheAwareFilter
  SecurityContextHolderAwareRequestFilter
  AnonymousAuthenticationFilter
  ExceptionTranslationFilter
  AuthorizationFilter
]

问题现象

在无状态SSO(基于Negotiate认证)结合CSRF防护的场景下,出现异常流程:

  • SSO协商将标准"Ping Pong"请求变为三次(Fiddler抓包:401、401、200/403)
  • 第三次已完成认证的请求触发 "Invalid CSRF Token found" 错误
  • 推测原因:CsrfFilter在第一个请求时生成并标记令牌为已使用/从存储移除,导致SSO协商完成后令牌失效

尝试操作

通过addFilterBefore/addFilterAfter尝试调整CsrfFilter顺序至BasicAuthenticationFilter之后,但过滤器链顺序未发生变化。

当前SecurityConfig配置

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;

import jakarta.servlet.Filter;
import waffle.spring.NegotiateSecurityFilter;
import waffle.spring.NegotiateSecurityFilterEntryPoint;

@Configuration
@EnableWebSecurity(debug = true)
@EnableMethodSecurity(securedEnabled = true)
public class SecurityConfig {

    @Autowired
    private NegotiateSecurityFilter negotiateSecurityFilter;

    @Autowired
    private NegotiateSecurityFilterEntryPoint entryPoint;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.cors()
        .and().authorizeHttpRequests().anyRequest().authenticated()
        .and().httpBasic().authenticationEntryPoint(entryPoint)
        .and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
        .and().addFilterAfter(negotiateSecurityFilter, BasicAuthenticationFilter.class);
        return http.build();
    }
}

解决方案

1. 手动注册CsrfFilter并调整顺序

Spring Security默认自动注入CsrfFilter,若要修改其执行顺序,需先禁用CSRF自动配置,再手动实例化CsrfFilter并指定插入位置:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    // 禁用默认CSRF自动配置
    http.csrf(csrf -> csrf.disable())
        .cors()
        .and().authorizeHttpRequests().anyRequest().authenticated()
        .and().httpBasic().authenticationEntryPoint(entryPoint)
        .and().addFilterAfter(negotiateSecurityFilter, BasicAuthenticationFilter.class);

    // 手动创建CsrfFilter并添加到BasicAuthenticationFilter之后
    CookieCsrfTokenRepository csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
    CsrfFilter csrfFilter = new CsrfFilter(csrfTokenRepository);
    http.addFilterAfter(csrfFilter, BasicAuthenticationFilter.class);

    return http.build();
}

2. 调整NegotiateSecurityFilter顺序(替代方案)

若CsrfFilter顺序调整困难,可将NegotiateSecurityFilter移至CsrfFilter之前,确保认证完成后再执行CSRF校验:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.cors()
        .and().authorizeHttpRequests().anyRequest().authenticated()
        .and().httpBasic().authenticationEntryPoint(entryPoint)
        .and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
        // 将NegotiateSecurityFilter移至CsrfFilter之前
        .and().addFilterBefore(negotiateSecurityFilter, CsrfFilter.class);

    return http.build();
}

3. 针对SSO协商请求跳过CSRF校验

针对Negotiate认证的协商请求(携带Authorization: Negotiate头)跳过CSRF校验,避免令牌在协商过程中被消耗:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.cors()
        .and().authorizeHttpRequests().anyRequest().authenticated()
        .and().httpBasic().authenticationEntryPoint(entryPoint)
        .and().csrf(csrf -> csrf
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                // 跳过Negotiate认证相关请求的CSRF校验
                .ignoringRequestMatchers(request -> 
                        "Negotiate".equals(request.getHeader("Authorization"))
                )
        )
        .and().addFilterAfter(negotiateSecurityFilter, BasicAuthenticationFilter.class);

    return http.build();
}

验证调整结果

修改配置后重启应用,通过@EnableWebSecurity(debug=true)输出确认过滤器链顺序,同时用Fiddler抓包验证SSO流程的请求次数及CSRF令牌有效性。

内容的提问来源于stack exchange,提问作者Jonny

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 10:10:31