前面在《单web应用系统登录设计》中,都是从一个小型独立的web系统角度做的登录设计。要么是借助三方账号系统的Oauth2.0能力,要么是作为一个子业务系统直接使用单点登录的能力。
今天这一篇文章所要总结讲述的,就是如何在一个多模块web系统中,构建用户登录能力的技术方案。
SSO单点登录设计
认识SSO机制
对于对于一个大型的系统,往往具备两个及以上的产品或子业务系统。将其中的用户登录逻辑抽离出来,统一在一个页面进行一次页面登录,就能同时在各个子业务系统中达到登录的效果。
打个比方,SSO 和我们去游乐园时购买的通票很像。
我们只要买一次通票,就可以玩所有游乐场内的设施,而不需要在过山车或者摩天轮那里重新买一次票。在这里,买票就相当于登录认证,游乐场就相当于使用一套 SSO 的公司,各种游乐设施就相当于公司的各个子业务系统。
同域下的单点登录
对于同域下的单点登录,因为cookie可以同域共享,因此可以通过共享会话的形式快速构建该能力。
即用户模块完成用户登录后,在一级域名下设置cookie, 用户在访问时业务模块时,携带该cookie,从共享session会话中获取用户登录凭证。
但在大型的web应用中,sessoin会话在不同模块中共享是不安全的。其他业务模块往往通过服务互信的方式来获取用户凭证。和token鉴权或oauth鉴权模式一样,仍然需要走到用户模块去进行鉴权。在SSO的术语中,提供鉴权的用户模块又可以叫CAS(Central Authentication Service)。下面,就梳理一下CAS的交互流程。
CAS (Central Authentication Service)
术语解释
TGT:Ticket Grangting Ticket
TGT 是 CAS 为用户签发的登录票据,拥有了 TGT,用户就可以证明自己在 CAS 成功登录过。TGT 封装了 Cookie 值以及此 Cookie 值对应的用户信息。当 HTTP 请求到来时,CAS 以此 Cookie 值(TGC)为 key 查询缓存中有无 TGT ,如果有的话,则相信用户已登录过。
TGC:Ticket Granting Cookie
CAS Server 生成TGT放入自己的 Session 中,而 TGC 就是这个 Session 的唯一标识(SessionId),以 Cookie 形式放到浏览器端,是 CAS Server 用来明确用户身份的凭证。
ST:Service Ticket
ST 是 CAS 为用户签发的访问某一 service 的票据。用户访问 service 时,service 发现用户没有 ST,则要求用户去 CAS 获取 ST。用户向 CAS 发出获取 ST 的请求,CAS 发现用户有 TGT,则签发一个 ST,返回给用户。用户拿着 ST 去访问 service,service 拿 ST 去 CAS 验证,验证通过后,允许用户访问资源。
CAS实现会话登录的步骤:
用户访问系统1的受保护资源,系统1发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数
sso认证中心发现用户未登录,将用户引导至登录页面
用户输入用户名密码提交登录申请
sso认证中心校验用户信息,创建用户与sso认证中心之间的会话,称为全局会话,同时创建授权令牌。
(该步骤即创建了TGC(全局会话cookie)/TGT(全局会话session)/ST(授权令牌)sso认证中心带着令牌跳转会最初的请求地址(系统1)
系统1拿到令牌,去sso认证中心校验令牌是否有效
sso认证中心校验令牌,返回有效,注册系统1
系统1使用该令牌创建与用户的会话,称为局部会话,返回受保护资源
用户访问系统2的受保护资源
系统2发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数
sso认证中心发现用户已登录,跳转回系统2的地址,并附上令牌
系统2拿到令牌,去sso认证中心校验令牌是否有效
sso认证中心校验令牌,返回有效,注册系统2
系统2使用该令牌创建与用户的局部会话,返回受保护资源
(这里的流程仅展示了子业务系统内访问自己的受保护资源的方式。对于子业务系统内访问其他业务系统的受保护资源,其实现原理及方案则需要引入Oauth2.0机制,这里不做细究。)
用户登录成功之后,会与sso认证中心及各个子系统建立会话,用户与sso认证中心建立的会话称为全局会话,用户与各个子系统建立的会话称为局部会话,局部会话建立之后,用户访问子系统受保护资源将不再通过sso认证中心,全局会话与局部会话有如下约束关系:
- 局部会话存在,全局会话一定存在
- 全局会话存在,局部会话不一定存在
- 全局会话销毁,局部会话必须销毁
第1点和第2点,因为局部会话都是由子业务系统向认证中心主动发起鉴权,所以说天然满足。但对于第3点,则需要一个通知机制,从认证中心向子业务系统发出。
CAS实现会话注销的步骤
- 用户向系统1发起注销请求
- 系统1根据用户与系统1建立的会话id拿到令牌,向sso认证中心发起注销请求
- sso认证中心校验令牌有效,销毁全局会话,同时取出所有用此令牌注册的系统地址
- sso认证中心向所有注册系统发起注销请求
- 各注册系统接收sso认证中心的注销请求,销毁局部会话
- sso认证中心引导用户至登录页面
因为这个通知机制,需要在认证中心额外存储ST与注册系统的映射关系,并通过异步消息队列完成通知。对应的业务系统也得维护这个局部会话的时效性。
CAS的技术能力
CAS采用客户端/服务端架构
CAS Client:
- 拦截子系统未登录用户请求,跳转至sso认证中心
- 接收并存储sso认证中心发送的令牌
- 与CAS Server通信,校验令牌的有效性
- 建立局部会话
- 拦截用户注销请求,向sso认证中心发送注销请求
- 接收sso认证中心发出的注销请求,销毁局部会话
CAS Server:
- 验证用户的登录信息
- 创建全局会话
- 创建授权令牌
- 与CAS Client通信发送令牌
- 校验CAS Client令牌有效性
- 系统注册
- 接收CAS Client注销请求,注销所有会话
统一网关+授权中心
对于一个使用微服务架构的大型系统来说,相比于独立一个CAS服务,构建一个API网关可能是一个更优雅的解决方案。将用户的认证鉴权统一到网关模块进行统一处理,
鉴权逻辑描述
- 当客户端第一次请求服务时,网关发现未携带token,向授权中心请求授权
- 客户端完成对用户进行信息认证(登录)
- 认证通过,将用户信息进行签名加密形成token,返回给客户端
- 客户端每次请求,都携带认证的token
-
发送给服务端的请求,首先经过网关,网关向授权中心发起鉴权,鉴权通过则放行。
该方案之于SSO,就犹如token鉴权之于session会话管理。该方案相较于CAS模式,有一个天然的优势,即同时支持多端的登录鉴权。
单设备登录设计
对于一些敏感应用,需要有一定的风控措施,例如说仅允许单设备登录。对应于web端,相当于仅允许一个登录凭证或sessionId有效,你如果换了浏览器或异地登录,系统则需要将以前的会话踢出,并警示风险。
在CAS模式下,这需要SSO认证中心内缓存的userId与ST的映射关系再添加上一些其他信息。例如将userId和全局sessionId一起作为key来检索ST, 则可以保证换了浏览器或主机的情况下可以识别到重复登录了,如果将urserId和ip一起作为key来检索ST,则可以保证同一时刻异地登录可以得到检测。
同理,网关token模式下,可以将userId和token/ip一起做key来检索用户登录凭证。而在App侧,单设备登录可以控制得更加精细。
当然,如果收集更多用户登录的环境信息或历史行为信息,可以更精准地对用户做风险控制。