最后更新:2026-04-17
跨站请求伪造(Cross-Site Request Forgery,CSRF)是指攻击者诱导已登录用户的浏览器向目标网站发送恶意请求,利用用户已认证的身份执行非预期操作(如转账、改密码、删除数据)。
| 维度 | 评级 |
|---|---|
| OWASP Top 10 | A01:2025 - Broken Access Control |
| CWE | CWE-352 |
| 严重程度 | 高危 |
| 类型 | 说明 |
|---|---|
| GET 型 CSRF | 敏感操作用 GET 请求,攻击者用 <img src="..."> 触发 |
| POST 型 CSRF | 攻击者构造自动提交的 HTML 表单 |
| JSON CSRF | Content-Type 为 text/plain 绕过同源限制 |
| Flash CSRF | 利用 Flash 发送跨域请求(已基本消亡) |
// [VULNERABLE] Spring Security 关闭了 CSRF 保护
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.formLogin(Customizer.withDefaults())
.csrf(csrf -> csrf.disable()); // 危险:完全禁用 CSRF 保护
return http.build();
}
}
// [VULNERABLE] 敏感操作使用 GET 请求
@GetMapping("/transfer")
public String transfer(@RequestParam String toAccount,
@RequestParam BigDecimal amount,
Principal principal) {
// 危险:转账操作使用 GET,攻击者可构造链接诱导点击
accountService.transfer(principal.getName(), toAccount, amount);
return "转账成功";
}
// 攻击:<img src="https://bank.com/transfer?toAccount=evil&amount=10000">
// [SECURE] Spring Security 默认启用 CSRF,前端传递 Token
@Configuration
@EnableWebSecurity
public class SecureSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.formLogin(Customizer.withDefaults())
// 默认启用 CSRF,使用 CookieCsrfTokenRepository 支持前后端分离
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()));
return http.build();
}
}
// [VULNERABLE] REST API 未校验 CSRF Token
@RestController
public class UserController {
@PostMapping("/api/user/password")
public ResponseEntity<?> changePassword(@RequestBody PasswordRequest req,
Principal principal) {
// 危险:仅凭 Cookie 中的 Session 认证,无 CSRF Token 校验
userService.changePassword(principal.getName(), req.getNewPassword());
return ResponseEntity.ok().build();
}
}
// [SECURE] 前后端分离使用双重提交 Cookie 模式
@Configuration
public class CsrfConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf
// 将 CSRF token 写入 Cookie,前端从 Cookie 读取并放入请求头
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
// 忽略不需要 CSRF 保护的端点(如 OAuth 回调)
.ignoringRequestMatchers("/oauth/**")
);
return http.build();
}
}
// 前端 JavaScript(示例):
// const csrfToken = document.cookie.match(/XSRF-TOKEN=([^;]+)/)[1];
// fetch('/api/user/password', {
// method: 'POST',
// headers: { 'X-XSRF-TOKEN': csrfToken },
// body: JSON.stringify({ newPassword: '...' })
// });
// [SECURE] 纯 API 服务使用 SameSite Cookie + 检查 Origin/Referer
@Configuration
public class ApiSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// Stateless API 可使用 JWT + 检查 Origin Header 替代 CSRF Token
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.csrf(csrf -> csrf.disable()) // 无状态 JWT API 可禁用
.addFilterBefore(new OriginCheckFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
// Origin 来源校验过滤器
public class OriginCheckFilter extends OncePerRequestFilter {
private static final Set<String> ALLOWED_ORIGINS = Set.of(
"https://app.example.com",
"https://www.example.com"
);
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
if (!isSafeMethod(request.getMethod())) {
String origin = request.getHeader("Origin");
if (origin != null && !ALLOWED_ORIGINS.contains(origin)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "非法来源");
return;
}
}
filterChain.doFilter(request, response);
}
private boolean isSafeMethod(String method) {
return "GET".equals(method) || "HEAD".equals(method) || "OPTIONS".equals(method);
}
}
csrf.disable()SameSite=Strict 或 SameSite=LaxCookieCsrfTokenRepositorySameSite=StrictOrigin 或 Referer 请求头