跨站点请求伪造(CSRF)原理与Spring Security防护机制详解
跨站点请求伪造(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 {
@Override
protected 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();
}
@Bean
public 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攻击,同时提供灵活的扩展性以适应不同业务场景。