如何在Spring Boot中正确实现CSRF防护?
CSRF防护配置问题排查与解决
初始问题
在为应用实现CSRF防护时遇到问题,当前安全配置如下:
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .authorizeHttpRequests(authorization -> authorization .requestMatchers("/api/admin/**").hasRole("ADMIN") .requestMatchers("/api/moderator/**").hasAnyRole("ADMIN", "MODERATOR") .requestMatchers("/api/orders/**").authenticated() .requestMatchers("/api/**").permitAll() .anyRequest().authenticated()) .csrf().disable() .addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class) .build(); }
移除.csrf().disable()后,所有POST请求都会被拦截并返回403 Forbidden,需要明确如何安全实现CSRF防护,让服务器识别可信请求。
更新尝试后的问题
添加CSRF配置后,服务器会向Postman返回XSRF-TOKEN:
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .authorizeHttpRequests(authorization -> authorization .requestMatchers("/api/admin/**").hasRole("ADMIN") .requestMatchers("/api/moderator/**").hasAnyRole("ADMIN", "MODERATOR") .requestMatchers("/api/orders/**").authenticated() .requestMatchers("/api/**").permitAll() .anyRequest().authenticated()) .addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class) .csrf((csrf) -> csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())) .build(); }
在Postman请求中添加X-XSRF-TOKEN请求头并填入服务器返回的值后,仍收到403错误,服务器日志显示:为http://localhost:8080/api/login找到无效的CSRF令牌
问题根源:JWT与CSRF的兼容性
你的应用同时使用JWT认证和CSRF防护,核心矛盾在于:Spring Security的CSRF防护默认依赖服务器端会话(Session),但JWT是无状态认证方案,不依赖服务器会话。这种情况下,CSRF令牌的存储与验证逻辑会出现不匹配,导致令牌无效的错误。
解决方案步骤
1. 按需配置CSRF(推荐方案)
对于使用JWT的无状态API接口,完全不需要启用CSRF防护——CSRF攻击的核心是利用浏览器自动携带会话Cookie的特性,而JWT通常放在Authorization: Bearer xxx请求头中,不会被浏览器自动携带,攻击者无法伪造带有效JWT的请求。
你可以针对非API的页面请求(如果存在)启用CSRF,对所有API接口禁用:
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .authorizeHttpRequests(authorization -> authorization .requestMatchers("/api/admin/**").hasRole("ADMIN") .requestMatchers("/api/moderator/**").hasAnyRole("ADMIN", "MODERATOR") .requestMatchers("/api/orders/**").authenticated() .requestMatchers("/api/**").permitAll() .anyRequest().authenticated()) .csrf(csrf -> csrf // 对所有/api开头的接口跳过CSRF验证 .ignoringRequestMatchers("/api/**") // 对非API的页面请求启用CSRF,用Cookie存储令牌 .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())) .addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class) .build(); }
2. 若必须为API启用CSRF(不推荐)
如果场景特殊必须为API接口启用CSRF,需要确保以下几点:
- 客户端先发送一次GET请求获取
XSRF-TOKENCookie - 后续POST/PUT/DELETE请求需同时携带
XSRF-TOKENCookie和X-XSRF-TOKEN请求头,且两者值完全一致 - Postman配置注意事项:
- 开启自动保存Cookie(在Settings -> Cookies中启用)
- 获取Cookie后再发送POST请求,手动添加
X-XSRF-TOKEN头,值直接取Cookie中的XSRF-TOKEN内容 - 不要手动修改Cookie值,确保请求时Cookie自动携带到服务器
3. 检查过滤器顺序
确保JWT过滤器不会在CSRF过滤器之前执行——CSRF验证需要在认证前完成,否则服务器未识别用户就会拒绝请求。可调整过滤器顺序:
// 让JWT过滤器在CSRF过滤器之后执行 .addFilterAfter(jwtFilter(), CsrfFilter.class)
关键说明
- 无状态JWT API无需CSRF防护,因为其认证凭证不会被浏览器自动携带,从根源上避免了CSRF攻击的可能。
- 仅当应用使用传统Session认证(依赖浏览器会话Cookie)时,才需要启用CSRF防护,此时Cookie存储的CSRF令牌才能正常工作。
内容的提问来源于stack exchange,提问作者eskil4152
相关产品推荐
相关产品推荐

