前言
众所周知,Chrome在51版本时增加了SameSite属性,用于防止跨域携带Cookie引发的用户行为跟踪和CSRF攻击。
而最近两个月,越来越多的系统中会报出无法登录,无法鉴权,白屏,循环跳转等等等等问题,查看后大多数为后端接口报401。这个问题,大概率的起因是Chrome 80+版本后,Chrome将SameSite的默认值设为Lax导致,除非,你自己上线了Bug。
原因
要解决这样的问题,我们要先了解根源 SameSite属性有三:
- Strict: 任何跨域请求都不会携带Cookie,包括location.href
- Lax: 任何跨域请求都不会携带Cookie,不包括location.href
- None: 同时设置Secure为true时候,跨域请求会携带Cookie
这里要划个重点:跨域
而我们常见的Cookie写入操作,大多数都是由接口返回的Response Header中的Set-Cookie来触发写入的。
了解原因后,我们回到现在的鉴权问题,通过Chrome查看报401的接口中的Cookie信息,我们会发现根本没有把当前的Cookie带上去,而我们通过页面地址的方式直接打开当前请求,查看当前Application中的Cookie,我们会发现,这边的Cookie实际是存在的,但是SameSite为空,也就是没有设置。此时Chrome会取默认设置,也就是获取Lax方式来设置跨域Cookie,结果当然就是……不携带Cookie。
解决
个人不建议将chrome://flags中的SameSite相关验证改为Disabled,我们要面向未来,不能用关闭新特性的方式来规避已有问题。
Cookie无法携带的问题并不是不能解决,上文中我们看到,很关键的一点是:跨域请求,那么跨域是在什么情况下出现的呢? 假设如下几个方案:
- 1: A.com 访问 B.com/getData
- 2: subA.A.com 访问 subB.A.com/getData
分别来解析上面的方案:
1)如果A.com 访问 B.com/getData:这无疑是跨域访问,如果你非要这样子去请求,方案有两种:
- 使用Nginx或其他网关工具进行Proxy操作,将A.com/api/getData的转发至B.com/getData下,使跨域问题变为同域,也就不存在跨域问题了。
- 使用http auth也就是header auth方式进行,将令牌通过header的形式传输,不使用Cookie,那当然也就不存在Cookie中奇奇怪怪的问题了。
- 服务端set-cookie的时候,设置SameSite为None,同时设置Secure,便可以将cookie直接带上去了。
2)subA.A.com 访问 subB.A.com/getData:这牵扯到接口中set-cookie的domain设置的是多少,如果domain设置的为A.com,那两个之间的cookie是不存在跨域的,而如果domain设置的是subA.A.com,那则存在Cookie跨域,解决方式如1。
安全问题
那么另一个问题:这几种方法,安全么?
- 方案1是安全的,因为你的所有Cookie行为都是在网关层进行处理的,基本不会出现带给第三方的情况。
- 方案2如果被hook掉你的dns这些,的确是可以把你当前的鉴权相关的内容带给第三方的,安全这个就仁者见仁了。
- 方案3相当于把Google做的这些努力全部,刚回去了,那你说,安全么?
其他问题
- A.com已有Cookie,内部嵌入iframe接入B.com,B需要使用A的上下文,怎么处理?
使用解决方案2的延伸方法:
1:A.com在打开B.com时候,将当前的Header Auth信息传给B.com,然后在B中通过Header调用。
2:使用OAuth方案
使用解决方案3的延伸方法:
A.com在自动登录流程中,直接触发Set-Cookie操作,SetCookie时写入一个外部使用的outter-token,并设置其Secure为true,SameSite为None,在B.com调用时,通过outter-token获取上下文。
后续问题
iOS在13.4中也加入了Cookie与localStorage的相关处理,禁止三方Cookie,同时Google宣布要在2022年完全禁用第三方Cookie。
禁用第三方Cookie后,header auth的方案应该还是一个相对稳定的方向,如果可以的话,还是尽量使用header来进行处理吧。