JSON格式CSRF防御:从原理到实践全解析
在Web应用安全领域,跨站请求伪造(CSRF)一直是一种常见的攻击方式,随着前后端分离架构的普及,JSON格式数据交互逐渐成为主流,传统针对HTML表单的CSRF防御手段(如Token验证)在JSON场景下面临新的挑战,本文将从JSON-CSRF的攻击原理入手,分析其与传统CSRF的差异,并系统介绍针对性的防御策略,帮助开发者构建更安全的应用架构。
JSON-CSRF:被忽视的“灰色地带”
1 什么是JSON-CSRF攻击?
CSRF的核心是利用用户的登录状态,在用户不知情的情况下,诱使其浏览器向目标应用发送恶意请求,传统CSRF多针对HTML表单(如<form>提交),攻击者通过构造恶意页面,诱导用户点击按钮或自动提交表单,触发目标应用的接口请求。
而JSON-CSRF则是针对AJAX/JSON接口的攻击:攻击者构造恶意页面,通过fetch、XMLHttpRequest等API向目标JSON接口发送伪造请求(如修改用户信息、发起支付等),由于现代浏览器允许跨域请求,且JSON接口常用于敏感操作,JSON-CSRF的威胁性不容忽视。
2 JSON-CSRF与传统CSRF的差异
| 维度 | 传统CSRF | JSON-CSRF |
|---|---|---|
| 请求载体 | HTML表单(application/x-www-form-urlencoded) |
AJAX请求(application/json) |
| 请求头 | 默认不携带Content-Type或form-data |
常携带Content-Type: application/json |
| 传统防御手段 | Token验证、Referer验证等 | 部分Token手段失效(如隐藏Token无法通过JS获取) |
| 浏览器自动行为 | 会自动携带Cookie(含Session信息) | 同样会自动携带Cookie,但需跨域策略配合 |
3 JSON-CSRF的攻击场景示例
假设某银行转账接口支持JSON格式请求,参数为{"account": "attacker", "amount": 1000},且依赖Cookie中的sessionId验证用户身份,攻击者流程如下:
- 构造恶意HTML页面,内嵌如下JS代码:
fetch("https://bank.com/api/transfer", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ account: "attacker", amount: 1000 }) }); - 用户登录银行网站后访问恶意页面,浏览器自动携带
sessionId发送请求。 - 银服务器误认为是用户真实操作,完成转账攻击。
JSON-CSRF的核心防御策略
针对JSON-CSRF的攻击特点,防御需围绕“验证请求来源合法性”和“限制跨域请求”展开,以下是经过实践验证的有效方案:
1 双重Token验证:服务端与客户端协同
传统CSRF Token通过隐藏表单字段传递,但JSON请求的请求体(Body)通常由前端动态构造,且可能被中间层(如Nginx)或框架(如Spring Boot)自动解析,需采用“请求头+请求体验证”的双重机制。
实现步骤:
- 服务端生成Token:用户登录后,服务端生成随机CSRF Token,并存入Session(或Redis),同时返回给前端(可通过Cookie或响应体)。
- 前端携带Token:前端在发送JSON请求时,将Token放入请求头(如
X-CSRF-Token)和请求体(如csrfToken字段)。 - 服务端校验Token:服务端同时验证请求头和请求体中的Token是否与Session中的Token一致,且仅支持
application/json类型的请求。
示例代码(Spring Boot + Vue):
-
后端生成Token:
@GetMapping("/csrf-token") public ResponseEntity<Map<String, String>> getCsrfToken(HttpSession session) { String token = UUID.randomUUID().toString(); session.setAttribute("csrfToken", token); Map<String, String> response = new HashMap<>(); response.put("token", token); return ResponseEntity.ok(response); } -
前端携带Token:
// 获取Token(假设已从登录接口获取) const token = localStorage.getItem('csrfToken'); // 发送JSON请求 fetch("/api/transfer", { method: "POST", headers: { "Content-Type": "application/json", "X-CSRF-Token": token // 请求头Token }, body: JSON.stringify({ account: "target", amount: 500, csrfToken: token // 请求体Token }) }); -
后端校验Token:
@PostMapping("/api/transfer") public ResponseEntity<?> transfer( @RequestBody Map<String, Object> body, @RequestHeader("X-CSRF-Token") String headerToken, HttpSession session) { String sessionToken = (String) session.getAttribute("csrfToken"); String bodyToken = (String) body.get("csrfToken"); if (!sessionToken.equals(headerToken) || !sessionToken.equals(bodyToken)) { return ResponseEntity.status(403).body("CSRF Token验证失败"); } // 业务逻辑处理... }
优势:
- 同时验证请求头和请求体,避免单一途径被绕过(如中间层修改请求体)。
- Token与Session绑定,确保短期有效,增加攻击难度。
2 SameSite Cookie:浏览器内置的“防火墙”
SameSite是Cookie的一个属性,用于控制浏览器是否跨站发送Cookie,从根源上切断CSRF攻击的“认证依赖”。
SameSite取值:
- Strict:完全禁止跨站发送Cookie(如
SameSite=Strict),用户从外部链接访问时,Cookie不会被携带。 - Lax:允许部分跨站场景发送Cookie(如GET请求、POST跳转),适用于大部分场景(如
SameSite=Lax)。 - None:允许跨站发送Cookie,但要求必须同时配置
Secure(仅HTTPS),不推荐用于敏感接口。
实现方式(服务端设置Cookie):
- Java(Spring Boot):
@GetMapping("/login") public void login(HttpServletResponse response) { Cookie cookie = new Cookie("sessionId", "uuid"); cookie.setPath("/"); cookie.setHttpOnly(true); // 防止XSS窃取 cookie.setSecure(true); // 仅HTTPS传输 cookie.setAttribute("SameSite", "Lax"); // 设置SameSite response.addCookie(cookie); } - Node.js(Express):
res.cookie('sessionId', 'uuid', { path: '/', httpOnly: true, secure: true, sameSite: 'Lax' // 设置SameSite });
注意事项:
SameSite=Lax可防御99%的跨站场景,但对“跨站POST请求”(如JSON-CSRF)拦截有效。- 需确保用户浏览器支持
SameSite(Chrome 80+、Firefox 79+等主流浏览器已支持,旧版浏览器需降级处理)。
3 CORS策略:严格限制跨域请求
跨域资源共享(CORS)通过服务端响应头控制哪些域名可以访问接口,从“请求来源”层面拦截恶意跨域请求。
核心响应头:
- Access-Control-Allow-Origin:允许的跨域源(如
https://trusted.com),禁止使用(敏感接口场景)。 - Access-Control-Allow-Methods:允许的HTTP方法(如
GET, POST)。 - Access-Control-Allow-Headers:允许的请求头(如
Content-Type, X-CSRF-Token)。 - Access-Control-Allow-Credentials:是否允许携带Cookie(需配合前端
credentials: 'include')。
实现示例(Spring Boot):
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**") // 限制/api路径下的接口
.allowedOrigins("https://trusted-front.com") // 允许的可信域名
.allowedMethods("GET", "POST")
.allowedHeaders("Content-Type", "X-CSRF-Token")
.allowCredentials(true) // 允许携带Cookie
.maxAge(3600); // 预检请求缓存1小时
}
}
前端配合(AJAX请求):
fetch("/api/data", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ data: "test" }),
credentials: "include" // 必须携带Cookie


还没有评论,来说两句吧...