导读:本文介绍了一套基于 CAS 实现单点登录的方案,详尽介绍了单点登录、会话续期、单点注销功能的逻辑实现。
文|丁亚博
网易云商高级应用开发工程师
随着业务规模的增长,公司的业务系统越来越多,逐渐出现一个公司多个业务系统的现象。在业务前期,每个业务系统由于自身的业务特点分别拥有了各自的用户数据,随着业务规模不断增长,业务之间的关联性越来越多,一个用户可能会同时使用多个业务系统。此时就会出现诸多困扰,例如业务账号体系不通,用户首先要记录各个业务应用的用户名、密码,并在进入不同业务系统时都需要拿着对应的账号密码进行重新登录,势必会给用户带来很大的困扰。
业务增长,即单业务系统发展成多系统组成的应用群,其复杂性应该由系统内部承担,而不是用户。无论 Web 系统内部多么复杂,对用户而言,都是一个统一的整体,也就是说,用户访问 Web 系统的整个应用群与访问单个系统一样,登录/注销只要一次就够了。
一次登录,应用集群保持用户登录状态,必然需要建立统一的用户管理中心,进行统一的认证登录,即我们所说的单点登录。在讲解单点登录之前,我们先了解下传统单系统登录的会话机制。
会话机制
传统的单系统登录,一般基于 cookie-session,用户在输入正确用户信息进行登录后,系统会生成会话信息,并将会话信息的 ID 保存在浏览器的 cookie 中,后续用户再次通过浏览器发起请求时,浏览器会在请求中携带 cookie 信息访问系统,系统通过 cookie 获取会话信息 ID,并根据信息 ID 找到对应存储的用户 session,以此获取用户信息,从而校验登录状态。
单系统登录解决方案的核心是 cookie,cookie 携带会话 ID 在浏览器与服务器之间维护会话状态。但cookie是有限制的,cookie 无法跨域,浏览器在发送 http 请求时会自动携带与该域匹配的 cookie,而不是所有域名下 cookie 。因此,我们需要一种新的登录方式来实现多系统应用跨域登录,这就是下文即将介绍的基于 CAS 实现的单点登录。
CAS
CAS 是 Central Authentication Service 的缩写,即中央认证服务,其结构体系包含一个 CAS-SERVER跟多个 CAS-CLIENT 模块。
CAS-SERVER :负责完成对用户的认证工作 , 需要独立部署 , CAS Server 会处理用户名 / 密码等凭证(Credentials) 。
CAS-CLIENT:负责处理对客户端受保护资源的访问请求,需要对请求方进行身份认证时,重定向到 CAS Server 进行认证。CAS Client 与受保护的业务系统应用部署在一起,一般以过滤器或者拦截器的方式保护业务系统资源。
具体交互如下图展示:
CAS 通过客户端向服务端定向认证以及令牌(Ticket)转换用户信息大大提升了用户数据访问与获取的安全性。接下来我们将基于其理论从单点登录、会话续期、单点注销三部分进行具体实现。
单点登录
单点登录包含一个统一的认证中心(以下简称 SSO 服务),其提供登录界面由用户输入用户信息。
SSO 服务在接收到用户信息后,创建用户全局会话,并根据用户想访问业务系统的唯一标识创建一个与用户信息关联的登录令牌(令牌要保证随机性以及时效性), 最后携带该登录令牌重定向至业务系统处。
业务系统拿到令牌后,通过 SSO 服务提供的 API 获取用户信息,并根据用户信息建立业务系统局部会话。
当局部会话创建完成后,用户再次访问业务系统时,业务系统将直接根据其内部局部会话判断用户登录状态,将不再与 SSO 服务进行交互确认用户登录状态。另外考虑业务系统局部会话的建立都是依赖 SSO 服务的全局会话,因此我们需要保证全局会话的有效期时长大于其内部所有局部会话的有效期时长。
在上述单点登录流程中,我们可以发现业务系统在生成局部会话的时候均会携带业务标识访问 SSO 服务,其目的主要是便于 SSO 服务在为各个业务系统生成登录令牌的时候,记录全局会话以及与各个业务系统局部会话的映射关系,为便于说明,我们通过下图来查看确认全局、局部会话整体关系。
讲到这里,估计读者会有疑问,用户全局会话时长与局部会话时长该如何保持,是否会出现全局会话失效,局部会话有效的场景,这种场景一旦存在将导致用户无法切换进入其他系统,导致单点登录失效。
首先我们需要考虑这种场景是否会存在,从上文介绍中,我们可以了解到用户只有在首次访问集群中的业务系统时(即访问业务的局部会话不存在时),才会通过浏览器与 SSO 服务进行交互,一旦建立局部会话,浏览器将不再与 SSO 服务进行访问交互。如果用户长时间进行访问业务系统 A,业务系统 A 的局部会话时长将不断进行延长,SSO 服务由于长时间没有与浏览器进行交互,全局会话势必会失效过期,因此该场景存在,那我们该如何避免呢?
接下来将给大家介绍一种会话续期机制,以此保证全局会话永远在局部会话之后过期。
会话续期
会话是基于用户通过浏览器访问系统生成的,因此会话续期应该基于用户访问行为。基于系统并发量以及用户访问行为考虑,我们显然不能在每次访问业务系统时直接续期会话,因为如此操作势必会浪费服务器性能。我们应该依赖用户访问行为制定一个时间节点来进行续期。
这里我们给大家提供一个续期规则,以供参考与交流:基于局部会话过期时间的 1/2 进行设置,即用户在访问业务系统时,业务系统首先通过请求携带的 cookie 获取用户局部会话,并确认用户当前局部会话过期时间是否小于设定局部会话有效时间的 1/2,若小于,则进行业务系统续期。
通过在单点登录流程中建立的全局会话-局部会话关联关系,我们了解到全局会话包含用户信息、各个业务系统与登录令牌的关联关系,局部会话包含登录令牌、用户信息。因此每次局部会话续期的时候,业务系统可以携带用户信息和登录令牌通过 SSO 服务提供的 API 向全局会话进行续期,具体流程示意图如下。
单点注销
单点注销逻辑与会话续期逻辑基本一致,业务系统收到注销请求后,首先关闭局部会话,并通过SSO 服务提供的 API,携带用户信息以及登录令牌请求 SSO 服务注销全局会话。全局会话根据用户信息获取所有的局部会话信息,并依次调用各个业务系统的注销接口,进行局部会话注销。
在实际的业务需求中,跨域系统登录往往是在单系统登录之后出现的,如果按照以上方案做适配的话,显然需要变更登录页地址,即将单系统登录页地址改变成 SSO 服务提供的登录页。改变登录页意味着用户要更改登录习惯,显然不合理。另外基于用户访问行为,用户会出现重复登录的情况,即在登录后不退出的情况下,再次进行登录,重复登录会产生单用户多全局会话的情况,这样会影响会话续期以及单点注销流程。因此我们需要根据实际情况进行能力拓展。
业务需求拓展
保留原有登录系统, 通过局部会话打通全局会话
保留原有登录模式,用户依旧通过业务系统提供的登录页建立用户局部会话,后由业务系统创建用户信息关联的登录令牌,并携带登录令牌重定向至 SSO 服务,SSO 服务接收到登录令牌后,通过业务系统提供的 API 接口,将登录令牌转换为用户信息,并存入全局会话中,如此用户再想切换其他系统时,即可通过全局会话生成其他系统的局部会话信息。
用户多点登录时,自动注销上次登录会话
考虑用户登录习惯,一个用户往往在一个浏览器进行登录后,在不注销的情况下,会在另外一个浏览器进行再次登录,这样就会造成一个用户产生多个全局会话,上文介绍到我们注销流程跟会话续期流程均是通过用户信息寻找全局会话的,如果一个用户信息有多个全局会话,势必会影响注销流程跟会话续期流程。因此我们需要在用户再次登录时注销上一次登录信息,即清除上一次生成的全局会话以及局部会话信息。
需要注意的点
当全局会话建立后,用户首次访问业务系统前端资源时,由于前端资源包含多个 ajax 请求,所以会出现并行请求业务系统后端接口的情况,业务系统接收到前端请求后,发现用户局部会话不存在,会重定向至 SSO 服务获取用户登录令牌,此时就会出现并行重定向 SSO 服务获取登录令牌生成局部会话的行为。SSO 服务在全局会话保存业务系统的登录令牌是以最后生成的为准,业务方也以最后获取的登录登录令牌为准,由于网络传输以及服务响应时间不同,SSO 服务保存的用户登录令牌与业务方保留的登录令牌可能会出现不一致情况。通过上面的情况,用户的登录令牌是全局会话与局部会话之间的桥梁,登录令牌不一致,将会导致会话无法续期,单点注销功能失效。
这里给出一种解决方案,SSO 服务要保证在并发请求中同一业务系统针对同一用户获取的登录令牌一致。具体实现方案,SSO 服务在业务系统请求获取登录令牌的时候进行加锁,并将生成的登录令牌放入到缓存中,给一个较短时效(3s),在缓存未失效前,业务系统并发请求生成的登录令牌均相同。另外值得注意的是,业务系统在生成局部会话后均会在浏览器上生成 cookie,多次并行请求也会生成多个相同名称不同 value 值 的cookie,所以这里也需要保证 cookie 值相同,我们可以将 cookie 的 value 值设定为登录令牌的 MD5 加密值。
总结
本文介绍了一套基于 CAS 实现单点登录的方案,详尽阐述了单点登录、会话续期、单点注销功能的逻辑实现,并基于业务需求拓展介绍了一种通过局部会话打通全局会话的登录方案,以及保证用户多处登录仍能实现唯一会话的方法。另外由于篇幅原因,本文未介绍与用户单点登录关联的权限效验,这个在开发单点登录的时候也是必不可少的。开发一款 SSO 的产品,在功能上、性能上和安全上都必须有更加完备的考虑,现以此文与大家一起共勉。
作者介绍
丁亚博,网易云商高级应用开发工程师,平台架构组核心成员,目前负责云商平台体系搭建、开发工作,致力于云商平台、内部中间件建设。