cookie登录_关于我因为登录失败开始探究Cookie这档事

caee1fdf295db3c53d67eb8015da65e4.png

写在前面

最近小枫在工作时遇到一个问题,经过几天的思索和探究终于找到了问题所在,觉得有点价值便写了这篇文章记录下来,分享给热爱学习、乐于思考的各位,希望每个遇到相同情况的人可以通过阅读这篇文章得到解答。

话不多说,开始吧!

问题

小枫最近在开发一个平台,这里统称为A平台;A平台由两个部分构成:

  • 前端部分
  • 后端部分

可以看出,这是前后端分离开发的模式,前端和后端的正式环境都为:http://bin.ruofee.cn(题外话:SN战队很棒了,希望明年再度捧起LPL的荣光)。PS:本篇文章中的域名都是虚构的,如有雷同,纯属巧合。(-.-)

因为公司还有许多平台,考虑到用户信息的安全性,需要登录平台统一进行登录状态管理,因此也有个统一登录平台: http://login.ruofee.cn,这里统称为Login平台

9d925189797dd3de1b522279f23c7927.png
登录流程

平台的登录流程如下:

  1. 浏览器打开A平台,首先进行用户状态判断:判断是否存在auth_token(auth_token是Login平台设置到浏览器中的cookie,用于登录状态保持),如果auth_token不存在则表示A平台未进行登录操作(当然也有可能进行过登录操作,但cookie已经过期),通知浏览器跳转到Login平台进行账号密码登录,如(4);如果auth_token存在则表示A平台已经进行过登录操作,进行下一步操作;
  2. A平台后端接口根据业务提供了一个接口:http://bin.ruofee.cn/api/validate,用于验证auth_token是否有效,如果有效则返回用户的个人信息,如果无效则通知浏览器跳转Login平台重新登录,如(3)。/validate接口的逻辑很简单,直接访问Login平台提供的验证接口(http://login.ruofee.cn/auth)进行auth_token验证。
  3. 浏览器不存在auth_token或者auth_token失效时都会跳转到Login平台进行账号密码登录,登录成功时Login平台会将新的auth_token设置为cookie,保存在浏览器中,用于保持当前的登录状态;

以上就是平台登录的大致流程,可以看出,平台如果想接入统一登录服务,关键的点在于A平台前端在访问A平台后端时,是否可以自动带上Login平台设置的cookie(auth_token),从而进行后面的auth_token校验流程

那么来进行简单的分析:

Login平台在登录成功时,浏览器通过Response Headers中的Set-Cookie进行cookie设置:

# Login平台登录成功时设置cookieSet-Cookie: auth_token=xxx; domain=ruofee.cn; path=/;

从Set-Cookie的结构可以看出:

  • auth_token的domain为http://ruofee.cn,而不是login.ruofee.cn;
  • path为/;

因为auth_token设置的domain为http://ruofee.cn,所以访问http://ruofee.cn或是http://ruofee.cn的子域名时都将会自动带上auth_token。但由于同源策略对cookie的限制,分为以下两种情况

  1. 同源:请求将会自动带上对应的cookie,无需做其他设置;

2. 非同源:需要做跨域处理,在Response Headers做以下配置:

# Response HeaderAccess-Control-Allow-Origin: 前端Origin # 注意,这里不能为*,如果设置为*将不会带上cookie
Access-Control-Allow-Headers: 允许接收的Request Headers # 根据需要进行设置
Access-Control-Allow-Credentials: true

Ajax中需要把withCredentials设置为true,如下:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://example.com/', true);
xhr.withCredentials = true;
xhr.send(null);

MDN中对Access-Control-Allow-Credentials的解释如下:

Access-Control-Allow-Credentials头 工作中与 XMLHttpRequest.withCredentials或Fetch API中的 Request()构造器中的 credentials 选项结合使用。Credentials必须在前后端都被配置(即the Access-Control-Allow-Credentials header 和 XHR 或Fetch request中都要配置)才能使带credentials的CORS请求成功。

进行以上设置之后便可以自动带上cookie了!

地址协议HostPort
http://bin.ruofee.cnHTTPhttp://bin.ruofee.cn80
http://bin.ruofee.cn/apiHTTPhttp://bin.ruofee.cn80

从上表可以看出,A平台前端和后端属于第一种情况:满足同源策略;因此从理论上来说,A平台前端请求A平台后端接口时,会自动带上cookie(auth_token)。而事实上确实也成功了,小枫因此决定吃一顿麦当劳奖励一下自己。(^__^)

