作者:来自 vivo 互联网服务器团队- Wang Fei
单点登录作为公共组件,在各个公司内部被各个系统所广泛使用,但是在使用过程中我们会遇到各种各样的问题,其中循环登录问题就是一个比较经典的问题。本文主要分析单点登录和权限系统设计的基本原理,然后结合实际案例来分析循环登录的原因,并给出具体的解决办法。
一、单点登录简单介绍
1.1 基本概念
一个公司内部可能存在多个系统,如果每一个人在使用不同系统的时候都需要重新登录,那么会做大量系统登录切换、耗费比较多的精力去管理账号和密码,那么有没有办法在一个公司内部的所有系统只需要一次登录验证,后续使用其他系统的时候不用重复登录就可以直接使用呢,这就是单点登录要解决的问题。
单点登录英文全称 Single Sign On(SSO),允许用户一次登录即可访问多个应用程序或系统,无需为每个应用程序或系统分别输入认证凭据,便可在其他所有系统中得到授权,无需再次登录。
1.2 基本实现原理
- 用户登录:用户在任何一个应用程序或系统中进行身份验证,并提供他们的凭据。
- 认证系统验证:该凭据被发送到认证系统进行验证。如果凭据有效,则认证系统会为用户生成数字签名的令牌(如 Token 或 Ticket)。
- 令牌分发:认证系统将令牌返回给应用程序或子系统。
- 应用程序或系统授权:应用程序或系统使用令牌验证用户的身份,并授权其访问相应资源或服务。
- 跨域系统访问:用户可以通过同一令牌访问多个跨域应用程序或系统,而无需重复进行身份验证。
二、循环登录问题
在某一天我们登录一个内部系统时,突然出现了循环登录问题,前端页面不断刷新,提示“重定向次数过多问题”。
打开前端调试功能, 我们会发现确实存在大量重定向请求的问题:
那么平时登录没问题的系统为什么突然间就循环登录呢?并在页面上提示的解决方法“尝试删除您的 Cookie 操作”,按照这个操作以后,确实系统又可以跳转到登录页面正常进行登录了,这又是什么原因?下面我们将逐一分析。
三、从一次正常登录流程说起
上述是一个通用的系统权限管控和单点系统认证的标准流程:
- 用户第一次访问时,在浏览器输入 https://aaa.x.y 回车
- 权限系统进行拦截,判断用户是否登录,这里主要是通过是否有登录信息判断,如果没有登录,权限系统会帮我们跳转到单点登录系统,弹出用户登录页。
- 用户填写用户名、密码,单点登录系统进行认证后,将登录状态写入 SSO 的 session。
- SSO 系统登录完成后会给我们的系统生成一个 Token ,然后跳转到我们的系统,同时将 Token 作为参数传递给我们的系统。
- 我们系统拿到 Token 后,从后台向 SSO 发送请求,验证 Token 是否有效。
- 验证通过后,我们系统将记录顶级域下的 Cookie 信息。
Connection: keep-alive
Content-Length: 0
Date: Wed, 25 Oct 2023 08:29:43 GMT
Location: http://aaa.x.y/console/login/auth?redirectUrl=http://aaa.x.y/
optrace: xx.xx.xx.xx:80/302 <- -
Server: nginx
Set-Cookie: token=fakdfajdfdjfdjkfaldfjk'afafjasfasfa; Max-Age=86400; Expires=Thu, 26-Oct-2023 08:29:43 GMT; Domain=x.y; Path=/; HttpOnly
一个公司内部一般情况下只有一个域名,通过二级域名区分不同的系统。比如我们有个域名叫做:x.y ,同时有两个业务系统分别为:app1.x.y 和 app2.x.y。SSO 登录以后,可以将 Cookie 的域设置为顶域,即 x.y ,这样所有子域的系统都可以访问到顶域的 Cookie,实现单点登录功能。
四、循环登录产生的根本原因
那么为什么会不断循环登录呢?
(1)从跳转记录来看,我们发现重新刷新页面以后,重定向到了权限系统,并且 Request Headers 中的 Cookie 信息没有传对应的 Token 信息。
(2)跳转到权限系统过后,再跳转到本系统的时候,已经获取到了对应的 Token 信息,但是在 Set-Cookie 信息的时候,出现了一个警告。
警告的具体内容为:
大致意思是:这次执行 Set-Cookie 操作被阻止了,原因是这个 Cookie 不是通过安全的连接进行传输的,我们这次访问确实使用了 HTTP 进行,本应该通过设置 Secure 属性来覆盖对应的 Cookie。
这里的 Secure 属性是 Cookie 的一个属性,Secure 属性是说如果一个 Cookie 被设置了 Secure = true,那么这个 Cookie 只能用 HTTPS 协议发送给服务器,用 HTTP 协议是不发送的,而我们查看上面标注位置的下一个 Login 请求确实没有传 Cookie 信息,从而继续进行用户是否登录校验,进入死循环过程, 可以看下面的示意图:
五、清除浏览器缓存的底层原理及解决方法
5.1 清除浏览器缓存的底层原理
我们可以看到循环登录以后,会在浏览器页面上提示 xxx.x.y 重定向次数过多,尝试清除 Cookie 信息,那清除 Cookie 信息以后,是不是真的就可以解决这个问题呢,我们尝试着清除浏览器缓存,确实可以解决这个问题,那清除浏览器缓存来解决循环登录问题的底层原理是什么呢,其实质就是将 Cookie 删除,其他域名设置 Cookie 上的 Secure 属性也就一并删除了,从而使用 HTTP 域名进入重新登录流程,可以正常设置 Cookie 信息。
5.2 其他解决办法
方法一:使用 HTTPS 的方式进行访问
现实使用中,我们无法控制其他 HTTPS 访问的具有相同顶级域名的服务不去设置 Cookie 的 Secure 属性,因而我们在后面使用的过程中还是会遇到这个问题,那么有没有一种彻底的解决办法能够避免这个问题再次出现,我们前面已经分析,之所以我们在开始使用 HTTP 能够进行正常访问,然后突然间不能正常访问了,就是因为已经被 HTTPS 设置了的 Cookie 信息是无法被 HTTP 重新设置的,从而拿不到 Cookie 信息。那么就出现了第一种解决办法,使用 HTTPS 的方式进行访问,即使其他服务设置了 Cookie 的 Secure 属性,用 HTTPS 仍然能够成功设置 Cookie 和获取 Cookie。
方法二:在 Cookie 信息中新设置一个 newToken
以上从 HTTP 转为 HTTPS 访问的方法在用户主动找我们反馈时是能够告诉它切换为 HTTPS 访问的,但是如果对于一些没有主动找我们反馈的用户,其实是无法解决,可能丧失这个用户造成用户流失的情况,那么我们在用户不进行切换的情况下是否能够解决这个问题。
同一个公司内部接入的权限系统是一种底层公共能力,为了保证单点登录,其实用户信息的读取都是通过同一个 Cookie 参数(比如叫 Token )读取的,那么在其他域名设置了公共 Cookie 参数的 Secure 属性而影响到 HTTP 登录的时候,我们可以给服务新增加一个 Cookie 参数 newToken 去解决。
六、扩展: Cookie 的端口不隔离性
本文所阐述的问题,出现的背景是有两个基本前提的:一是为了保证单点登录,两个域名属于同一个顶级域名,权限系统中关于用户信息的校验都是通过同一个 Cookie 属性去读取的;第二个是 HTTPS 设置了顶级域名的 Cookie 信息的 Secure 属性,然后使用 HTTP 访问会导致循环登录。有些开发者可能会有这样一个疑问,那就是 HTTPS 我们一般开通的端口是 443 , HTTP 我们一般开通的端口是 8080 ,为啥不从端口上进行区分同一个 Cookie 属性从而避免干扰呢?
这个在 Cookie 规范(RFC 6265)中有所描述,那就是 Cookie 不提供通过端口进行隔离的,也就是说如果一个 Cookie 可以被一台服务器上的运行在某一个端口上的一个服务所读取,那么也可以被这台服务器上运行在另外一个端口上的服务所读取;如果一个 Cookie 可以被一台服务器上的运行在某一个端口上的一个服务所写入,那么也可以被这台服务器上运行在另外一个端口上的服务所写入。
七、总结
本文从实际开发过程中遇到的循环登录问题入手,分析了由于设置 Secure 属性导致使用 HTTP 访问网页无法保存 Cookie 信息从而导致循环登录的根本原因,也给出了其他两个解决此种问题的方案,对于其他开发人员解决权限系统循环登录问题具有一定的借鉴意义。
参考资料:
单点登录实现思路及方案
Sessions don’t work in Chrome but do in IE
8.5. Weak Confidentiality