Blog · Loji44AboutTAGSRSS🔍SEARCH

1. SSOCAS

在企业应用开发中,经常会使用到单点登录技术(Single Sign-on)来处理企业的各个应用之间的共享登录场景。

通常,企业内部会有多个应用,每个应用都需要用户登录之后才能允许用户访问其内容。现实中,一般不会为每个应用都实现一套登录逻辑,因为这样做既费时、费力又难以维护。而是通过一个统一登录服务,让每个应用都接入这个统一登录服务,这个登录服务可以在各个应用之间共享登录态。即,当用户在其中一个应用登录成功之后,在继续访问企业中的其他任何应用的时候都不需要重新登录,从而达到提升用户体验的效果。

试想,如果某个企业拥有10个应用服务并且用户每天都会使用这10个服务,用户每访问一个应用都需要重新填写账号密码进行登录,用户体验是不是很差?

单点登录技术(SSO)就是为了解决这种场景而生的。用户在一次成功登录之后,只要本次登录未过期,用户就可以直接访问接入了同一个单点登录服务的其他应用,而不需要再登录一次。即,一处登录,处处访问

SSO是一种架构设计,CAS则是SSO的一种实现。CAS(Central Authentication Service)单点登录技术是 Apereo 的一个开源项目,该项目包含了CAS单点登录协议、CAS客户端以及CAS服务器等项目(CAS官网地址)。

2. CAS架构

cas_architecture

CAS Clients是各种接入了CAS单点登录服务的业务应用;CAS Server就是单点登录服务器。各个应用可以通过各种协议接入CAS服务器,例如CAS、SALML以及OAuth协议等(本文只介绍 CAS Protocol);CAS服务器支持多种用户认证信息数据源,例如LDAP、Database以及Active Directory等,我们可以根据实际场景来选择不同的数据源。

3. CAS协议(CAS Protocol)

CAS协议(CAS-Protocol-Specification)当前有三个版本:CAS 1.0、2.0以及3.0

  • CAS 1.0 —— 基础模式,仅提供WEB应用之间的单点登录;
  • CAS 2.0 —— 代理模式,可以用于WEB应用或者非WEB应用之间的单点登录,例如移动应用+WEB应用等;
  • CAS 3.0 —— 在兼容1.0和2.0的基础上,新增了其他更高级的特性。

这里只介绍CAS 1.0版本的协议原理。CAS 1.0协议的工作流程图如下图所示:

cas_web_flow_diagram

这里先介绍几个CAS登录流程中涉及的相关票据(Ticket)的概念:

  • TGT —— Ticket Granting Ticket,用户登录成功后,CAS服务器为用户签发的一个登录票据。TGT就是服务器存储的用户Session的ID。如果CAS服务器通过TGT可以查询出用户的Session信息,则说明该用户已经登录过了。
  • TGC —— Ticket Granting Cookie,TGT是存储在CAS服务器端的Session的ID,TGC则是存储在浏览器端的Cookie的Key。用户登录成功之后,每次访问CAS服务器时浏览器都会自动携带Cookie中的TGC(Cookie: TGC=TGT-12345678),这样CAS服务器就能拿到TGT从而获取到用户的会话信息。
  • ST —— Service Ticket,用户登录成功后,CAS服务器为该用户签发的访问某一个应用的票据。用户访问应用时,会在URL上携带这个ST票据,这个应用收到请求后会去找CAS服务器验证ST的有效性,ST有效则CAS服务器会向该应用返回该用户的会话信息。如果ST验证有效,则该应用相信该用户已经登录,允许其访问应用资源。

4. 理解CAS工作流程

我将上面的CAS工作流程翻译了一遍,做成比较直观的流程图以方便理解:

cas_work_flow.png

流程图有了,现在我们根据流程图一步步走一遍,加深理解。

现在,用户首次访问应用A:http://web.a.com

步骤1:用户通过浏览器访问应用A:http://web.a.com 。应用A收到请求,先检查Cookie(这里的Cookie是应用A的局部Cookie,而非CAS服务器的全局TGC)中有没有携带该用户的会话ID(局部会话JSSESION)。如果没有则说明用户未登录,应用A会302重定向到CAS服务器并在重定向的URL上携带自己的应用链接:http://cas.local.com/login?service=http%3a%2f%2fweb.a.com

