小土刀

SSO 单点登录指南

单点登陆作为在网络应用开发中非常常见的实践,有必要深入理解其实现机制。


更新记录

  • 2016.06.28: 初稿

简介

当企业内部业务变多时,尤其是在平台化规范化的过程中,不同系统、不同应用的帐号需要互通,简单来说就是『一次登录,处处畅通』,即所谓的『单点登录』。具体到实现方式,可以参考 QQ 的做法。主要分两类,一类是针对第三方的接入,主要是通过 OAuth 协议进行授权,另一类是诸如 QQ 空间这样的第一方服务,就通过 SSO 来实现单点登录的功能。

我们来细化一下具体的场景,在整个 SSO 过程中,参与的有:

  • 多个用户
  • 多个应用
  • 一个认证中心

这里有两个关键点:

  1. 应用不处理用户的登录,所有的登录都在认证中心进行
  2. 认证中心通过后,不同应用通过认证中心返回的信息来进行下一步处理

在认证中心进行认证的方式不一定是常规的邮箱或手机号,也可以有新浪、QQ、微信等帐号的第三方登录,甚至是手机动态密码或扫码等方式。

不同的方案有很多,这里列举比较出名的几个:

  • OpenAM 项目的前身是 OpenSSO(被 Oracle 收购后便关闭,这家公司不是一次两次做这种事儿了),好在 ForgeRock 公司在 2010 宣布会继续开发,也就是现在的 OpenAM 项目(换名字是因为 Oracle 拥有 OpenSSO 这个名字,你说损不损…)。支持的功能很多,按照官网的说法是『The only “all-in-one” access management solution that includes Authentication, SSO, Authorization, Federation, Entitlements, Adaptive Authentication, Strong Authentication, and Web Services Security, in a single, unified product』,简直拽到没朋友。不过是走 Java 那一套系统的,因为公司原有系统结构的缘故,暂时就先不考虑了。
  • Kerberos 是来自 MIT 的计算机网络认证协议,针对的就是客户端-服务器模型,特点是提供了一系列交互认证——用户和服务器都能验证对方的身份。Kerberos 协议可以保护网络实体免受窃听和重复攻击。更多信息可以查阅维基百科: Kerberos 词条,介绍得还是非常清晰的。
  • CAS 是耶鲁大学发起的开源项目,简单来说,CAS 是最简单实效,而且足够安全的 SSO 选择,旨在为 Web 应用系统提供一种可靠的单点登录方法。这个方案的问题在于公司业务主要基于 Rails,但是 Rails 上相关的库乏善可陈而且文档非常『稀疏』,暂时还在考虑具体的方式
  • Shibboleth 也是广泛使用的一套身份验证系统,CMU 用的就是它!每个组建都是开源免费的,主要包括三大部分:Identity Provider(IdP), Native Service Provider(SP) 和 Discovery Service(DS)。具体还需要仔细研究一下,不过我更倾向于这种。

原理

基本登录

在开始具体介绍 SSO 及相关协议之前,我们先来简单了解一下登录的原理。

现在似乎已经很难找到不需要登录就可以深度体验全部功能的网站了(我的博客是一个例外,哈),为什么我们输入用户名密码之后,理论上『无状态』的 HTTP 连接就变得『有(登录)状态』了呢?其中的奥秘在 Cookie 和 Session 中。

当我们第一次访问某个新网站时,服务器会创建一个新的 Session 对象和对应的 Cookie,并且把 Cookie 写入到用户的浏览器中。下一次访问同一网站时,浏览器会在发送请求的时候捎带上 Cookie,服务器就根据 Cookie 找到该浏览器对应的 Session。用户一旦登录成功,服务器会在其 Session 中写入用户信息,在此之后服务器便可以通过 Cookie 得到对应的 Session,并据此读取当前用户的信息。

小结一下:Cookie 保存在用户的浏览器中,Session 对象存放在服务器中,服务器根据随请求发来的 Cookie 找到对应的 Session 来判断用户的身份。看起来非常合理,但是这里我们隐含了一个假设:只有一台服务器。

如果我们的后端是服务器集群,第一次用户的请求是 Server1 处理,第二次是 Server2 处理,因为第一次产生的 Session 是存放在 Server1 的,所以第二次访问 Server2 的时候,并没有对应的 Session 信息,便出现异常。

这怎么办?比较常用的办法有三种:

  1. 稳定分发,意思就是同一个浏览器发送来的请求,只会发给同一台服务器。这种方式简单粗暴,但是很难做负载均衡,实际上也把服务器集群劣化成了单点失败的系统,所以不建议采用。
  2. Session 复制,意思是 Server 之间进行 Session 的同步,保证两边的数据一致。这种方式需要额外的计算和带宽资源,而且同步的问题是非常棘手且容易出错的,不到万不得已也不建议。
  3. Session 共享,比方说使用 Redis 来保存 Session,不同的服务器都访问同一个 Redis 来进行 Session 的存取。

这样一来,我们的系统就支持登录功能了。

单点登录

