avatar

目录
SSO 单点登录

简介

单点登录英文全称 Single Sign On,简称就是 SSO。它的解释是:在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统;即,一处登录,处处访问。

SSO

如图所示,图中有 4 个系统,分别是 Application1、Application2、Application3、和 SSO。Application1、Application2、Application3 没有登录模块,而 SSO 只有登录模块,没有其他的业务模块,当 Application1、Application2、Application3 需要登录时,将跳到 SSO 系统,SSO 系统完成登录,其他的应用系统也就随之登录了。这完全符合我们对单点登录(SSO)的定义。

技术实现

在说单点登录(SSO)的技术实现之前,我们先说一说普通的登录认证机制。

SSO

如上图所示,我们在浏览器(Browser)中访问一个应用,这个应用需要登录,我们填写完用户名和密码后,完成登录认证。这时,我们在这个用户的 Session 中标记登录状态为 yes(已登录),同时在浏览器(Browser)中写入 Cookie,这个 Cookie 是这个用户的唯一标识。下次我们再访问这个应用的时候,请求中会带上这个 Cookie,服务端会根据这个 Cookie 找到对应的 Session,通过 Session 来判断这个用户是否登录。如果不做特殊配置,这个 Cookie 的名字叫做 jsessionid,值在服务端(server)是唯一的。

同域下的单点登录

一个企业一般情况下只有一个域名,通过二级域名区分不同的系统。比如我们有个域名叫做:a.com,同时有两个业务系统分别为:app1.a.com 和 app2.a.com。我们要做单点登录(SSO),需要一个登录系统,叫做:sso.a.com。

我们只要在 sso.a.com 登录,app1.a.com 和 app2.a.com 就也登录了。通过上面的登陆认证机制,我们可以知道,在 sso.a.com 中登录了,其实是在 sso.a.com 的服务端的 Session 中记录了登录状态,同时在浏览器端(Browser)的 sso.a.com 下写入了 Cookie。那么我们怎么才能让 app1.a.com 和 app2.a.com 登录呢?这里有两个问题:

  • Cookie 是不能跨域的,我们 Cookie 的 domain 属性是 sso.a.com,在给 app1.a.com 和 app2.a.com 发送请求是带不上的。
  • sso、app1 和 app2 是不同的应用,它们的 Session 存在自己的应用内,是不共享的。

SSO

那么我们如何解决这两个问题呢?针对第一个问题,SSO 登录以后,可以将 Cookie 的域设置为顶域,即: a.com,这样所有子域的系统都可以访问到顶域的 Cookie。我们在设置 Cookie 时,只能设置顶域和自己的域,不能设置其他的域。比如:我们不能在自己的系统中给 baidu.com 的域设置 Cookie。

Cookie 的问题解决了,我们再来看看 Session 的问题。我们在 SSO 系统登录了,这时再访问 app1,Cookie 也带到了 app1 的服务端(Server),app1 的服务端怎么找到这个 Cookie 对应的 Session 呢?这里就要把 3 个系统的 Session 共享,如图所示。共享 Session 的解决方案有很多,例如:Spring-Session。这样第 2 个问题也解决了。

同域下的单点登录就实现了,但这还不是真正的单点登录。

不同域下的单点登录

同域下的单点登录是巧用了 Cookie 顶域的特性。如果是不同域呢?不同域之间 Cookie 是不共享的,怎么办?

这里我们就要说一说 CAS 流程了,这个流程是单点登录的标准流程。

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

CAS 架构

CAS 架构

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

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_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 验证有效,则该应用相信该用户已经登录,允许其访问应用资源。

CAS 工作流程阐述

下面我们结合 CAS 协议流程图来阐述一下整个过程,假设我们的应用架构如下图所示:

CAS 工作流程

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

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

步骤 2:由于用户在访问应用 A 的时候,CAS 已经向浏览器写入了全局 Cookie:CASTGC=TGT-12345678,若 CAS 服务器验证 TGT 有效,则认为用户登录过了,不会再向用户返回登录表单页面,而是直接为用户生成一个用于访问应用 B 的 ST 票据。

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

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

ST 票据的安全性

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

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

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

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

上图是 CAS 官网上的标准流程,具体流程如下:

总结

  • 单点登录(SSO 系统)是保障各业务系统的用户资源的安全;
  • 各个业务系统获得的信息是,这个用户能不能访问我的资源;
  • 单点登录,资源都在各个业务系统这边,不在 SSO 那一方。 用户在给 SSO 服务器提供了用户名密码后,作为业务系统并不知道这件事。SSO 随便给业务系统一个 ST,那么业务系统是不能确定这个 ST 是用户伪造的,还是真的有效,所以要拿着这个 ST 去 SSO 服务器再问一下,这个用户给我的 ST 是否有效,是有效的我才能让这个用户访问。
文章作者: Tim
文章链接: http://w3ctim.com/post/31927831.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Tim

评论