步骤2:CAS服务器收到应用A的302跳转请求,先检查Cookie中是否包含TGC。如果没有TGC则说明用户未登录过,CAS服务器会向浏览器返回登录表单页面,让用户填写账号密码信息进行登录。

步骤3:用户填写登录账号密码,点击登录。CAS服务器会验证用户的账号密码正确性,如果正确,执行如下动作:

  • 生成用户的会话信息,存储在服务器缓存(例如Redis)中,TGT就是缓存会话ID(Key);
  • 通过Response向浏览器的cas.local.com域写入 Set-Cookie: CASTGC=TGT-12345678
  • 为用户签发一个用于访问应用A的ST票据。

最后,CAS会302跳转回应用A并将ST传递给应用A:http://web.a.com?ticket=ST-345678

应用A拿到ST票据之后,会向CAS服务器请求验证ST的有效性。若ST有效,应用A会创建该用户的局部会话,并将用户的局部会话Key写入浏览器Cookie(写入自己的域名web.a.com):Set-Cookie: JSSESION=ABC1234567 。这样在JSESSION过期之前,用户再访问应用A的时候,就不需要再跳转到CAS服务器确认了。

接下来,用户首次访问应用B:http://web.b.com

步骤1:应用B同样也会先检查Cookie(这里的Cookie是应用B的局部Cookie,而非CAS服务器的全局TGC)中有没有携带该用户的会话ID。因为用户还没在应用B创建过局部会话,所以应用B会同样跳转到CAS服务器:http://cas.local.com/login?service=http%3a%2f%2fweb.b.com

步骤2:由于用户在访问应用A的时候,CAS已经向浏览器写入了全局Cookie:CASTGC=TGT-12345678,所以应用B通过http://cas.local.com/login?service=http%3a%2f%2fweb.b.com跳转到CAS服务器时,浏览器会自动给cas.local.com域名携带上CASTGC=TGT-12345678这个Cookie给CAS服务器。CAS服务器发现请求中带有CASTGC=TGT-12345678这个Cookie,则会验证TGT-12345678是否有效(即是否能查到用户的Session),若CAS服务器验证TGT有效,则认为用户登录过了,不会再向用户返回登录表单页面,而是直接为用户生成一个用于访问应用B的ST票据。

步骤3:应用B接下来就跟应用A所做的事情一样了:验证ST的有效性,创建该用户的局部会话,并将用户的局部会话Key写入浏览器Cookie(写入自己的域名web.b.com):Set-Cookie: JSSESION=XYZ1234567 。同样在JSESSION过期之前,用户再访问应用B的时候,就不需要再跳转到CAS服务器进行确认了。

我们发现,用户在应用A完成登录之后,再继续访问应用B的时候完全就不需要再次登录。这是因为CAS服务器的全局TGC的存在,让用户轻松地完成了跨域(web.a.comweb.b.com两个域)的单点登录,一处登录,处处访问!

5. ST票据的安全性

目标应用在是否创建用户局部会话的判定关键在于ST票据的有效性。所以才会先去找CAS服务器验证这个ST是不是服务器颁发的、有效的。

ST票据是用户登录成功后,CAS服务器重定向到目标应用时携带在URL上的参数。这样很容易被攻击者拦截到这个参数。如果攻击者抢在用户之前先向CAS服务器验证了这个票据,那么用户将会验证失败,无法登录目标应用。

这时候可能CAS需要做一些安全的防范,比如在生成ST票据的时候使用多种参数组合,例如:随机数 + IP等来判断前来验证ST票据的主体是不是用户将要登录的目标应用,若不是则拒绝验证请求。

ST在验证成功之后,CAS服务器会向目标应用返回用户的信息,目标应用根据这些用户信息创建用户的局部会话。出于安全考虑,CAS服务器应该在验证完毕ST之后,删除ST在CAS服务器上的缓存,保证每次请求都生成新的ST票据。