跨站点请求伪造(CSRF)原理与Spring Security防护机制详解
CSRF攻击原理
攻击者诱导已登录用户访问恶意页面,利用用户当前会话的Cookie权限,向目标网站发送恶意请求(如转账、修改密码等)。例如:
-
用户登录了银行网站(已携带有效Cookie)。
-
用户访问攻击者伪造的页面,该页面包含一个隐藏的表单:
<form action="https://bank.com/transfer" method="POST"><input type="hidden" name="amount" value="10000"><input type="submit" value="领取优惠"> </form>
-
用户被诱导点击提交,银行服务器误认为是合法请求,执行转账。
Spring Security如何防御CSRF
Spring Security通过 CSRF令牌机制 实现防护,核心步骤如下:
- 生成令牌:在用户会话中生成唯一CSRF令牌(存储在Cookie或Session中)。
- 表单嵌入令牌:在每个POST/PUT/DELETE请求的表单中添加隐藏字段,值为当前令牌。
- 验证令牌:服务器端对请求的令牌进行校验,若不匹配则拒绝请求。
完整代码示例
以下代码展示如何在Spring Boot中配置CSRF防护,并通过表单和AJAX请求验证其有效性。
1. Spring Security配置类
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http// 启用CSRF防护(默认已启用,可显式配置).csrf(csrf -> csrf.requireCsrfProtectionMatcher(request -> {// 自定义需要CSRF保护的请求路径(示例:所有POST请求)return request.getMethod().equals("POST");}).ignoringAntMatchers("/api/public") // 排除特定路径(如公开API).csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) // 允许通过JS访问CSRF令牌).authorizeRequests().antMatchers("/login").permitAll().anyRequest().authenticated().and().formLogin().loginPage("/login").permitAll().and().logout().permitAll();}@Beanpublic UserDetailsService userDetailsService() {return username -> {if ("user".equals(username)) {return User.withUsername("user").password("{noop}password").roles("USER").build();}return null;};}
}
2. 表单页面(Thymeleaf示例)
<!-- login.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><title>Login</title>
</head>
<body><form method="post" action="/login"><input type="text" name="username" placeholder="Username" required><input type="password" name="password" placeholder="Password" required><button type="submit">Login</button><!-- 自动添加CSRF令牌字段 --><input type="hidden" name="_csrf" th:value="${_csrf.token}"><input type="hidden" name="_csrf_header" th:value="${_csrf.headerName}"></form>
</body>
</html>
3. 受保护的POST接口
@RestController
public class UserController {@PostMapping("/profile/update")public String updateProfile(@RequestParam String username,@RequestParam String email,@CsrfTokenRequestAttribute CsrfToken csrf) {// 验证逻辑(Spring Security已自动校验CSRF令牌)return "Profile updated successfully";}
}
4. AJAX请求示例(JavaScript)
// 从Cookie中获取CSRF令牌(需确保Cookie可访问)
function getCookie(name) {const value = `; ${document.cookie}`;const parts = value.split(`; ${name}=`);if (parts.length === 2) return parts.pop().split(';').shift();
}const csrfToken = getCookie('XSRF-TOKEN');fetch('/profile/update', {method: 'POST',headers: {'Content-Type': 'application/json','X-XSRF-TOKEN': csrfToken // 添加CSRF令牌到请求头},body: JSON.stringify({ username: 'user', email: 'user@example.com' })
});
关键配置说明
-
CSRF令牌存储:
- 默认存储在Cookie(
XSRF-TOKEN
)和Session中。 CookieCsrfTokenRepository
允许前端通过JavaScript读取令牌。
- 默认存储在Cookie(
-
表单自动注入:
- Thymeleaf模板中通过
_csrf
对象自动添加隐藏字段。 - Spring Security会自动校验请求中的令牌。
- Thymeleaf模板中通过
-
自定义规则:
requireCsrfProtectionMatcher
:指定需要CSRF保护的请求方法或路径。ignoringAntMatchers
:排除不需要防护的公开API。
测试CSRF防护
-
正常请求:
- 提交表单或携带有效令牌的AJAX请求,返回成功。
-
伪造请求:
-
使用Postman发送POST请求,未携带CSRF令牌:
POST /profile/update Content-Type: application/json Body: { "username": "attack" }
-
响应状态码:
403 Forbidden
(被拦截)。
-
总结表格
配置项 | 描述 | 默认行为 |
---|---|---|
CSRF防护 | 需要显式配置(默认已启用) | 启用 |
令牌存储位置 | Cookie(XSRF-TOKEN )和Session | Cookie + Session |
保护的HTTP方法 | POST/PUT/PATCH/DELETE | 支持所有非安全方法 |
前端集成方式 | 表单隐藏字段或AJAX请求头(X-XSRF-TOKEN ) | 自动注入 |
排除路径 | 通过ignoringAntMatchers 配置 | 无 |
通过上述配置,Spring Security能够有效防御CSRF攻击,同时提供灵活的扩展性以适应不同业务场景。