在 Spring Security 6 中,CSRF(跨站请求伪造)防护默认启用,以下是详细说明和代码示例:
核心概念
-
CSRF 防护原理
- 服务端生成唯一令牌(CSRF Token)
- 客户端在修改状态的请求(POST/PUT/DELETE/PATCH)中携带令牌
- 服务端验证令牌有效性
-
令牌存储方式
CookieCsrfTokenRepository
:令牌存储在XSRF-TOKEN
Cookie 中(推荐)HttpSessionCsrfTokenRepository
:令牌存储在 Session 中
配置代码(Java Config)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// 自定义请求处理器(适配SPA应用)
CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
requestHandler.setCsrfRequestAttributeName(null); // 禁用从请求属性中获取token
http
// 配置CSRF(使用Cookie存储Token)
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.csrfTokenRequestHandler(requestHandler)
)
// 授权配置
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
// 表单登录
.formLogin(form -> form
.loginPage("/login")
.permitAll()
);
return http.build();
}
}
客户端集成示例
1. Thymeleaf 表单(自动处理)
<form method="post" action="/transfer">
<!-- 自动注入CSRF令牌 -->
<input type="hidden"
th:name="${_csrf.parameterName}"
th:value="${_csrf.token}" />
<!-- 表单字段 -->
<input type="text" name="amount"/>
<button type="submit">提交</button>
</form>
2. JavaScript(SPA 应用)
// 从Cookie中获取CSRF令牌
function getCsrfToken() {
return document.cookie
.split('; ')
.find(row => row.startsWith('XSRF-TOKEN='))
?.split('=')[1];
}
// 发送请求时携带令牌
fetch('/api/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-XSRF-TOKEN': getCsrfToken() // 关键头
},
body: JSON.stringify({ amount: 100 })
});
3. 手动禁用CSRF(不推荐)
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // 禁用CSRF防护
// ...其他配置
return http.build();
}
关键配置说明
配置项 | 说明 |
---|---|
CookieCsrfTokenRepository | 令牌通过Cookie传输(符合OAuth2标准) |
withHttpOnlyFalse() | 允许JavaScript读取Cookie(SPA必需) |
X-XSRF-TOKEN | 客户端需要在请求头中添加此令牌 |
CsrfTokenRequestAttributeHandler | 新处理器,解决CsrfToken 在请求属性中的问题(Spring Security 6.1+) |
自定义行为
排除特定路径
.csrf(csrf -> csrf
.ignoringRequestMatchers("/api/public/**") // 忽略公共API
)
自定义令牌仓库
.csrf(csrf -> csrf
.csrfTokenRepository(new CustomTokenRepository())
)
常见问题解决
-
403 Forbidden 错误
- 检查客户端是否发送了有效的
X-XSRF-TOKEN
头 - 确认Cookie域/路径配置正确
- 检查客户端是否发送了有效的
-
Cookie 未生成
- 确保首次访问安全端点(如登录页)
- 检查浏览器是否阻止第三方Cookie
-
SPA 应用适配
- 使用
CookieCsrfTokenRepository.withHttpOnlyFalse()
- 实现令牌从Cookie到请求头的自动转换
- 使用
最佳实践:对浏览器访问保留CSRF防护,对非浏览器客户端(如移动APP)使用JWT等替代方案。