1. Spring Security中的CSRF防护机制解析
CSRF(Cross-Site Request Forgery)跨站请求伪造是Web应用中常见的安全威胁。Spring Security默认会对所有非安全HTTP方法(如POST、PUT等)启用CSRF防护。其核心原理是通过同步器令牌模式:
- 服务端生成唯一令牌(CsrfToken)
- 令牌存储在会话或Cookie中
- 客户端需在后续请求中携带该令牌
- 服务端验证令牌有效性
1.1 默认配置与工作原理
Spring Security 6.x的默认CSRF配置包含以下关键组件:
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(Customizer.withDefaults()); // 启用默认CSRF防护 return http.build(); } }默认实现包含三个核心部分:
HttpSessionCsrfTokenRepository- 将令牌存储在HttpSession中XorCsrfTokenRequestAttributeHandler- 提供BREACH攻击防护CsrfFilter- 处理实际的令牌验证逻辑
关键点:从Spring Security 6开始,CSRF令牌加载改为延迟模式,只有需要验证的请求才会加载会话,这显著提升了性能。
1.2 令牌存储策略对比
Spring Security提供两种主要的令牌存储方式:
| 存储方式 | 实现类 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| Session存储 | HttpSessionCsrfTokenRepository | 传统Web应用 | 安全性高 | 需要会话支持 |
| Cookie存储 | CookieCsrfTokenRepository | SPA/前后端分离 | 无状态 | 需防范XSS |
Cookie存储的典型配置:
http.csrf(csrf -> csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) );1.3 禁用CSRF防护的场景
在某些API-only的应用中可能需要禁用CSRF:
http.csrf(csrf -> csrf.disable());更精细的控制方式是指定忽略路径:
http.csrf(csrf -> csrf .ignoringRequestMatchers("/api/public/**") );2. CORS跨域配置实战
2.1 CORS与CSRF的关系
虽然都涉及跨域安全,但CORS和CSRF解决的是不同层面的问题:
- CORS:控制哪些外部域可以访问资源
- CSRF:防止未授权的命令执行
2.2 Spring Security中的CORS配置
全局CORS配置示例:
@Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowedOrigins(List.of("https://trusted.com")); config.setAllowedMethods(List.of("GET","POST")); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; }与Spring Security集成:
http.cors(cors -> cors .configurationSource(corsConfigurationSource()) );2.3 常见CORS错误处理
遇到"has been blocked by CORS policy"错误时检查:
- 预检请求(OPTIONS)是否通过
- 响应头是否包含
Access-Control-Allow-Origin - 带凭证请求时是否设置
allowCredentials=true - 复杂请求是否声明了
Access-Control-Allow-Methods
3. Session管理高级配置
3.1 会话固定保护
Spring Security默认启用会话固定保护:
http.sessionManagement(session -> session .sessionFixation().migrateSession() );可选策略:
changeSessionId:仅更改ID(Servlet 3.1+)migrateSession:创建新会话并复制属性(默认)newSession:创建全新会话none:禁用保护
3.2 并发会话控制
限制单个用户的会话数量:
http.sessionManagement(session -> session .maximumSessions(1) .maxSessionsPreventsLogin(true) );3.3 会话超时处理
结合CSRF时需特别注意:
@Bean public HttpSessionEventPublisher httpSessionEventPublisher() { return new HttpSessionEventPublisher(); }配置会话失效URL:
http.sessionManagement(session -> session .invalidSessionUrl("/session-expired") );4. 综合配置示例
4.1 安全配置模板
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .cors(cors -> cors .configurationSource(corsConfigurationSource()) ) .csrf(csrf -> csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .ignoringRequestMatchers("/api/public/**") ) .sessionManagement(session -> session .sessionFixation().migrateSession() .maximumSessions(1) .expiredUrl("/session-expired") ); return http.build(); } @Bean public CorsConfigurationSource corsConfigurationSource() { // 同上文CORS配置 } }4.2 测试配置要点
测试CSRF防护的MockMvc示例:
@Autowired private WebApplicationContext context; private MockMvc mockMvc; @BeforeEach void setup() { mockMvc = MockMvcBuilders .webAppContextSetup(context) .apply(springSecurity()) .build(); } @Test void postWithValidCsrf() throws Exception { mockMvc.perform(post("/submit") .with(csrf())) // 自动添加有效CSRF令牌 .andExpect(status().isOk()); }5. 疑难问题排查指南
5.1 CSRF相关错误
问题1:InvalidCsrfTokenException
可能原因:
- 表单缺少
_csrf参数 - AJAX请求未携带X-CSRF-TOKEN头
- 会话超时导致令牌失效
解决方案:
- 检查表单是否包含:
<input type="hidden" name="_csrf" th:value="${_csrf.token}"/> - 对于AJAX请求,添加:
headers: {'X-CSRF-TOKEN': token}
问题2:CookieCsrfTokenRepository不工作
检查点:
- 是否设置了
withHttpOnlyFalse() - 前端是否正确读取XSRF-TOKEN cookie
- 是否在请求头中携带X-XSRF-TOKEN
5.2 CORS相关错误
问题:预检请求失败
典型表现:
Response to preflight request doesn't pass access control check解决方案:
- 确保OPTIONS请求不被拦截:
.requestMatchers(HttpMethod.OPTIONS).permitAll() - 检查CORS配置是否包含所需方法:
config.setAllowedMethods(List.of("GET","POST","PUT","DELETE","OPTIONS"));
5.3 Session相关错误
问题:session persistence异常
现象:
- 重启后会话丢失
- 集群环境下会话不同步
解决方案:
- 配置持久化Session存储:
spring.session.store-type=redis - 添加依赖:
<dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency>
6. 性能优化建议
CSRF令牌延迟加载(Spring Security 6+默认)
// 无需特别配置,默认已启用会话优化:
- 减少会话中存储的数据量
- 考虑使用
session.stateless=true的无状态架构
CORS缓存:
config.setMaxAge(3600L); // 预检结果缓存1小时异步处理:
http.sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.ALWAYS) );
实际项目中,我曾遇到一个SPA应用因频繁预检请求导致性能下降的情况。通过调整CORS缓存时间和精简允许的方法列表,最终将API响应时间降低了40%。关键配置如下:
config.setAllowedMethods(List.of("GET","POST")); // 仅允许必要方法 config.setMaxAge(7200L); // 预检缓存2小时