Jimliu


一只刚上路的前端程序猿


cookie SameSite

背景

1.我们业务系统有被第三方系统嵌套使用,通过Node端下发cookie来实现一键登录。

2.Chrome 80.0中将SameSite的默认值设为Lax。

解释

SameSite详解

SameSite Cookie,防止 CSRF 攻击

因为 HTTP 协议是无状态的,所以很久以前的网站是没有登录这个概念的,直到网景发明 cookie 以后,网站才开始利用 cookie 记录用户的登录状态。cookie 是个好东西,但它很不安全,其中一个原因是因为 cookie 最初被设计成了允许在第三方网站发起的请求中携带,CSRF 攻击就是利用了 cookie 的这一“弱点”,如果你不了解 CSRF,请移步别的地方学习一下再来。

当我们在浏览器中打开 a.com 站点下的一个网页后,这个页面后续可以发起其它的 HTTP 请求,根据请求附带的表现不同,这些请求可以分为两大类:

  1. 异步请求(不会改变当前页面,也不会打开新页面),比如通过 script、link、img、iframe 等标签发起的请求,还有通过各种发送 HTTP 请求的 DOM API(XHR,fetch,sendBeacon)发起的请求。

  2. 同步请求(可能改变当前页面,也可能打开新页面),比如通过对 a 的点击,对 form 的提交,还有改变 location.href,调用 window.open() 等方式产生的请求。

这些由当前页面发起的请求的 URL 不一定也是 a.com 上的,可能有 b.com 的,也可能有 c.com 的。我们把发送给 a.com 上的请求叫做第一方请求(first-party request),发送给 b.com 和 c.com 等的请求叫做第三方请求(third-party request),第三方请求和第一方请求一样,都会带上各自域名下的 cookie,所以就有了第一方 cookie(first-party cookie)和第三方 cookie(third-party cookie)的区别。上面提到的 CSRF 攻击,就是利用了第三方 cookie 。

SameSite=Strict:

严格模式,表明这个 cookie 在任何情况下都不可能作为第三方 cookie,绝无例外。比如说假如 b.com 设置了如下 cookie:

1
2
Set-Cookie: foo=1; SameSite=Strict
Set-Cookie: bar=2

这种模式下你在 a.com 下发起的对 b.com 的任意请求中,foo 这个 cookie 都不会被包含在 Cookie 请求头中,但 bar 会

SameSite=Lax:

If the value is “Lax”, thecookie will be sent with same-site requests, and with “cross-site”top-level navigations, as described in Section 5.3.7.1.

上面规范已经说了,如果 SameSite 值是 Lax, 那么在发送同站请求的时候会带上 Cookie。那么跨站请求会不会带呢?上面说了这块规范在5.3.7.1中有写,我们看看这节内容相关的描述:

developers may set the “SameSite” attribute in a “Lax”enforcement mode that carves out an exception which sends same-sitecookies along with cross-site requests if and only if they are top-level navigations which use a “safe” (in the [RFC7231] sense) HTTPmethod.

宽松模式,比 Strict 放宽了点限制:假如这个请求是我上面总结的那种同步请求(改变了当前页面或者打开了新页面)且同时是个 GET 请求(因为从语义上说 GET 是读取操作,比 POST 更安全),则这个 cookie 可以作为第三方 cookie。比如说假如 b.com 设置了如下 cookie:

1
2
3
Set-Cookie: foo=1; SameSite=Strict
Set-Cookie: bar=2; SameSite=Lax
Set-Cookie: baz=3

当用户从 a.com 点击链接进入 b.com 时,foo 这个 cookie 不会被包含在 Cookie 请求头中,但 bar 和 baz 会,也就是说用户在不同网站之间通过链接跳转是不受影响了。但假如这个请求是从 a.com 发起的对 b.com 的异步请求,或者页面跳转是通过表单的 post 提交触发的,则 bar 也不会发送。

SameSite=None

cookie会像当前模式一样可以被第三方网站读取

注意当SameSite=None 必须同时添加 Secure, 而且 HTTPS是必须的。

1
2
Set-cookie: key=value; SameSite=None; Secure
Set-cookie: key=value; Secure

影响

因为我们网站嵌在第三方系统内(iframe),对于我们是不可见的,客服反馈打开系统跳转到登录页,但是我们自己访问却很正常。

在我本机使用他们系统时也正常。

所以让客户打开 chrome://flags/#same-site-by-default-cookies

将sameSite by default cookies 设置为disable。一切正常了。

所以就定位到问题为 sameSite 默认被设置为lax导致iframe页面请求不携带原有cookie。

解决

系统内cookie为express从服务端写入。

express changelog

express在4.17.0版本中 res.cookie()支持设置 sameSite为none

1
res.cookie('name', 'tobi', { domain: '.example.com', path: '/admin', sameSite: 'none', secure: true })

注意该方式必须在https环境下进行!!

更新

之前在系统内写的cookie都已经是sameSite:’None’且secure为true了。

但是express-session 种的session key 没有做该操作。所以查阅express-session的文档, 在1.17.0版本中新增对SameSite=None的支持

1
2
3
4
5
6
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
cookie: { secure: true, sameSite: 'none' }
}))

设置结束以后cookie中的session key消失了。

删除secure cookie被种上,sameSite:None 也没问题。

但是根据上文,sameSite:None 必须与secure同时使用。

翻看express-session源码查找问题。

找到isSecure方法来进行判断是否可以为secure:true。

虽然我们系统时https的 但是 req.headers[‘x-forwarded-proto’]却是http。

因为我们系统使用nginx做反向代理了,如果是用 nginx 做了反向代理,那默认情况下是取不到的,因为 nginx 到 node 始终是 HTTP 的。
有一种方式是通过一个特定的 header 字段来处理,nginx 做反向代理的时候加上这个字段,node 里获取就可以了。

方案:

1
2
3
4
5
location = /index.js {
proxy_set_header X-Forwarded-Proto https;
proxy_pass http://127.0.0.1:$node_port$request_uri;
proxy_redirect off;
}

添加header: proxy_set_header X-Forwarded-Proto https;
解决问题。

最近的文章

webpack优化打包速度

新搭了一套前端开发代码框架,使用Typescript。 webpack相关配置如下 123456789101112131415161718192021{ test: /\.(j|t)sx?$/, loader: 'ts-loader', include: SRC_PATH …

于  【webpack】 继续阅读
更早的文章

React Native 移动APP架构升级

之前一篇文章总结了关于APP的技术选型及开发过程中遇到的各种坑点。由于时间紧迫,所以开发第一期以做完需求和熟悉React Native为目标。 APP上线后我进行总结思考,开发APP过程中需要有哪些点需要优化。 这是画的架构图 移动端组件 埋点 Bundle拆分 热更新服务 一、移动端 …

于  react-native 继续阅读