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-TOKENCookie; - 每次请求(包括GET/POST/PUT/DELETE)都会刷新
XSRF-TOKENCookie的有效期; - 后续的POST/PUT/DELETE请求会自动携带
X-XSRF-TOKEN头,无需手动重新获取令牌; - Cookie不会被随意清除,除非过期或用户主动清除。
补充说明
虽然JWT是无状态的,但只要前端存储了JWT(无论是localStorage还是Cookie),就存在CSRF攻击风险,启用CSRF保护是合理的。尤其是当JWT存储在非HttpOnly Cookie中时,CSRF保护能有效防止恶意网站盗用令牌发起请求。
内容的提问来源于stack exchange,提问作者Spindoctor
相关产品推荐
相关产品推荐

