使用 OAuth 2.0 实现单点登录(SSO)时,多个子应用共享同一登录状态,可以通过以下方式实现
场景背景
- 平台统一认证服务器(IdP):如
auth.example.com
- 子应用 A 和 B:如
appA.example.com
和appB.example.com
- 目标:用户在子应用 A 登录后,在同一浏览器中访问子应用 B 时,无需重新登录。
使用 OAuth 2.0 的模式
授权码模式(Authorization Code Grant) 是最适合此场景,因为:
- 支持前后端分离: Token 不直接暴露在浏览器中,安全性高。
- 适合浏览器登录场景: 子应用 A 和 B 都可以通过重定向完成授权。
- 支持 SSO: 登录状态通过浏览器的 Cookie 在认证服务器(IdP)共享。
完整流程
以下是基于授权码模式的 SSO 实现流程:
1. 用户访问子应用 A
- 场景:用户首次访问
appA.example.com
。 - 行为:
- 子应用 A 检查用户是否已登录(通过存储在浏览器的 Cookie 或 Token)。
- 如果未登录,子应用 A 重定向用户到统一的认证服务器(
auth.example.com
),并附带以下参数:GET https://auth.example.com/oauth/authorize ?response_type=code &client_id=appA &redirect_uri=https://appA.example.com/callback &scope=read_profile &state=random_string_for_csrf_protection
response_type=code
:请求授权码。client_id=appA
:标识子应用 A。redirect_uri
:授权后跳转的 URL。scope
:请求访问的权限范围。state
:防止 CSRF 攻击的随机字符串。
2. 用户登录
- 场景:用户在认证服务器
auth.example.com
上完成登录。 - 行为:
- 用户输入用户名和密码。
- 认证服务器验证成功后,在其域(
auth.example.com
)设置一个浏览器会话 Cookie,用于维护登录状态。- 这个 Cookie 是 HttpOnly、Secure 的,绑定到
auth.example.com
,确保安全。
- 这个 Cookie 是 HttpOnly、Secure 的,绑定到
3. 子应用 A 获取 Access Token
- 场景:用户登录后,认证服务器返回授权码给子应用 A。
- 行为:
- 认证服务器通过浏览器重定向用户到子应用 A 的回调地址:
https://appA.example.com/callback?code=auth_code&state=random_string
- 子应用 A 后端使用授权码(
auth_code
)向认证服务器请求 Access Token:POST https://auth.example.com/oauth/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code code=auth_code redirect_uri=https://appA.example.com/callback client_id=appA client_secret=appA_secret
- 认证服务器验证后,返回 Access Token 和 ID Token:
{ "access_token": "access_token_value", "id_token": "id_token_value", "expires_in": 3600, "token_type": "Bearer" }
- 子应用 A 将 Access Token 存储在用户的浏览器(如 Cookie 或 Local Storage),以便后续 API 调用。
- 认证服务器通过浏览器重定向用户到子应用 A 的回调地址:
4. 用户访问子应用 B
- 场景:用户在同一浏览器中访问
appB.example.com
。 - 行为:
- 子应用 B 检查用户是否已登录(本地无 Token)。
- 子应用 B 重定向用户到认证服务器的授权页面(流程类似子应用 A 的初始登录):
GET https://auth.example.com/oauth/authorize ?response_type=code &client_id=appB &redirect_uri=https://appB.example.com/callback &scope=read_profile &state=random_string_for_csrf_protection
5. 认证服务器检测登录状态
- 场景:用户已登录认证服务器。
- 行为:
- 认证服务器检查用户在其域的会话 Cookie,发现用户已登录,无需再次输入用户名和密码。
- 认证服务器直接生成授权码,并重定向用户回子应用 B:
https://appB.example.com/callback?code=auth_code_for_appB&state=random_string
6. 子应用 B 获取 Access Token
- 场景:子应用 B 使用授权码向认证服务器换取 Token。
- 行为:
- 后端请求 Access Token:
POST https://auth.example.com/oauth/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code code=auth_code_for_appB redirect_uri=https://appB.example.com/callback client_id=appB client_secret=appB_secret
- 认证服务器返回 Token,子应用 B 存储并使用该 Token。
- 后端请求 Access Token:
7. 用户已在子应用 B 登录
- 子应用 B 使用 Token 访问受保护的资源(如用户信息 API)。
- 用户感知为“无缝登录”。
关键技术点
-
共享会话依赖于认证服务器的 Cookie:
- 认证服务器(IdP)的会话 Cookie 是跨子应用 SSO 的核心。
- 只要用户的浏览器在认证服务器域(
auth.example.com
)中有有效 Cookie,认证服务器即可快速判断用户登录状态。
-
Token 隔离:
- 子应用 A 和 B 使用各自的 Client ID 和 Secret,Token 互不影响。
- 每个子应用的 Token 只对其自身有效,提升安全性。
-
安全措施:
- 使用
state
防止 CSRF 攻击。 - Token 存储和传输时加密。
- 确保所有通信使用 HTTPS。
- 使用
总结流程图
- 用户访问子应用 A → 未登录 → 跳转到认证服务器。
- 用户登录认证服务器 → 设置会话 Cookie。
- 认证服务器生成授权码 → 子应用 A 换取 Token。
- 用户访问子应用 B → 检测认证服务器会话 → SSO 无缝登录。
优点
- 标准化(OAuth 2.0 授权码模式)。
- 安全性高,防止 Token 泄露。
- 子应用独立授权,隔离性好。
- 无缝登录,提升用户体验。
适用场景
- 适用于 SaaS 平台中多个子应用的单点登录场景。
- 适合 Web 浏览器中用户操作的典型 SSO 场景。