CSRF 防护中 SameSite 策略详解

1 人参与

提到CSRF防护,很多开发者会立刻想到CSRF Token。这没错,Token验证是服务器端的坚实防线。但现代浏览器其实已经为我们提供了一件内置的“软猬甲”——那就是Cookie的SameSite属性。它直接在请求发起的源头,为那些携带身份凭证的Cookie加上了“情境锁”,从根本上改变了跨站请求的默认行为。

SameSite策略的三种模式:从宽松到严格

Lax:平衡之选

  • 导航请求(如点击链接)会被发送。
  • 跨站的POST请求、通过iframe/script/img发起的请求,Cookie将被阻止。

这是Chrome等浏览器目前的默认值。它巧妙地保护了大多数危险的POST操作(比如表单提交),同时又保证了用户体验:用户从搜索结果或社交媒体点击链接进入你的网站时,登录状态依然存在。想象一下,你从技术博客的链接点进GitHub,如果登录态没了,得多恼火?Lax模式解决了这个问题。

Strict:最严苛的堡垒

任何跨站(跨域)请求都不会携带该Cookie。即使是用户主动点击的链接,从外部网站跳转过来时,也会以“未登录”状态着陆。这提供了最高级别的CSRF防护,但牺牲了一定的用户体验。它通常适用于对安全性要求极高的金融或管理后台操作。

None:回到过去

Cookie将在所有上下文中发送,包括跨站请求。但这里有个关键前提:必须同时设置Secure=True,即Cookie只能通过HTTPS传输。这是为了确保在开放传输中不泄露敏感信息。如果你的应用需要被嵌入在第三方网站的iframe中(比如某些社交组件或支付插件),就必须使用SameSite=None; Secure

一个常被忽略的陷阱:顶级导航与子资源请求

SameSite的判定逻辑核心在于“站点”(Site),而非“源”(Origin)。站点是eTLD+1,例如docs.google.commail.google.com属于同一站点(google.com),但a.github.iob.github.io却是不同的站点(因为github.io是公共后缀)。这有时会带来意想不到的结果。

更微妙的是“顶级导航”(Top-level navigation)与“子资源请求”的区别。浏览器对“用户是否明确发起了跨站点请求”有自己的判断。一个典型的误用场景是:你的单页应用(SPA)通过JavaScript的fetchXMLHttpRequest向同站点的另一个子域发起API请求。如果这个请求是跨域的(CORS),并且你希望携带Cookie,即使子域相同,如果Cookie设置为SameSite=Strict,请求也会失败。因为浏览器认为这是由脚本发起的子资源请求,而非用户点击链接的顶级导航。

实践中的部署与降级策略

将现有应用的Cookie全部迁移到SameSite=LaxStrict,绝非简单地修改配置。你需要一个清晰的审计清单:

  • 识别所有依赖跨站Cookie的功能,如第三方登录回调、被嵌入的组件、跨子域的单点登录(SSO)。
  • 为这些特殊用例的Cookie单独设置SameSite=None; Secure,而将普通的会话Cookie设置为LaxStrict
  • 实施客户端降级检测。可以在前端脚本中检查document.cookie是否包含某个测试Cookie,如果不包含,则提示用户或回退到基于Token的验证流程。

安全从来不是银弹。SameSite Cookie策略是一道极其有效的边界防护,但它不能替代服务器端的CSRF Token验证。真正的纵深防御,是在浏览器用SameSite锁上第一道门的同时,在服务器端用Token验证再设一道关卡。当攻击者费尽心机绕过浏览器的限制,自以为成功伪造了请求时,等待他的将是服务器冷冰冰的Token验证失败响应。

参与讨论

1 条评论
  • 星图测绘师

    SameSite=Lax 默认值确实香,不用改代码就防住了大部分。

    回复