springboot~传统WEB应用开启CSRF
简单比喻:
想象你在咖啡店会员卡里有钱,你每次消费只需要说“用会员卡支付”。攻击者伪装成服务员,在你面前说“用会员卡转账100元到XXX账户”。因为你已经在咖啡店的系统中“登录”了(身份已认证),系统就会执行这个操作。
CSRF攻击原理:
攻击流程:
- 用户登录:用户登录正常的网站A(如银行网站),获得登录凭证(Cookie/Session)
- 用户访问恶意网站:用户在同一个浏览器中访问了攻击者的网站B
- 恶意请求:网站B通过隐藏表单、图片src、AJAX等方式,向网站A发送请求
- 自动携带凭证:浏览器会自动携带网站A的Cookie
- 网站A执行请求:网站A看到合法Cookie,误以为是用户的自愿操作
攻击示例:
<!-- 恶意网站上的代码 --> <img src="https://your-bank.com/transfer?to=hacker&amount=10000" width="0" height="0" /> <!-- 或隐藏表单 --> <form action="https://your-bank.com/change-email" method="POST"> <input type="hidden" name="email" value="hacker@evil.com"> </form> <script>document.forms[0].submit();</script>为什么有时要禁用CSRF保护?
适用禁用场景:
纯API服务(无浏览器交互)
# 移动App通过API访问后端 # 使用Token认证(JWT/OAuth),而不是Session Authorization: Bearer eyJhbGciOiJIUzI1NiIs...仅提供非状态改变的操作
GET /api/users # 只读操作,通常不需要CSRF POST /api/transfer # 写操作,需要CSRF微服务内部通信
# 服务间调用使用服务凭证,而不是用户会话 service-to-service: true某些特殊框架/场景
// GraphQL通常使用token而不是session // 或某些实时通信应用
具体框架中的禁用示例:
Spring Security:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() // 禁用CSRF .authorizeRequests() .anyRequest().authenticated() .and() .httpBasic(); // 使用HTTP Basic认证 } }Django:
# settings.py CSRF_COOKIE_SECURE = False # 禁用CSRF # 或针对特定视图 from django.views.decorators.csrf import csrf_exempt @csrf_exempt def api_view(request): passCSRF保护机制对比:
| 保护机制 | 工作原理 | 适用场景 |
|---|---|---|
| CSRF Token | 服务器生成Token,表单/请求必须携带 | 传统Web应用 |
| SameSite Cookie | Cookie仅在同站请求中发送 | 现代浏览器支持 |
| 双重Cookie验证 | 客户端读取Cookie并附加到请求 | 兼容性较好 |
| Referer检查 | 检查请求来源 | 简单但不可靠 |
| 无(禁用) | 不验证 | 纯API、内部服务 |
何时应该启用/禁用CSRF?
应该启用CSRF的场景:
- ✅ 传统的基于Session的Web应用
- ✅ 用户通过浏览器访问的表单提交
- ✅ 需要用户交互的操作(转账、修改数据)
- ✅ 使用Cookie/Session进行身份认证
可以禁用CSRF的场景:
- ✅ 纯REST API,使用JWT/OAuth Token认证
- ✅ 仅限移动App访问的后端服务
- ✅ 微服务间的内部通信
- ✅ 只读的公共API
- ✅ 使用其他认证方式(API Key、HMAC签名)
禁用CSRF后的替代安全方案:
# 替代方案示例: 1. JWT Token认证: 每次请求携带: Authorization: Bearer <token> 2. API Key + Secret: 请求签名: X-Signature: sha256(api_secret + request_data) 3. OAuth 2.0: 使用Access Token进行授权 4. CORS限制: Access-Control-Allow-Origin: https://trusted-domain.com 5. Rate Limiting: 限制请求频率防止滥用最佳实践建议:
// 在Spring中,可以针对不同端点配置不同的CSRF策略 public class SecurityConfig { protected void configure(HttpSecurity http) { http // 对Web页面启用CSRF .csrf(csrf -> csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler()) ) .requireCsrfProtectionMatcher( new RequestMatcher() { public boolean matches(HttpServletRequest request) { // 仅对特定路径启用CSRF return request.getRequestURI().startsWith("/web/"); } }) .and() // API端点使用无状态认证 .authorizeRequests() .antMatchers("/api/**").authenticated() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); // API无状态 } }验证 CSRF 是否生效
- 检查 Cookie:登录后查看浏览器是否有
XSRF-TOKENCookie - 测试请求:
- 直接发送 POST 请求应该被拒绝(403 错误)
- 携带正确的 CSRF Token 的请求应该成功
- 检查响应头:某些配置下,响应头会包含 CSRF Token
# 使用 curl 测试 # 1. 先获取 CSRF Token(从登录后的 Cookie 或响应头) # 2. 发送带 CSRF Token 的请求 curl -X POST http://localhost:8080/api/test \ -H "Content-Type: application/json" \ -H "X-XSRF-TOKEN: YOUR_CSRF_TOKEN" \ -H "Cookie: XSRF-TOKEN=YOUR_CSRF_TOKEN" \ -d '{"data": "test"}'总结:
禁用CSRF的前提条件:
- 应用不使用Cookie/Session进行身份认证
- 请求来源可控(如仅限移动App、内部服务)
- 已实施同等或更强的安全措施替代
