本文目录导读:

跨站请求伪造(CSRF)是一种常见的Web安全漏洞,攻击者诱导用户点击链接或访问页面,在用户不知情的情况下,利用用户已登录的身份向目标网站发送恶意请求(如转账、改密),要有效拦截CSRF攻击,通常采用以下后端防御策略(前端无法独立防御CSRF):
核心防御策略(推荐组合使用)
使用 Anti-CSRF Token(最常用、最有效)
- 原理:服务器为每个会话或每个表单生成一个随机、不可预测的Token,并将其嵌入到表单的隐藏字段或请求头中,服务器验证请求时,检查Token是否合法。
- 实现方式:
- 表单嵌入:在HTML表单中生成
<input type="hidden" name="_csrf_token" value="随机值">。 - 请求头携带:通过JavaScript从Cookie或页面变量中读取Token,附加到Ajax请求的
X-CSRF-Token或X-XSRF-TOKEN头中(常见于前后端分离项目,如Spring Security的默认实现)。
- 表单嵌入:在HTML表单中生成
- 关键点:Token必须与用户会话绑定,且攻击者无法伪造(不能通过跨域请求获取)。
- 注意:不要在GET请求中使用Token(GET请求可能被第三方网站直接触发),只对POST/PUT/DELETE等修改性请求进行验证。
同源检测(Origin / Referer 头验证)
- 原理:检查HTTP请求头中的
Origin(推荐)或Referer字段,判断请求是否来自本站(同源)。 - 实现:
- 验证
Origin头是否在允许的白名单中。Origin是安全头,由浏览器自动添加(仅跨域请求时)。 Origin为空(某些情况下,如HTTPS→HTTP请求可能丢失),可回退验证Referer。
- 验证
- 局限性:
Referer可能被用户隐私设置禁用,也可能由攻击者在某些漏洞下伪造(低概率)。Origin相对可靠,但某些旧浏览器不支持。
- 建议:作为辅助手段,与Token结合使用。
设置 SameSite Cookie 属性(浏览器原生防御)
- 原理:通过设置Cookie的
SameSite属性,控制浏览器在跨站请求时是否附带Cookie(默认不携带)。 - 属性值:
Strict(严格):所有跨站请求都不发送Cookie(最安全,但用户体验差,如从第三方网站点击链接到本站,Session丢失)。Lax(宽松,现代浏览器默认值):仅允许“顶级导航”GET请求(如点击链接、地址栏输入)发送Cookie,表单POST、Ajax等跨站请求不发送,这是平衡安全与体验的最佳选择。None:禁用SameSite防御,但必须同时设置Secure(仅HTTPS)。
- 用法(以Spring Boot为例):
response.addHeader("Set-Cookie", "JSESSIONID=xxx; SameSite=Lax; HttpOnly")。 - 注意:仅依靠SameSite还不够,因为有少数请求(如某些GET链接)仍可能携带Cookie,且旧浏览器不支持。
验证 HTTP 请求方法
- 原理:强制要求状态修改操作(增、删、改)使用POST、PUT、DELETE等安全方法,拒绝GET请求执行修改(因为攻击者容易构造
<img>或<a>标签触发GET)。 - 注意:不是防御CSRF的充分条件,但能消除大部分简单攻击向量。
二次验证(对高风险操作)
- 原理:对敏感操作(如修改密码、转账、删除账户)不依赖Token,强制用户输入当前密码、短信验证码或图形验证码。
- 效果:攻击者无法自动完成,极大提高难度。
- 适用场景:金融、账户管理等关键功能。
哪些方法“不能”拦截CSRF?
- 仅依赖前端(如JS防点击、验证Referer):攻击者可构造请求绕过前端。
- 使用Session在URL中传参:能被直接看到,且易泄露(如URL被分享到社交媒体)。
- 单纯依赖IP/UA/时间戳:这些信息可以被伪造或获取。
最佳实践组合(推荐)
对所有状态修改请求(POST/PUT/DELETE)生成并验证 Anti-CSRF Token(服务端会话绑定)。 2. 在全局过滤器/中间件中验证 Origin 头(或 Referer),作为第二道防线。 3. 将所有Session Cookie设置为 SameSite=Lax(或Strict),并 HttpOnly。 4. 拒绝GET请求做状态修改(RESTful规范遵循)。 5. 对高风险操作(如转账、改密)强制二次验证。
代码示例(简单Spring Boot防护)
生成Token(后端):
@RequestMapping("/form")
public String showForm(HttpServletRequest request, Model model) {
String csrfToken = UUID.randomUUID().toString();
request.getSession().setAttribute("CSRF_TOKEN", csrfToken);
model.addAttribute("csrfToken", csrfToken);
return "form";
}
验证Token(拦截器):
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if ("POST".equalsIgnoreCase(request.getMethod())) {
String sessionToken = (String) request.getSession().getAttribute("CSRF_TOKEN");
String requestToken = request.getParameter("_csrf_token");
if (sessionToken == null || !sessionToken.equals(requestToken)) {
response.sendError(403, "CSRF token mismatch");
return false;
}
}
return true;
}
- 最推荐:Anti-CSRF Token + SameSite=Lax + Origin验证,三者结合抵御绝大多数CSRF攻击。
- 核心思想:让攻击者无法伪造合法的请求凭证(Token)或无法附带关键身份凭证(Cookie)。
- 注意:CSRF防御是后端责任,前端只能辅助(如通过JS读取Cookie中的Token并添加到请求头),但不能作为唯一防线。