SSO 单点登录详解
本文授权转载自:https://ken.io/note/sso-design-implement 作者:ken.io
SSO 介绍
什么是 SSO?
SSO 英文全称 Single Sign On,单点登录。SSO 是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
例如你登录网易账号中心(https://reg.163.com/ )之后访问以下站点都是登录状态。
- 网易直播 https://v.163.com
- 网易博客 https://blog.163.com
- 网易花田 https://love.163.com
- 网易考拉 https://www.kaola.com
- 网易 Lofter http://www.lofter.com
SSO 有什么好处?
- 用户角度 :用户能够做到一次登录多次使用,无需记录多套用户名和密码,省心。
- 系统管理员角度 : 管理员只需维护好一个统一的账号中心就可以了,方便。
- 新系统开发角度: 新系统开发时只需直接对接统一的账号中心即可,简化开发流程,省时。
SSO方案
单点登录(SSO)是一种身份验证方法,允许用户在一个应用程序或服务中登录后,无需再次输入凭据即可访问其他相关应用程序或服务。这种方法通过将登录认证和业务系统分离,使用独立的登录中心,实现了在登录中心登录后,所有相关的业务系统都能免登录访问资源。
SSO 单点登录的方案实际上有很多种:
- 基于会话的单点登录(Session-Based SSO):
这是最早和最简单的单点登录实现方式。当用户在第一个应用程序中登录时,服务器会创建一个会话,并将该会话 ID 存储在用户的浏览器中(通常是通过 Cookie)。当用户访问其他应用程序时,浏览器会发送该会话 ID,从而允许服务器验证用户的身份。此方法的缺点是它依赖于浏览器和会话状态,对于分布式或者微服务系统而言,可能需要在服务端做会话共享,但是服务端会话共享效率比较低,这不是一个好的方案。
- 基于令牌的单点登录(Token-Based SSO):
这种方法通常使用 JSON Web Tokens(JWT)或类似的令牌格式。当用户在第一个应用程序中登录时,服务器会生成一个包含用户信息的令牌,并将其发送给客户端(通常是浏览器)。客户端会存储这个令牌,并在访问其他应用程序时将其作为请求的一部分发送。应用程序会验证令牌的有效性,并据此授予用户访问权限。这种方法更加安全和灵活,因为它不依赖于会话状态,可以在多个域和服务器之间工作。这种方案实际上有很多变种,但是目前大部分的分布式项目单点登录基本上都是这种方案,或者是基于这种方案衍生出来的变种方案。
- 基于 OAuth 的单点登录(OAuth-Based SSO):
OAuth 是一个开放标准,允许用户授权第三方应用程序访问其存储在另一个服务提供商上的信息,而无需将用户名和密码提供给该第三方应用程序。OAuth2.0 是最常用的版本,它支持多种授权流程,包括授权码流程、隐式流程和客户端凭据流程。
在单点登录的上下文中,OAuth 可以用作一个中介,用户在一个“授权服务器”上登录,并获得一个访问令牌,该令牌可以用于访问其他“资源服务器”上的资源。OAuth 提供了丰富的功能和安全性,但它也相对复杂,需要仔细配置和管理。
- 基于SAML的单点登录(SAML-Based SSO):
SAML(Security Assertion Markup Language)是一种 XML 框架,用于在不同安全域之间交换身份验证和授权信息。SAML 允许一个实体(通常是身份提供商或 IdP)向另一个实体(通常是服务提供商或 SP)发送安全断言,证明用户已经成功登录。SAML 通常与 OAuth 结合使用,以提供更强大和灵活的单点登录解决方案。但是 SAML 比较复杂,所以维护起来可能会有压力。
回到具体的生产环境,选择哪种单点登录方案取决于具体的需求和环境。对于是分布式但是又比较简单的内部应用程序,基于会话的 SSO 可能就足够了。但是大型分布式系统,基于令牌或 OAuth 的 SSO 可能更合适。小伙伴还是要结合自己的实际项目去选择。
SSO 设计与实现
本篇文章也主要是为了探讨如何设计&实现一个 SSO 系统
以下为需要实现的核心功能:
- 单点登录
- 单点登出
- 支持跨域单点登录
- 支持跨域单点登出
核心应用与依赖

应用/模块/对象 | 说明 |
---|---|
前台站点 | 需要登录的站点 |
SSO 站点-登录 | 提供登录的页面 |
SSO 站点-登出 | 提供注销登录的入口 |
SSO 服务-登录 | 提供登录服务 |
SSO 服务-登录状态 | 提供登录状态校验/登录信息查询的服务 |
SSO 服务-登出 | 提供用户注销登录的服务 |
数据库 | 存储用户账户信息 |
缓存 | 存储用户的登录信息,通常使用 Redis |
用户登录状态的存储与校验
常见的 Web 框架对于 Session 的实现都是生成一个 SessionId 存储在浏览器 Cookie 中。然后将 Session 内容存储在服务器端内存中,这个 ken.io 在之前Session 工作原理中也提到过。整体也是借鉴这个思路。
用户登录成功之后,生成 AuthToken 交给客户端保存。如果是浏览器,就保存在 Cookie 中。如果是手机 App 就保存在 App 本地缓存中。本篇主要探讨基于 Web 站点的 SSO。
用户在浏览需要登录的页面时,客户端将 AuthToken 提交给 SSO 服务校验登录状态/获取用户登录信息
对于登录信息的存储,建议采用 Redis,使用 Redis 集群来存储登录信息,既可以保证高可用,又可以线性扩充。同时也可以让 SSO 服务满足负载均衡/可伸缩的需求。
对象 | 说明 |
---|---|
AuthToken | 直接使用 UUID/GUID 即可,如果有验证 AuthToken 合法性需求,可以将 UserName+时间戳加密生成,服务端解密之后验证合法性 |
登录信息 | 通常是将 UserId,UserName 缓存起来 |
用户登录/登录校验
登录时序图

按照上图,用户登录后 AuthToken 保存在 Cookie 中。 domain=test.com
浏览器会将 domain 设置成 .test.com,
这样访问所有 *.test.com 的 web 站点,都会将 AuthToken 携带到服务器端。
然后通过 SSO 服务,完成对用户状态的校验/用户登录信息的获取
登录信息获取/登录状态校验

用户登出
用户登出时要做的事情很简单:
- 服务端清除缓存(Redis)中的登录状态
- 客户端清除存储的 AuthToken
登出时序图

跨域登录、登出
前面提到过,核心思路是客户端存储 AuthToken,服务器端通过 Redis 存储登录信息。由于客户端是将 AuthToken 存储在 Cookie 中的。所以跨域要解决的问题,就是如何解决 Cookie 的跨域读写问题。
解决跨域的核心思路就是:
- 登录完成之后通过回调的方式,将 AuthToken 传递给主域名之外的站点,该站点自行将 AuthToken 保存在当前域下的 Cookie 中。
- 登出完成之后通过回调的方式,调用非主域名站点的登出页面,完成设置 Cookie 中的 AuthToken 过期的操作。
跨域登录(主域名已登录)

跨域登录(主域名未登录)

跨域登出

OAuth2.0
OAuth2.0 是一种开放授权协议,允许用户授权第三方应用程序访问其存储在服务提供商(如QQ、WeiXin、抖音等)上的特定资源。与 SSO 类似,OAuth2.0 也使用了令牌的概念来实现身份验证和授权。
什么是Oauth2.0
OAuth 是一个行业的标准授权协议,主要用来授权第三方应用获取有限的权限。而 OAuth 2.0 是对 OAuth 1.0 的完全重新设计,OAuth 2.0 更快,更容易实现,OAuth 1.0 已经被废弃。详情请见:rfc6749。
实际上它就是一种授权机制,它的最终目的是为第三方应用颁发一个有时效性的令牌 Token,使得第三方应用能够通过该令牌获取相关的资源。
OAuth 2.0 比较常用的场景就是第三方登录,当你的网站接入了第三方登录的时候一般就是使用的 OAuth 2.0 协议。
另外,现在 OAuth 2.0 也常见于支付场景(微信支付、支付宝支付)和开发平台(微信开放平台、阿里开放平台等等)。
下图是 Slack OAuth 2.0 第三方登录的示意图:

OAuth2.0 定义了四种授权模式,分别是:
- 授权码模式
- 隐式模式
- 密码模式
- 客户端模式
其中,授权码模式是最常用的一种模式,适用于那些有后端的 Web 应用程序。在这种模式下,第三方应用程序首先向授权服务器申请一个授权码,然后使用这个授权码向授权服务器请求访问令牌。一旦获得访问令牌,第三方应用程序就可以使用这个令牌访问用户授权的资源。
注意,OAuth2.0 并不直接实现单点登录功能。它主要关注授权和访问控制,允许用户授权第三方应用程序访问其资源。然而,通过与其他技术(如SSO)结合使用,OAuth2.0 可以实现单点登录的效果。
目前来说,如果你想在项目中使用 OAuth2 的话,主要有如下几种主流框架:
- Spring Security OAuth:Spring Security OAuth 是 Spring框架的一个扩展,提供了对 OAuth2 协议的全面支持。它允许开发者在 Spring 应用程序中轻松实现 OAuth2 认证和授权流程,包括授权服务器、资源服务器和客户端应用程序的配置。
- Keycloak:Keycloak 是一个开源的身份和访问管理解决方案,它支持 OAuth2、OpenID Connect 和其他身份协议。Keycloak 提供了一个易于使用的管理界面,允许开发者配置和管理 OAuth2 相关的设置,如客户端、用户和角色等。
- Apache Oltu:Apache Oltu 是一个实现了 OAuth2 协议的 Java 库,它提供了对 OAuth2 流程的抽象和简化。Oltu 可以帮助开发者快速构建 OAuth2 客户端和服务器组件,并支持多种授权流程,如授权码流程、隐式流程等。
这些框架和库提供了 OAuth2 协议的完整实现,包括令牌生成、验证、刷新、撤销等。它们简化了 OAuth2 流程的集成,使得开发者能够专注于业务逻辑的实现,而无需过多关注底层的认证和授权细节。
SSO 与 OAuth2.0
首先,SSO 主要关注用户在多个应用程序和服务之间的无缝切换和保持登录状态的问题。它通过独立的登录中心来实现这一目标,使用户只需在一个地方输入凭据即可访问所有相关应用程序和服务。而 OAuth2.0 则主要关注授权和访问控制的问题,允许用户授权第三方应用程序访问其存储在服务提供商上的特定资源。
其次,SSO 通常只涉及用户、登录中心和业务系统之间的交互,而 OAuth2.0 则涉及用户、第三方应用程序、授权服务器和资源服务器之间的交互。这使得 OAuth2.0 更加复杂和灵活,适用于多种场景和应用程序类型。
