一、CSRF攻击到底是什么鬼?
咱们先从一个生活场景说起。假设你正在网上银行转账,填好收款人和金额后点击确认,钱就转出去了。这时候如果有个坏蛋给你发了个钓鱼链接,你一不小心点了,结果发现账户里的钱莫名其妙转给了陌生人。这就是典型的CSRF(跨站请求伪造)攻击。
专业点说,CSRF就是攻击者利用用户已登录的身份,在用户不知情的情况下,以用户的名义发送恶意请求。因为浏览器会自动带上用户的cookie等认证信息,服务器无法区分这是用户自愿的操作还是被伪造的请求。
举个具体例子:
<!-- 恶意网站上的代码 -->
<img src="http://yourbank.com/transfer?to=hacker&amount=10000" width="0" height="0">
当你登录网银后访问这个恶意页面,浏览器会自动向银行发送转账请求,而你可能完全不知情。
二、防御CSRF的三大法宝
1. 验证Referer头
服务器可以检查HTTP请求头中的Referer字段,判断请求是否来自合法域名。
// Java示例(Spring框架)
@GetMapping("/transfer")
public String transfer(@RequestParam String to, @RequestParam int amount,
HttpServletRequest request) {
String referer = request.getHeader("Referer");
if(referer == null || !referer.startsWith("https://yourbank.com")) {
return "非法请求来源";
}
// 处理转账逻辑
return "转账成功";
}
注意:这种方法依赖浏览器行为,可能被绕过,且某些情况下Referer会被过滤。
2. 使用CSRF Token
这是目前最可靠的防御方式。服务器生成一个随机token,要求客户端在提交请求时必须带上这个token。
// Java示例(Spring Security)
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
}
// 前端表单中需要包含这个token
<form action="/transfer" method="post">
<input type="hidden" name="_csrf" value="${_csrf.token}"/>
<!-- 其他表单字段 -->
</form>
3. 双重Cookie验证
将token放在Cookie和请求参数中,服务器验证两者是否一致。
// Node.js示例(Express框架)
const csrf = require('csurf');
const cookieParser = require('cookie-parser');
app.use(cookieParser());
app.use(csrf({ cookie: true }));
// 前端需要这样发送请求
fetch('/transfer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'CSRF-Token': getCookie('XSRF-TOKEN') // 从cookie中读取
},
body: JSON.stringify(payload)
});
三、实战:Spring Security中的CSRF防护
让我们深入看看Spring Security是如何实现CSRF防护的:
// 更完整的Spring Security配置
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll()
.and()
.csrf()
.csrfTokenRepository(new HttpSessionCsrfTokenRepository())
.ignoringAntMatchers("/api/public/**");
}
}
// 控制器示例
@RestController
public class AccountController {
@PostMapping("/transfer")
public ResponseEntity<String> transferMoney(
@RequestParam String toAccount,
@RequestParam BigDecimal amount,
@RequestHeader("X-CSRF-TOKEN") String csrfToken) {
// Spring Security会自动验证CSRF token
// 业务逻辑处理
return ResponseEntity.ok("转账成功");
}
}
关键点说明:
csrfTokenRepository指定token存储方式(Session或Cookie)ignoringAntMatchers可以排除某些API的CSRF检查- 前端需要从
_csrf属性获取token并包含在请求中
四、特殊场景下的CSRF防护
1. REST API的防护
对于无状态的REST API,传统的CSRF防护可能不太适用。这时候可以考虑:
// 使用自定义Header的防护方式
@Configuration
@EnableWebSecurity
public class RestSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.requireCsrfProtectionMatcher(
new RequestMatcher() {
private Pattern allowedMethods =
Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$");
@Override
public boolean matches(HttpServletRequest request) {
// 仅对非安全方法启用CSRF检查
return !allowedMethods.matcher(request.getMethod()).matches();
}
})
.and()
.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class);
}
}
// 自定义Filter将token放入响应头
public class CsrfTokenResponseHeaderBindingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
CsrfToken token = (CsrfToken) request.getAttribute("_csrf");
if (token != null) {
response.setHeader("X-CSRF-HEADER", token.getHeaderName());
response.setHeader("X-CSRF-TOKEN", token.getToken());
}
filterChain.doFilter(request, response);
}
}
2. 单页应用(SPA)的防护
现代前端框架如React/Vue通常使用JWT认证,这时可以采用以下方案:
// 前端axios配置示例(Vue项目)
import axios from 'axios';
import cookies from 'js-cookie';
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 5000
});
// 请求拦截器
service.interceptors.request.use(
config => {
// 从cookie中获取CSRF token
const csrfToken = cookies.get('XSRF-TOKEN');
if (csrfToken) {
config.headers['X-XSRF-TOKEN'] = csrfToken;
}
return config;
},
error => {
return Promise.reject(error);
}
);
五、CSRF防护的注意事项
Token的存储:不要将CSRF token存储在localStorage中,这可能导致XSS攻击窃取token。推荐使用HttpOnly的Cookie。
Token的时效性:应该为每个会话生成新的token,重要操作可以使用一次性token。
CORS配置:正确配置CORS策略,避免恶意网站发起跨域请求。
敏感操作二次验证:对于转账、改密等敏感操作,应该要求用户进行二次验证(如短信验证码)。
框架选择:优先使用成熟框架(如Spring Security、Django CSRF中间件)提供的防护机制,不要自己造轮子。
六、总结
CSRF攻击虽然原理简单,但危害巨大。通过本文的介绍,我们了解了多种防御手段:
- 验证Referer头是最简单的方式,但不够可靠
- CSRF Token是目前最主流的解决方案
- 双重Cookie验证适合前后端分离的应用
- 不同技术栈(Java/Node.js等)都有成熟的实现方案
在实际开发中,我们应该根据应用场景选择合适的防护策略,同时注意与其他安全措施(如XSS防护)配合使用。记住,安全是一个整体,不能只依赖单一防护手段。
最后提醒大家,安全无小事,特别是在金融、电商等涉及敏感数据的系统中,一定要重视CSRF防护,定期进行安全审计和渗透测试,确保系统安全可靠。
评论