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

Spring Security 6无状态REST中JWT与CSRF集成问题

Angular + Spring Security 6:JWT与CSRF集成的正确实现及问题修复

核心问题分析

你遇到的CSRF令牌Cookie被清除的问题,根源在于无状态会话策略(STATELESS)与默认CSRF令牌绑定会话的机制冲突:Spring Security默认将CSRF令牌与HttpSession绑定,当设置SessionCreationPolicy.STATELESS后,请求结束会话会被销毁,绑定的CSRF令牌也会失效,导致对应的Cookie被清除。

解决方案:调整CSRF配置,让令牌脱离会话依赖

以下是具体的配置修改和实现步骤:

1. 修正SecurityFilterChain中的CSRF配置

调整CSRF令牌仓库和请求处理器,让令牌通过Cookie持久化,不依赖会话:

@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    CsrfTokenRequestAttributeHandler csrfRequestHandler = new CsrfTokenRequestAttributeHandler();
    // 移除CSRF令牌与请求属性的绑定,确保无状态场景下正常处理
    csrfRequestHandler.setCsrfRequestAttributeName(null);

    http
        .sessionManagement(sessionConfig -> sessionConfig.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .cors(corsConfig -> corsConfig.configurationSource(request -> {
            CorsConfiguration myconfig = new CorsConfiguration();
            myconfig.setAllowedOrigins(Collections.singletonList("http://localhost:4200"));
            myconfig.setAllowedMethods(Collections.singletonList("*"));
            myconfig.setAllowCredentials(true);
            myconfig.setAllowedHeaders(Collections.singletonList("*"));
            myconfig.setExposedHeaders(Arrays.asList("Authorization", "X-XSRF-TOKEN")); // 暴露CSRF头让前端读取
            myconfig.setMaxAge(3600L);
            return myconfig;
        }))
        .csrf(csrfConfig -> csrfConfig
            .csrfTokenRequestHandler(csrfRequestHandler)
            .ignoringRequestMatchers(REGISTER_URL, H2_CONSOLE_URL)
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()
                .setCookieName("XSRF-TOKEN")
                .setCookiePath("/")
                .setCookieMaxAge(3600) // 设置Cookie有效期,比如1小时
                .setCookieSecure(false) // 生产环境请改为true(HTTPS)
            ))
        .addFilterAfter(new JWTTokenGeneratorFilter(), BasicAuthenticationFilter.class)
        .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class)
        .addFilterBefore(new JWTTokenValidationFilter(), BasicAuthenticationFilter.class)
        .requiresChannel(rcc -> rcc.anyRequest().requiresInsecure())
        .authorizeHttpRequests(requests -> requests
            .requestMatchers(H2_CONSOLE_URL).permitAll()
            .requestMatchers(REGISTER_URL, "/error", "/invalidSession").permitAll()
            .requestMatchers(LOGIN_URL, OCCASSIONS_LIST_URL, OCCASSION_GET, OCCASSIONS_EDIT, OCCASIONS_ADD, USER_GET, USER_EDIT).authenticated())
        .headers(h -> h.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin))
        .formLogin(withDefaults())
        .httpBasic(hbc -> hbc.authenticationEntryPoint(new CustomBasicAuthenticationEntryPoint()))
        .exceptionHandling(ehc -> ehc.accessDeniedHandler(new CustomAccessDeniedHandler()));

    return http.build();
}

2. 修正CsrfCookieFilter逻辑,确保每次请求刷新令牌

修改过滤器,让所有请求(包括POST/PUT/DELETE)都重新颁发CSRF令牌Cookie,避免Cookie被清除:

public class CsrfCookieFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
        if (csrfToken != null) {
            // 强制在响应中设置CSRF令牌Cookie,覆盖旧的Cookie
            Cookie csrfCookie = new Cookie("XSRF-TOKEN", csrfToken.getToken());
            csrfCookie.setPath("/");
            csrfCookie.setHttpOnly(false); // 允许Angular读取
            csrfCookie.setMaxAge(3600); // 与令牌仓库配置一致
            csrfCookie.setSecure(false); // 生产环境改为true
            response.addCookie(csrfCookie);
            // 同时在响应头中暴露令牌,可选但更可靠
            response.setHeader(csrfToken.getHeaderName(), csrfToken.getToken());
        }
        filterChain.doFilter(request, response);
    }
}

3. Angular端确认自动携带CSRF令牌

Angular的HttpClientModule默认会自动读取XSRF-TOKEN Cookie,并在POST/PUT/DELETE等不安全请求中添加X-XSRF-TOKEN请求头,无需额外配置。如果需要自定义,可以在HttpClientModule的配置中调整:

import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { HttpClientXsrfModule } from '@angular/common/http';

@NgModule({
  imports: [
    HttpClientModule,
    HttpClientXsrfModule.withOptions({
      cookieName: 'XSRF-TOKEN',
      headerName: 'X-XSRF-TOKEN'
    })
  ]
})
export class AppModule { }

预期行为

  • 登录成功后,浏览器会收到有效期为1小时的XSRF-TOKEN Cookie;
  • 每次请求(包括GET/POST/PUT/DELETE)都会刷新XSRF-TOKEN Cookie的有效期;
  • 后续的POST/PUT/DELETE请求会自动携带X-XSRF-TOKEN头,无需手动重新获取令牌;
  • Cookie不会被随意清除,除非过期或用户主动清除。

补充说明

虽然JWT是无状态的,但只要前端存储了JWT(无论是localStorage还是Cookie),就存在CSRF攻击风险,启用CSRF保护是合理的。尤其是当JWT存储在非HttpOnly Cookie中时,CSRF保护能有效防止恶意网站盗用令牌发起请求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 20:23:19