为了方便开发,小枫决定将平台前端部署到本地(总不能在服务器上开发吧),开启服务:localhost:8080。

地址协议HostPort
http://localhost:8080HTTPlocalhost8080
http://bin.ruofee.cn/apiHTTPhttp://bin.ruofee.cn80

从上表可以知道,本地开启的前端服务与线上的后端服务存在跨域,因此需要参考上面讲的非同源情况,设置跨域相关Headers;在全部设置完毕之后,本地开发一切正常,cookie能顺利带上,小枫加快步伐,逐步完成开发。

这时候读者肯定就郁闷了,说好的问题呢?莫急,接下来进入正题!

就在2020年5月份的某一天,小枫如往常一样打开本地的前端服务,准备开始一天的忙碌,却突然一直重复登录;打开chrome的devtools查看http请求发送情况才发现,本地前端请求接口时(http://bin.ruofee.cn/api/validate),auth_token没有自动带上,因此后端判断前端平台为未登录状态,于是重定向到Login平台进行登录,Login平台登录成功跳转回本地前端,再次访问接口(http://bin.ruofee.cn/api/validate),auth_token没有自动带上,于是再次重定向到Login平台进行登录...

小枫挠头,开始一步一步进行检查,发现问题卡在流程中的(5),虽然登录成功了,返回的Response Headers如下:

Set-Cookie: auth_token=xxx; domain=ruofee.cn; path=/;

Response Headers很正常,但是请求后端接口时没有自动带上cookie,小枫陷入思考,发现有两种可能:

  1. cookie没有设置到浏览器中;
  2. cookie设置成功,但因为一些原因未在接口请求时自动带上;

为了先验证cookie是否成功设置到浏览器中,小枫先清除了chrome中所有的cookie(从零开始嘛),在本地进行登录操作,此时平台一直重复登录,接着打开线上的A平台,发现A平台并未跳转到Login平台,说明此时Login平台的auth_token已经设置到浏览器中。所以可以得出结论:cookie设置成功,但因为一些原因未在接口请求时自动带上。从这个方向出发进行思索和探究,终于找到了原因!

这里我们先科普cookie的几个属性:

属性描述默认值
domainDomain 指定了哪些主机可以接受 Cookie。如果不指定,默认为 origin,不包含子域名。如果指定了Domain,则一般包含子域名当前origin或者父origin当前origin
pathPath 标识指定了主机下的哪些路径可以接受 Cookie任意值当前路径
SameSiteSameSite Cookie 允许服务器要求某个 cookie 在跨站请求时不会被发送,(其中 Site 由可注册域定义),从而可以阻止跨站请求伪造攻击(CSRF)。1. None。浏览器会在同站请求、跨站请求下继续发送 cookies,设置为None时需要Secure同时设置为true才生效;
2. Strict。浏览器将只在访问相同站点时发送 cookie;3. Lax。与 Strict 类似,但用户从外部站点导航至URL时(例如通过链接)除外。 在新版本浏览器中,为默认选项,Same-site cookies 将会为一些跨站子请求保留,如图片加载或者 frames 的调用,但只有当用户从外部站点导航到URL时才会发送。如 link 链接
默认为空,但部分浏览器为Lax
Expires/Max-Agecookie的生命周期session
Secure标记为 Secure 的 Cookie 只应通过被 HTTPS 协议加密过的请求发送给服务端true/false默认为空
HttpOnlyJavaScript 中的Document.cookie API 无法访问带有 HttpOnly 属性的cookie;此类 Cookie 仅作用于服务器true/false默认为空

看了上面的Cookie属性表,聪明的同学应该已经知道原因了~原因就出在SameSite:

SameSite为Strict或是Lax时,通过Ajax进行cross-site的接口请求时,将不会自动带上cookie;SameSite的值默认为空,但部分浏览器默认为”SameSite=Lax“小枫通过上网查阅资料,终于在Chrome 80版本的更新日志中找到原因:

Chrome 80: February 4, 2020 Updates to cookies with SameSite
Starting in Chrome 80, cookies that don’t specify a SameSite attribute will be treated as if they were SameSite=Lax. Cookies that still need to be delivered in a cross-site context can explicitly request SameSite=None. Cookies with SameSite=None must also be marked Secure and delivered over HTTPS. To reduce disruption, the updates will be enabled gradually, so different users will see it at different times. We recommend that you test critical sites using the instructions for testing.

中文翻译为:

Chrome 80: February 4, 2020 更新Cookie的SameSite
从Chrome 80开始,未指定SameSite属性的Cookie将被视为SameSite = Lax。仍然需要在跨站点中传递的Cookie可以设置为SameSite = None。具有SameSite = None的Cookie也必须设置为Secure,并通过HTTPS传送。为了减少中断,将逐步启用更新,因此不同的用户将在不同的时间看到它。我们建议您按照测试说明来测试关键站点。

所以在2020年5月之前跨站的cookie仍能正常携带,而在2020年5月之后Chrome更新之后,Chrome 80将Cookie的SameSite默认设置为Lax,因此本地部署的平台在访问跨站的线上后端接口时,cookie将不再可以自动在Headers中带上。

这里再贴一下SameSite=Lax的作用:

当SameSite=Lax时,浏览器只在访问相同站点时发送 cookie,但用户从外部站点导航至URL时(例如通过链接)除外。 在新版本浏览器中,为默认选项,Same-site cookies 将会为一些跨站子请求保留,如图片加载或者 frames 的调用,但只有当用户从外部站点导航到URL时才会发送。如 link 链接

事实上,如果是跨站点设置cookie,Chrome甚至会阻拦Set-Cookie生效

使用Express在线上服务器搭建一个简单的Node服务器,并提供一个设置cookie的接口:

app.use('/cookie', (req, res) => {res.append('Set-Cookie', 'cookie=test_cookie; domain=ruofee.cn; path=/;');res.send('success');
});

在解决了跨域问题之后,在本地搭建的前端平台访问该线上接口(http://bin.ruofee.cn/api/cookie),从chrome的devtools - network中查看Response Headers的Set-Cookie,发现有着这么一段话:

This Set-Cookie didn't specify a "SameSite" attribute and was defaulted to "SameSite=Lax" and was blocked because it came from a cross-site response which not the response to a top-level navigation. The Set-Cookie had to have been set with "SameSite=None" to enable cross-site usage.

中文翻译为:

当Set-Cookie中的”SameSite“没有设置值时,默认为”SameSite=Lax“,并且因为Set-Cookie来自于一个跨站点的响应,导致Set-Cookie被阻拦。如果需要跨站点设置cookie,Set-Cookie必须设置为”SameSite=None“。

757c2a9a77c61ee2605be00fc7fd5f0c.png
chrome浏览器提示

这里再科普一下cross-site和cross-origin的区别:

  • cross-site,意为跨站;site指的是ETLD+1(有效顶级域名左边加一个子域名,例如http://ruofee.cn即为一个ETLD+1),若是两个url的site不同,则表示跨站;
  • cross-origin,意为跨域;origin是协议头、主机名、端口的合并,因此若是两个url协议头、主机名、端口中有一个不相同则表示跨域;

贴一篇总结cross-site、cross-origin的文章:

Understanding "same-site" and "same-origin"​web.dev

总结

小枫遇到的本地环境重复登录的根本原因在于:Chrome 80将SameSite的默认值修改为Lax,导致cookie无法在跨站的情况下发送。而Chrome之所以对cookie作出调整是出于安全性的考量,具体可以参考这篇文章:

back2wild:即将到来的Chrome新的Cookie策略​zhuanlan.zhihu.com c92be7c9a045fc02a5b68a41d59dda41.png

SameSite=Strict/Lax使用户基本杜绝CSRF攻击,并且避免因为cookie可以跨站发送而导致用户行为被追踪。

总而言之,Cookie默认设置”SameSite=Lax“是浏览器的行为,是Chrome 80的一次更新;Chrome以推动者的身份进行这项改动,或许在今后,所有的浏览器都将会跟进,并完善web安全,让用户真正拥有隐私,又或许在今后,Chrome从屠龙少年变成恶龙,走上IE的道路……

解决方案

  1. 通过代理或是修改Host文件的方式,将本地前端地址的ETLD+1修改为http://ruofee.cn;cookie设置为”SameSite=Lax“时不能跨站发送Cookie,因此只要避免跨站即可解决问题;
  2. 本地搭建一个转发服务器,将本地前端发送的请求转发到线上后端服务;同样避免跨站,本地的转发服务器和本地前端平台的site都是localhost,因此不存在跨站现象;
  3. 更换浏览器;当前只有Chrome 80默认修改了SameSite的值,其他浏览器仍然保持原样,因此不存在cookie不能跨站发送的情况;
  4. 升级协议为HTTPS,并将SameSite设置为none;SameSite设置为“none”时,Secure必须设置为“true”,也就是说协议必须为HTTPS

结尾

完美撒花,感谢大家的阅读,以上就是《关于我因为登录失败开始探究Cookie这档事》。

”长按点赞可以一键三连哦!“

(^__^)


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部