前面的基本登录是针对于一个系统的,但是一般公司内部会有各种各样的系统,而且因为历史原因每个系统的登录往往是分开的,系统一多就很麻烦。这时候如果我们想一处登录处处畅通的话,就需要处理下面几个问题:

  1. 用户数据如何维护?
    • 各自维护:每个系统不需要做太大变动,但是最好保证相同用户的登录名一致
    • 主从维护:选一个系统作为主系统,其他的系统从该系统同步
    • 独立维护:独立于原来的系统的一套专有系统
  2. 用户数据如何同步?(针对于后两种维护方式)
    • 主系统推送:子系统单点故障容易导致数据不一致
    • 子系统定期同步:不能达到实时,比方说用户更换头像不能及时同步过去
    • 用户登录后更新:无法控制用户的访问
    • 最好根据实际情况挑选一个或多个来配合实现
  3. 用户权限如何设计?
    • 割据模式:不同系统自行决定
    • 分封模式:主系统只指定子系统的超级管理员
    • 集权模式:主系统定义各种用户的角色权限
    • 其中割据模式不推荐,集权模式比较适用于新系统,分封模式可以用作过渡
  4. 系统如何实现?
    • 共享 Cookie:前面提到过,如果不同的 session 共用一个 Cookie 不就可以统一验证了嘛,但是需要域名相同,并且也不是太安全,不建议
    • Ticket 验证:
      • 用户访问某个子系统,发现如果未登录,则引导用户跳转到 SSO 登录页面
      • 判断 SSO 是否已经登
      • 如果已经登录,直接跳转到回调地址,并返回认证 ticket
      • 如果未登录,用户正确输入用户名/密码,认证通过跳转到回调地址,并返回认证 ticket
      • 子系统获取 ticket,调用 SSO 获取用户 uid 等信息,成功后让用户登录

前面提到过 CAS 协议,接下来我们会以此为主介绍 SSO 的具体实现细节。

深入理解 CAS

一般来说 CAS 包含两部分:CAS Server 和 CAS Client。前者需要独立部署,负责用户认证工作,后者则是放在应用中(主要就是 Web 应用),并把所有的认证请求都重定向到 CAS Server 中。

继续之前我们来了解三个术语:

Ticket Granting Ticket(TGT)

TGT 是 CAS 为用户签发的登录票据,拥有了 TGT,用户就可以证明自己在 CAS 成功登录过。TGT 封装了 Cookie 值以及此 Cookie 值对应的用户信息。用户在 CAS 认证成功后,CAS 生成 cookie(叫 TGC),写入浏览器,同时生成一个 TGT 对象,放入自己的缓存,TGT 对象的 ID 就是 cookie 的值。当 HTTP 再次请求到来时,如果传过来的有 CAS 生成的 cookie,则 CAS 以此 cookie 值为 key 查询缓存中有无 TGT,如果有的话,则说明用户之前登录过,如果没有,则用户需要重新登录。

Ticket-granting cookie(TGC)

存放用户身份认证凭证的 cookie,在浏览器和 CAS Server 间通讯时使用,并且只能基于安全通道传输(Https),是 CASServer 用来明确用户身份的凭证

Service ticket(ST)

服务票据,服务的惟一标识码 , 由 CASServer 发出(Http 传送),用户访问 Service 时, service 发现用户没有 ST,则要求用户去 CAS 获取 ST。用户向 CAS 发出获取 ST 的请求, CAS 发现用户有 TGT,则签发一个 ST,返回给用户。用户拿着 ST 去访问 service,service 拿 ST 去 CAS 验证,验证通过后,允许用户访问资源。

工作流程

整个流程在前面有简单介绍过,现在具体说明一下:

  1. 我们首次访问子系统时,通过内嵌的 CAS Client 经浏览器发送访问请求,此时没有 Session 也没有 ST,所以会重定向到 CAS Server
  2. 请求发送到 CAS Server 后,因为浏览器中没有 cookie,所以服务器无法获取 TGC,这时候我们需要重新登录
  3. 转到登录界面后,用户输入正确的凭证(Credentials)后认证成功,服务器生成对应的 cookie 并写入浏览器,同时生成一个 TGT 对象,然后根据 TGT 生成 ST,最后附带着 ST 把请求重定向到 CAS Client
  4. CAS Client 接收到 ST 之后,会去 CAS Server 验证,成功之后 CAS Server 返回对应的用户信息
  5. CAS Client 根据对应的信息进行内容展示。为什么收到 ST 后还要验证呢?因为目前只有 CAS Server 知道该用户登录了,但是对于子应用来说并不知道,所以要验证
  6. 当用户访问另一个子应用的时候,同样会被重定向到 CAS Server,这时候用户是带有对应的 TGC 的
  7. 如果此时 TGC 没有失效,那么返回第三步,生成 ST 并重定向;如果 TGC 失效了,用户需要重新登录,也就是返回第二步

简单来说,就是 1 个 Cookie 配合 多个 Session 的套路。CAS Server 创建的 cookie 在所有应用中登录时使用,各应用通过创建各自的 session 来标识应用是否已经登录。

安全

作为统一的验证机制,自然而然就把所有的脆弱性集中到了一起,于是安全就成了重中之重。CAS 协议从最开始就很依赖 SSL,所以之前的 SSL 漏洞着实影响着很多系统。

在 CAS 系统中,最重要的要数 TGC,如果 TGC 泄露,黑客就能够利用 TGC 来冒充子应用骗过 CAS Server。

那么具体在应用中可以注意什么呢?

  • 根据业务需求设定合适的 TGC 存活周期
  • ST 只使用一次,并且在一段时间后失效
  • ST 需要足够随机,不能太容易被猜中

参考资料

您的支持是对我创作最大的鼓励!

热评文章