最近某天,同事F突然找我:快来看用chrome后台进不去...xxx也是。
我立刻在我自己电脑试,很正常啊...跑过去他工位一看,确实进不去,登录框登陆后跳不过去页面,会继续返回登录页。换成edge,就正常了。基于是内部使用,就先换个浏览器将就吧。顺便说一下,是单点登陆。
然后我就回来,开始找为什么???大家都是chrome,我怎么就可以...除非是不一样的chrome。查了下我的版本,79.x.x.x,让小哥发下他的版本80.0.3987.132,再问xxx,也是80.0.3987.132。那就是了...
搜了下谷歌80新版,发现这次更新有关于cookie的描述:变更列表
Cookie default to SameSite=Lax
...
Reject insecure SameSite = None cookies
...
Cookie
先来理一理cookie。
怎么设置cookie
服务器端通过响应头设置cookie,前端可以通过document.cookie来设置:
const setCookie = function (key, value, options) {
if (arguments.length > 1 && !$.isFunction(value)) {
options = $.extend({}, options)
if (typeof options.expires === 'number') {
const days = options.expires
const t = (options.expires = new Date())
t.setMilliseconds(t.getMilliseconds() + days * 864e5)
}
return document.cookie = [
encodeURIComponent(key),
'=',
encodeURIComponent(value),
options.expires ? `; expires=${options.expires.toUTCString()}` : '', // use expires attribute, max-age is not supported by IE
options.path ? `; path=${options.path}` : '',
options.domain ? `; domain=${options.domain}` : '',
options.secure ? '; secure' : ''
].join('')
}
}
当我setCookie('anchen','good',{expires:7}) 后,可以在浏览器的cookie看到设置成功。
const getCookie = name => {
let arr
const RE = new RegExp(`(^| )${name}=([^;]*)(;|$)`)
if ((arr = document.cookie.match(RE))) {
return unescape(arr[2])
} else {
return ''
}
}
SameSite&&Secure
从上面引用的标题,提取了两个关键:SameSite和Secure
在Chrome 80中,Chrome会将cookie默认设置为SameSite=Lax。只有显式声明SameSite=None,才能加入不受限制的使用状态;Chrome 80的稳定版默认用于启用此功能。
那什么是SameSite??
SameSite是针对某些类型的跨站点请求伪造(CSRF)攻击的相当强大的防御措施。site是指有相同的domain.name
第三方Cookie:由当前a.com页面发起的请求的 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有3个值:
- None : 所有同域请求和跨栈请求浏览器都会发送 Cookie,这是 Chrome 80 之前的默认值。注意这里的跨栈请求是指 subrequest 比如加载一个图片资源。fetch 和 XHR 携带 Cookie 遵循 CORS 标准,可以参考 CORS 跨域发送 Cookie 一文。
- Strict : 只有同域请求(从设置 Cookie 的域发起的请求)浏览器才发送 Cookie。包括 subrequest,也包括顶级跳转(top-level navigations),也就是说只有从自己的网站发起的跳转请求或资源请求才发送 Cookie。
- Lax : 只有顶级跳转才跨域发送 Cookie,subrequest 不发送,这是 Chrome 80 之后的默认值。也就是说页面里的跨域图片不会发送 Cookie,但用户点击超链接跳转到其他域仍然会发送。
那什么是Secure
Secure设置的cookie可以从外部访问,前提是通过安全连接(即HTTPS)访问。它是布尔类型,如果不设置,就是insecure。画重点,要设置secure,需要https访问。
SameSite=None;Secure
怎么检测
samesite-sandbox.glitch.me/ ,如果每一行都为绿色证明已经在应用新的规则。
兼容
所以如果要看起來效果跟80前版本的chrome一样的话,除了设置SameSite=None外还需要设置 Secure=true。
真的吗,我又看到这个:
SameSite=None: Known Incompatible Clients :
Chrome的版本,从Chrome 51到Chrome 66(两端)。这些Chrome版本将拒绝带有“ SameSite = None”的Cookie。这也会影响旧版的Chromium衍生浏览器,以及Android WebView。
Android 12.13.2。之前的UC浏览器版本。较旧的版本将拒绝带有“ SameSite = None”的cookie。
Safari和MacOS 10.14上的嵌入式浏览器以及iOS 12上的所有浏览器的版本。这些版本将错误地将标记为“ SameSite = None”的cookie视为标记为“ SameSite = Strict”的cookie。
官方建议:服务端必须在下发 Set-Cookie 响应头时进行 User-Agent 检测,对这些浏览器不下发 SameSite=none 属性
所以,可以实现的方式是:
-
采用两套cookie,原来的cookie保持不变,再设置一个名字对应的 Cookie 项,设置
Secure和SameSite=None。后端如果拿不到第一个cookie,就拿第二个。(这个好理解,只要后端支持) -
统一把token放在header的authorization,就不存在跨域携带cookie的困扰了:
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。
此后,客户端每次与服务器通信,都要带上这个 JWT(JSON WEB Token)。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息
Authorization字段里面。或者,跨域的时候,JWT 就放在 POST 请求的数据体里面。
题外话:我的chrome升级到80+并没有这个特性。。。截至2020年3月份,谷歌其实只是对一小部分用户开放了这个新特性...然后我就去下了81的canary版本(也只有50%,但是有了这个新特性了。描述)
(ps:后来由于yq原因,chrome又把samesite回退了,但是这个也是早晚的事,还是先了解)
说说JWT
JSON Web Token:服务器认证后,生成一个JSON对象,发给用户。用户每次与服务端通信的时候,都要发回这个Json 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。
JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
JWT可防护CSRF攻击(CSRF攻击:说到底就是恶意网站利用你的cookie做恶意操作。),服务端代码在完成认证后,会在HTTP response的header中返回JWT,前端代码将该JWT存放到Local Storage里待用,或是服务端直接在cookie中保存HttpOnly=false的JWT。在向服务端发起请求时,用Javascript取出JWT(否则前端Javascript代码无权从cookie中获取数据),再通过header发送回服务端通过认证。总而言之,就是由前端自己用js获取到JWT再发送给服务端,这一步区别煜cookie-section模式的自动发送,所以可以防护CSRF攻击。
参考链接:
Chrome 80 跨域 Cookie 变化的影响和应对方案