Blog
SSO 单点登录入门:从概念到前端回调流程
用一次普通用户登录过程讲清 SSO、回调地址、code、token、会话、权限校验和前后端职责边界。
很多系统做到一定规模之后,都会遇到同一个问题:用户不可能在每个系统里都单独注册、单独登录、单独维护密码。
比如一个公司内部可能有这些系统:
OA 系统
项目管理系统
数据看板
知识库
审批系统
第三方业务平台
如果每个系统都自己做登录,用户体验会很差,安全策略也很难统一。SSO 单点登录就是为了解决这个问题。
SSO 是什么
SSO 是 Single Sign-On 的缩写,中文通常叫单点登录。
它的核心目标是:
用户只登录一次,就可以访问多个互相信任的系统。
这里的“单点”不是说系统只有一个页面,而是说登录入口和身份校验能力被收口到了统一身份系统。
用户访问业务系统时,如果还没有登录,业务系统会把用户引导到统一登录平台。用户在统一登录平台完成身份验证后,再回到原来的业务系统。
为什么需要 SSO
SSO 解决的不只是“少输几次密码”。
更重要的是这些工程问题:
- 统一账号体系:用户、组织、角色从一个地方维护。
- 统一安全策略:密码规则、多因素认证、登录风控可以集中控制。
- 统一登录体验:不同业务系统不用各做一套登录页。
- 降低系统复杂度:业务系统不用直接处理密码。
- 方便权限扩展:登录后可以继续做角色、订阅、租户等校验。
对于企业系统来说,SSO 往往是身份体系的入口层。
一次 SSO 登录的完整流程
可以先看一个典型流程:
flowchart TD
user["用户"] --> frontend["业务系统前端"]
frontend -->|1. 发现用户未登录| login["统一登录平台"]
login -->|2. 完成账号、密码或其他认证| code["生成一次性 code"]
code -->|3. 携带 code 回调| backend["业务系统后端"]
backend -->|4. 用 code 换取用户信息或 token| session["建立业务系统自己的会话"]
session --> page["用户进入业务页面"]
这个流程里,前端通常负责跳转和展示状态;后端负责真正的 code 校验、token 换取、用户会话建立和权限判断。
回调地址是什么
回调地址是 SSO 流程里非常关键的概念。
业务系统告诉统一登录平台:
用户登录完成后,请把他送回这个地址。
这个地址通常长这样:
https://example.com/login/callback
登录平台完成认证后,会重定向到这个地址,并在 URL 上带一个临时凭证:
https://example.com/login/callback?code=temporary_code
这个 code 一般是短时间有效、只能使用一次的临时凭证。业务系统需要用它去服务端换取用户身份信息。
code 和 token 的区别
很多刚接触 SSO 的人会把 code 和 token 混在一起。可以这样理解:
| 名称 | 作用 | 生命周期 |
|---|---|---|
| code | 登录完成后的临时换票凭证 | 很短,通常只能用一次 |
| token | 代表用户身份或接口访问权限 | 相对更长,但也应该有过期时间 |
| session | 业务系统自己的登录态 | 由业务系统控制 |
比较安全的做法是:
flowchart LR
code["前端拿到 code"] --> exchange["后端用 code 换 token 或用户信息"]
exchange --> session["后端建立业务系统自己的 session"]
session --> result["前端只感知“已登录”"]
不要把 client secret、长期 token、第三方接口密钥直接放在前端。
前端在 SSO 中应该做什么
前端适合承担这些职责:
- 判断当前是否需要跳转登录。
- 拼接登录地址中的公开参数。
- 接收回调地址上的 code。
- 展示“登录中”“校验中”“跳转中”等过渡状态。
- 把 code 交给后端处理。
- 根据后端返回结果进入业务页面或显示错误。
前端不适合承担这些职责:
- 保存 client secret。
- 直接用 code 调第三方敏感接口。
- 在浏览器里生成第三方接口签名。
- 自己判断最终权限。
- 保存长期敏感 token。
原因很简单:前端代码和构建产物最终都会进入用户浏览器。只要在浏览器里出现过,就不能当成真正的秘密。
后端在 SSO 中应该做什么
后端通常负责更敏感的部分:
- 校验 code 是否真实有效。
- 使用 client secret 与统一登录平台通信。
- 换取用户信息或 access token。
- 建立业务系统自己的 session。
- 查询用户角色、组织、租户、订阅状态。
- 对跳转地址做白名单校验。
- 记录登录审计日志。
如果系统还要对接第三方服务,最好也由后端完成签名和转发。
推荐结构是:
flowchart LR
browser["浏览器"] --> backend["业务系统后端"]
backend --> provider["统一登录平台 / 第三方服务"]
这样可以把密钥、签名、token 和权限判断都收在后端。
SSO 和权限校验不是一回事
SSO 只能证明:
这个用户是谁。
但它不一定能证明:
这个用户能不能访问当前业务。
所以很多系统会在 SSO 登录成功后继续做二次校验,例如:
- 用户是否属于某个组织。
- 用户是否拥有某个角色。
- 用户是否购买或订阅了某个服务。
- 用户是否在当前租户下有权限。
- 用户是否被管理员禁用。
这也是为什么一个完整登录流程常常是:
flowchart LR
authenticate["身份认证"] --> profile["用户信息获取"]
profile --> authorize["业务权限校验"]
authorize --> system["进入系统"]
常见异常路径
SSO 集成页一定要把异常路径设计清楚。
常见异常包括:
- 回调地址没有 code。
- code 已过期。
- code 已被使用过。
- 后端换取用户信息失败。
- 用户信息缺少唯一 ID。
- 用户没有访问当前系统的权限。
- 用户订阅已过期。
- 登录平台不可用。
- 跳转地址不在白名单。
这些异常不应该让用户停在空白页。至少应该给出明确提示,并提供一个可控的返回入口。
安全检查清单
做 SSO 集成时,可以用下面这份清单快速自查:
- client secret 是否只保存在后端。
- access token 是否避免暴露在 URL 中。
- code 是否一次性、短有效期。
- 回调地址是否配置了白名单。
- 跳转目标是否做了白名单校验。
- 前端是否避免保存长期敏感 token。
- 退出登录时是否清理了业务系统 session。
- 登录失败是否有明确提示和返回入口。
- 后端是否记录了必要的登录审计。
- 不同环境是否使用不同的登录配置。
一个简单的前后端分工模板
可以把一次登录拆成这样:
flowchart TD
subgraph entry["前端入口页"]
check["检查是否已登录"]
redirect["未登录则跳转统一登录平台"]
check --> redirect
end
subgraph callback["前端回调页"]
read["读取 URL 中的 code"]
requestBackend["调用后端 /auth/sso/callback"]
status["展示登录状态"]
read --> requestBackend
requestBackend --> status
end
subgraph backend["后端回调接口"]
validate["校验 code"]
exchange["换取用户信息"]
permission["查询业务权限"]
session["建立 session"]
response["返回登录结果"]
validate --> exchange --> permission --> session --> response
end
subgraph business["前端业务页"]
enter["根据登录结果进入系统"]
error["失败时显示原因并返回入口"]
end
redirect --> read
requestBackend --> validate
response --> status
status --> enter
status --> error
这个分工的重点是:前端编排流程,后端守住秘密和权限边界。
总结
SSO 单点登录不是简单的“跳到一个登录页再跳回来”。它连接了统一身份、业务系统、权限校验和用户会话,是系统安全边界的一部分。
理解 SSO 时,可以抓住四个关键词:
- 回调地址:登录完成后回到哪里。
- code:短期一次性换票凭证。
- token/session:登录态和接口访问能力。
- 权限校验:确认用户能不能访问当前业务。
只要把这些边界想清楚,再去实现 Vue、React、后端接口或 Nginx 代理,思路就会顺很多。