本文目录导读:

跨域伪造请求(通常指CSRF,跨站请求伪造)是一种常见的Web安全威胁,要有效规避,需从服务端防护、前端配合及浏览器安全机制三个层面入手,以下是具体、可操作的规避方案:
核心原则:服务端必须验证请求的“真实性”
前端的一切措施(如设置Token)都只是辅助,服务端必须对每一个敏感操作(修改、删除、转账等)进行合法性校验。
使用 Anti-CSRF Token(最经典、最有效)
服务端生成一个随机、不可预测的Token,存储在用户的Session或Cookie中,每次前端发送敏感请求时,必须携带该Token(通常在请求头或请求体中),服务端验证Token是否匹配。
- 实现方式:
- 前端:从服务端获取Token(例如在页面加载时,嵌入到
<meta>标签或JavaScript变量中),在发起请求时(如fetch或axios)手动添加到请求头中。 - 服务端:验证请求头中的Token与Session中的Token是否一致。
- 前端:从服务端获取Token(例如在页面加载时,嵌入到
- 注意:Token必须与服务端Session绑定,并且不能通过Cookie自动发送(避免被攻击者利用Cookie自动携带的特性),Token放在
Authorization或自定义请求头中。
同源检查(Origin / Referer 验证)
服务端检查请求头中的Origin或Referer字段,判断请求是否来自允许的域名。
Origin字段(更推荐):由浏览器自动添加,只包含协议、域名和端口(不含路径),且对于POST请求几乎必定存在,服务端验证Origin是否在白名单中。Referer字段(备选):可能因浏览器隐私设置或被开发者禁用而丢失,可靠性较低。- 规则:如果是来自第三方域名(如
evil.com)的伪造请求,其Origin或Referer必然与你的站点不同,直接拒绝。
自定义请求头(如 X-Requested-With: XMLHttpRequest)
利用浏览器同源策略的一个特性:只有JavaScript发起的跨域请求(XHR/Fetch)才能添加自定义请求头,而通过HTML表单提交(伪造CSRF的主要手段)无法添加。
- 实现:前端所有AJAX请求(包括
fetch、axios)统一添加一个非标准但常用的请求头,例如X-Requested-With: XMLHttpRequest或你自定义的X-CSRF-TOKEN。 - 服务端:检查该头部是否存在且值正确,如果缺失或错误,则拒绝请求。
- 注意:这种方法依赖于简单请求和预检请求的区别,如果请求是简单请求(如
Content-Type: application/x-www-form-urlencoded的POST),浏览器不会发预检请求,此时自定义头部的有效性较弱(但依然比没有好),更可靠的做法是配合Token使用。
二次验证(重验证)
对于高敏感操作(修改密码、删除账号、转账大额资金),要求用户进行二次确认。
- 方式:弹出验证码(CAPTCHA)、输入当前密码、发送短信验证码或邮箱二次确认链接。
- 效果:即使CSRF攻击成功,攻击者也无法获取用户的验证码或密码,无法完成操作,这是最后一道防线。
设置 Cookie 的 SameSite 属性(现代浏览器的强力防御)
这是浏览器层面的防御机制,能自动阻止大多数跨站点请求中发送Cookie。
SameSite=Lax(推荐,但需注意兼容性):允许在用户点击链接(a标签)、GET表单提交等顶链导航时发送Cookie,但禁止在跨站点的POST表单提交、跨站点的Fetch/AJAX请求中发送Cookie,这个模式能覆盖绝大多数CSRF攻击场景(因为攻击者通常通过POST提交表单或发送AJAX请求)。SameSite=Strict:严格模式,完全禁止跨站点请求携带Cookie(包括点击链接),可能影响用户体验(如从邮件点击链接无法保持登录状态)。SameSite=None(需配合Secure,且浏览器显式同意):表示允许跨站点携带Cookie,但必须通过HTTPS,这通常用于需要跨域共享Cookie的场景(如SSO),但会显著增加CSRF风险。
最佳实践:对于登录态相关的会话Cookie,设置为SameSite=Lax;对于非关键但敏感的Cookie,可设置为SameSite=Strict,设置HttpOnly和Secure属性是必须的。
使用 JSON API 并强制 Content-Type: application/json
攻击者通过HTML表单(<form>)提交的数据,其Content-Type只能是application/x-www-form-urlencoded、multipart/form-data或text/plain。无法设置application/json。
- 实现:所有敏感API只接受
Content-Type: application/json,服务端验证请求头中的Content-Type。 - 效果:攻击者无法通过表单提交触发API调用,但要小心,如果API也接受
application/x-www-form-urlencoded(例如为了兼容性),则此方法无效。
避免使用 GET 请求进行修改操作
这是最基本的安全编码规范,所有修改、删除、写入操作必须使用POST、PUT、DELETE等非GET方法,因为GET请求的URL容易被嵌入到图片、链接中,且浏览器会直接发起,形成CSRF。
综合建议(最佳实践组合)
-
对于所有受登录保护的页面:
- 在服务端生成一个随机Token,存储在Session中。
- 在页面渲染时,将Token写入一个自定义请求头(例如
X-CSRF-Token)或JavaScript变量。 - 前端所有非GET请求(POST、PUT、DELETE)在发起时,都必须通过JavaScript读取这个Token,并添加到请求头中。
- 服务端验证请求头中的Token是否与Session中的Token匹配。
- 同时,将登录后的会话Cookie设置为
SameSite=Lax和HttpOnly。
-
对于全站的通用防御:
- 服务端统一验证
Origin或Referer头部(优先用Origin),如果请求来源不在白名单内,直接拒绝。 - 强制使用POST/JSON处理所有修改接口,并限制Content-Type。
- 服务端统一验证
-
对于极高安全要求的操作:
- 加上二次验证(验证码、密码、短信)。
绝对不能做的事(易犯错误)
- 使用Cookie来存储CSRF Token:绝不要把CSRF Token放在Cookie里,然后通过Cookie自动发送来验证,因为Cookie会被自动携带,攻击者也可以访问到(如果Cookie未设置同源策略),Token必须放在请求头或请求体中,手动发送。
- 依赖Referer或Origin做唯一验证:它们可能被篡改(虽然很难)或缺失(隐私模式、代理),它们应作为辅助防御,而非唯一防线。
- 忽略JSONP的GET请求:JSONP本身是GET请求,且允许跨域,如果JSONP接口存在且用于修改数据,则极易被CSRF利用,应避免使用JSONP处理写操作。
总结表格
| 防御措施 | 核心机制 | 防御强度 | 注意事项 |
|---|---|---|---|
| Anti-CSRF Token | 服务端生成随机token,前端手动携带 | 高 | 必须服务端验证;token不能存Cookie;需前端配合 |
| 同源检查 | 验证Origin/Referer | 中高 | Origin更可靠;可作为第二道防线 |
| 自定义请求头 | 利用表单无法添加自定义头部 | 中 | 依赖预检请求;最好配合Token |
| SameSite Cookie | 浏览器限制跨站发Cookie | 高(现代浏览器) | 需浏览器支持;注意Lax和Strict的兼容性 |
| 二次验证 | 重放攻击无法绕过 | 极高 | 影响用户体验;仅用于关键操作 |
| JSON API+Content-Type | 限制Content-Type为JSON | 中 | 需服务端严格校验;兼容性风险 |
核心思路:让攻击者无法构造一个“看起来像是用户自己发出的、且被服务端信任的合法请求”,防御组合拳远胜于单一措施。