SameSite属性踩坑记一次搞懂跨站请求的安全边界

Code°文瑞 安全 阅读 1,331
赞 22 收藏
二维码
手机扫码查看
反馈

我的写法,亲测靠谱

说实话,SameSite这个东西刚出来的时候我也挺懵的,各种属性值看得眼花缭乱。现在做了这么多年,算是摸清楚了其中的门道。我现在的做法很简单粗暴:大部分情况下都用 Strict,只有少数特殊场景才考虑 Lax。

SameSite属性踩坑记一次搞懂跨站请求的安全边界

// 服务端设置Cookie的通用模板
app.use((req, res, next) => {
  // 登录相关的敏感操作用Strict
  res.cookie('session_id', sessionId, {
    secure: true,
    httpOnly: true,
    sameSite: 'strict', // 关键就在这里
    maxAge: 3600000
  });
  
  // 非敏感操作可以用Lax
  res.cookie('user_pref', preferences, {
    secure: true,
    httpOnly: true,
    sameSite: 'lax',
    maxAge: 86400000
  });
  
  next();
});

为什么我偏爱 Strict?因为它真的能最大程度防止 CSRF 攻击。虽然可能会导致跨站跳转时丢失会话,但安全比便利更重要。而且现在的用户习惯已经改变了,很少有人会开多个浏览器标签页同时操作不同站点。

这几种错误写法,别再踩坑了

我见过太多人的 SameSite 写法都是有问题的,最常见的是这几种:

错误写法一:大小写搞错

// 错误!首字母必须大写
res.cookie('token', value, {
  sameSite: 'lax' // 小写lax是错的!
});

// 正确写法
res.cookie('token', value, {
  sameSite: 'Lax' // 首字母大写
});

我第一次踩这个坑的时候调试了整整一个下午,Chrome 控制台居然不报错,害得我以为是别的地方出了问题。记住:Strict、Lax、None 都要首字母大写,而且区分大小写。

错误写法二:用了 None 却没配 Secure

// 错误!sameSite: 'none' 必须配合 secure: true
res.cookie('tracking_id', id, {
  sameSite: 'None',
  httpOnly: true
  // 缺少 secure: true
});

// 正确写法
res.cookie('tracking_id', id, {
  sameSite: 'None',
  secure: true, // 必须加上
  httpOnly: true
});

这个错误更坑,浏览器会直接拒绝设置这样的 Cookie,而且没有任何错误提示。我当时查资料才发现,Chrome 从某个版本开始强制要求 SameSite=none 必须配合 Secure,否则就不让你设置 Cookie。

错误写法三:盲目追求兼容性,全设为 None

// 错误!这不是解决问题的方法
res.cookie('all_cookies', data, {
  sameSite: 'None',
  secure: true
});

有些老开发者看到旧版浏览器兼容性问题,就想当然地把所有 Cookie 都设为 None。这样做等于完全放弃了 CSRF 防护,风险极大。正确的做法是根据业务需求选择合适的策略。

实际项目中的坑

我在一个电商项目中遇到过一个很奇怪的问题。支付页面是从第三方支付网关跳转回来的,结果用户登录状态直接丢了。查了半天才发现是 SameSite 设置的问题。

当时的设置是这样的:

// 出问题的代码
res.cookie('auth_token', token, {
  sameSite: 'Strict',
  secure: true,
  httpOnly: true
});

Strict 的机制是完全阻止跨站请求携带 Cookie,但支付回调是典型的跨站场景。后来我改成这样解决:

// 解决方案:根据不同场景动态设置
function setAuthCookie(res, token, isCrossSite = false) {
  if (isCrossSite) {
    res.cookie('auth_token', token, {
      sameSite: 'Lax', // 允许简单的跨站GET请求
      secure: true,
      httpOnly: true
    });
  } else {
    res.cookie('auth_token', token, {
      sameSite: 'Strict',
      secure: true,
      httpOnly: true
    });
  }
}

但这种动态设置也有风险,如果判断逻辑不严谨,可能会被攻击者利用。所以我现在更倾向于采用统一的策略,通过 URL 参数等方式传递必要的信息,而不是放宽 Cookie 的限制。

还有个小细节要注意:在开发环境测试时,localhost 和 127.0.0.1 是不同的源,所以 SameSite 的限制同样适用。我以前经常在本地调试时遇到莫名其妙的问题,后来才知道是因为这个。

几个需要注意的细节

HTTPS 下才能正常使用 SameSite=none,这是 Chrome 的强制要求。我见过有人在 HTTP 环境下测试 SameSite=none,结果怎么都设置不成功。

另外,移动端浏览器的支持情况可能跟桌面端不一样。iOS Safari 在某些版本上有兼容性问题,建议用 caniuse 查一下目标用户的浏览器分布情况。

还有一个经验:在设置 Cookie 时,我习惯同时指定 domain、path、secure、httpOnly 和 sameSite,一个都不能少。这样可以避免因为某个参数缺失导致的安全问题。

// 我的标准 Cookie 设置模板
res.cookie('user_session', sessionData, {
  domain: '.yoursite.com', // 明确指定域名
  path: '/',               // 路径范围
  secure: true,           // HTTPS only
  httpOnly: true,         // 防 XSS
  sameSite: 'Strict',     // CSRF 防护
  maxAge: 3600000         // 过期时间
});

性能方面的考量

SameSite 设置本身对性能影响很小,但它会影响浏览器发送 Cookie 的时机。Lax 模式下,跨站 GET 请求也会携带 Cookie,这意味着每次跳转都可能传输 Cookie 数据。

如果你的 Cookie 很大,这种频繁传输会造成带宽浪费。我在一个项目中就把大体积的用户配置信息拆分成多个小 Cookie,敏感数据用 Strict,非敏感数据用 Lax,这样既能保证安全又能优化传输效率。

以上是我关于 SameSite 的一些实战经验总结,重点还是那句话:能用 Strict 就别用 Lax,能用 Lax 就别用 None。安全永远是第一位的,那些所谓的兼容性和便利性,往往没我们想象的那么重要。如果有更好的实现方式,欢迎评论区交流讨论。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论