全链路交互
角色场景
-
场景描述:用户 Zhangsan 想要在"系统 B"里查看他在"系统 A"的订单,但他还没登录。
-
四大角色:
- User:用户(Zhangsan)
- Client (System B):客户端(系统B),包含前后端,假设登录归AS管
- Resource Server (System A):资源服务器(系统A),包含前后端,登录归AS管
- Authorization Server (AS):授权服务器,包含前后端
-
核心痛点:根据最小权限原则,系统B不应该拥有系统A所有接口以及数据的权限
第一部分 ------ 身份的证明(SSO 登录与授权意愿)
核心逻辑:用户浏览器在系统 B 和授权中心之间"反复横跳",完成登录并表达授权意愿。
-
1.1 发起授权
- 系统 B 前端首先检查浏览器 localStorage,因未获取到有效用户信息(如 Zhangsan 的凭证),判定当前为未登录状态,并向用户展示指向 SSO 的登录链接。
- 用户点击登录链接后,系统 B 前端执行跳转操作,将浏览器重定向至授权服务器的前端页面(SSO.vue)。
- 在跳转的 URL 中,系统 B 附带了以下核心 OAuth2 参数以声明意图:
client_id:系统 B 的身份标识(申请者)。resource_id:目标资源标识(本例为系统 A 的client_id,表明要访问系统 A)。scope:申请的权限范围(系统 B 要在系统 A 执行的操作)。response_type:授权类型(通常为code)。redirect_uri:登录后的回调地址。state:随机防伪参数(用于防范 CSRF 攻击)。
-
1.2 SSO 的静默检测
-
授权服务器前端页面(
SSO.vue)加载时触发created()生命周期钩子。前端随即向后端接口GET /system/oauth2/authorize发起请求,透传了从系统 B 接收到的所有关键参数(client_id、response_type、scope、state、resource_id),以询问当前用户的登录与授权状态。 -
授权服务器后端通过过滤器(Filter)拦截请求,执行两层校验:
- 合法性校验 :确认
client_id有效,且该客户端具备请求目标resource_id下对应scope的权限。 - 身份校验 :由于请求头未携带有效的
AccessToken(SSO 会话凭证),导致 Spring Security 上下文(SecurityContext)为空。后端判定用户未登录,校验失败并返回 HTTP 401 Unauthorized 状态码。
- 合法性校验 :确认
-
授权服务器前端捕获到 401 响应后,判定当前用户处于未登录状态。在尝试使用
RefreshToken刷新会话失败后,前端将路由导航至授权服务器的 统一登录页面(Login Page),等待用户输入账号密码。
-
-
1.3 登录授权中心
-
用户在授权服务器的统一登录页面输入账号与密码,并点击登录按钮。授权服务器前端随即请求向后端接口
/system/oauth2/login发送用户的身份凭证。 -
授权服务器后端接收请求并执行身份鉴权逻辑(如查库比对密码)。验证通过后,后端为该用户建立 SSO 全局会话,并颁发
AccessToken1(注:此处指 SSO 系统的会话凭证,用于证明用户在授权中心的登录态),返回给前端。
-
-
1.4 二次握手与意愿确认
-
登录验证通过后,前端将页面重定向回之前的授权入口页面(
SSO.vue)。页面初始化逻辑再次触发,向后端接口GET /system/oauth2/authorize发起第二次授权请求。与首次请求不同,此次请求头部(或 Cookie)携带了新获取的AccessToken1(SSO 会话凭证),同时依然包含client_id、resource_id、scope等原始 OAuth2 协议参数。 -
授权服务器后端首先校验
AccessToken1的有效性以确认用户身份(Zhangsan),随后复查客户端与目标资源的权限申请合法性。验证无误后,后端查询并返回用于渲染 "授权确认页(Consent Page)" 的关键元数据,包括:客户端名称("系统 B")、当前用户信息、以及申请的权限范围(如"读取系统 A 的数据")。 -
授权服务器前端根据返回元数据渲染页面,向用户展示:"系统 B 请求访问您在系统 A 的 user.read 和 user.write 权限"。用户确认勾选并点击"允许"后,前端构建
POST /system/oauth2/authorize请求发送给后端,提交用户的最终授权意愿。该请求携带了AccessToken1(证明是谁授权的)、client_id、redirect_uri、resource_id以及用户最终批准的scope列表。
-
-
1.5 生成授权码
-
授权服务器后端接收用户的确认请求,首先验证 SSO 凭证(
AccessToken1)以确立当前操作者身份。随后进入核心处理流程:- 协议参数校验 :再次确认
redirect_uri与注册白名单匹配,且response_type为code。 - 持久化授权记录 :在数据库
approve表中插入或更新记录,正式将"用户 Zhangsan 对系统 B 访问系统 A 资源的授权"标记为 已批准(Approved)。 - 生成授权码(Code) :生成一个短时效、一次性的 Authorization Code。将该 Code 作为 Key,对应的用户 ID、Scope、ResourceID 等上下文信息作为 Value,存入 Redis(设置短期过期)及数据库中。
- 重定向响应 :将生成的 Code(以及原样返回的 State)拼接到回调地址后(如
http://sys-b.com/cb?code=xyz&state=...),向前端返回 HTTP 302 重定向 指令。
- 协议参数校验 :再次确认
-
第二部分 ------ 以Code换Token
核心逻辑:前端退场,后端登场。这是最安全的"背靠背"通信环节。
-
2.1 传递Code
- 浏览器响应重定向指令,跳转回系统 B 前端,系统B此时拿到了Code (及 State)。系统 B前端构建 HTTP POST 请求(如
/systemB/login-by-code),附带Code 发送给系统 B 后端。
- 浏览器响应重定向指令,跳转回系统 B 前端,系统B此时拿到了Code (及 State)。系统 B前端构建 HTTP POST 请求(如
-
2.2 颁发令牌
-
系统 B 后端接收到前端传来的 Authorization Code 后,立即构建 HTTP POST 请求,向授权服务器的令牌接口(
auth/oauth2/token)发起调用。此次请求必须通过 HTTPS 通道传输,且携带以下核心凭证:code:核心兑换凭证。client_id&client_secret:系统 B 的"身份证"与"密码"(用于证明请求者是合法的系统 B 后端,而非黑客)。resource_id:再次确认目标资源归属。- (注:标准协议中通常还需携带
redirect_uri用于二次校验)
-
授权服务器后端接收请求(此端点通常不校验 User Token,而是校验 Client Credentials)。服务器执行严格的 "阅后即焚" 逻辑:
- 身份认证 :比对
client_secret,确认系统 B 身份合法。 - Code 验证 :检查
code是否存在、未过期,且与当前client_id及user_id强绑定。 - Code 销毁 :验证通过后,立即从数据库/Redis 中删除该 Code,防止重放攻击。
- 令牌生成与持久化 :生成正式的 AS_AccessToken ,将其与
user_id、client_id、resource_id及scope绑定并持久化存储。最终,将 Token 通过响应体返回给系统 B 后端。
- 身份认证 :比对
-
第三部分 ------ 本地会话的建立(身份同步)
核心逻辑:系统 B 拿到了 AS 的令牌,但它自己也需要建立登录态。
-
3.1 用户身份同步
-
系统 B 后端首先将刚刚获取的 AS_AccessToken 进行持久化存储(通常存入 Redis 并与当前会话绑定,或存入数据库),以便后续复用。紧接着,系统 B 后端构建 HTTP GET 请求,调用授权服务器的 UserInfo 端点 (
auth/oauth2/userinfo)。在此请求中,必须将 AS_AccessToken 置于 HTTP Header 的Authorization: Bearer <token>字段中,作为调用凭证。 -
授权服务器后端接收请求,执行严格的令牌内省逻辑:
- 有效性校验 :确认 AS_AccessToken 签名正确且未过期。
- Scope 审查 :检查该 Token 是否包含获取用户信息所需的权限(如
openid、profile或email)。 - 数据组装与响应 :校验通过后,根据 Token 绑定的
user_id查询用户档案,按需提取字段(如 Username、Email、Avatar),将其封装为 JSON 对象返回给系统 B。
-
-
3.2 本地账户映射与会话确立
-
系统 B 后端基于获取的用户身份信息(UserInfo),执行 "即时开户" 策略:
- 账户映射 :检索本地数据库是否存在该
user_id(来自授权服务器)对应的记录。若不存在,则自动注册生成一个 "影子账号";若存在,则根据最新信息更新本地档案。 - 会话签发 :系统 B 为该用户生成专属的 本地会话凭证(AccessTokenB) (注:此 Token 仅由系统 B 签发和验证,用于访问系统 B 自身的业务接口,完全独立于授权服务器的 Token)。
- 响应返回:将 AccessTokenB 及用户基础画像封装后返回给前端。
- 账户映射 :检索本地数据库是否存在该
-
系统 B 前端接收响应后,执行以下操作以确立前端登录态:
- 凭证存储 :将 AccessTokenB 写入浏览器的持久化存储区域(通常为
localStorage或Cookie),以便在后续调用系统 B 业务接口时放入 Header 使用。 - UI 更新:解析用户画像数据,移除"登录/注册"入口,渲染包含用户头像、昵称在内的已登录界面,标志着 SSO 登录流程的最终完成。
- 凭证存储 :将 AccessTokenB 写入浏览器的持久化存储区域(通常为
-
第四部分 ------ 跨系统借阅(资源代理)
核心逻辑:系统 B 充当代理人,帮用户去取系统 A 的数据。
-
4.1 业务触发与代理请求发起
- 用户在系统 B 前端界面点击"查看系统 A 订单"按钮,触发业务流程。系统 B 前端并不直接请求系统 A(由于 CORS 跨域限制及安全考量),而是向 系统 B 后端 的代理接口(如
GET /systemB/api/proxy/orders)发起请求。在此次请求中,前端仅需携带 本地会话凭证(AccessTokenB) 及用户标识,将获取外部资源的重任委托给系统 B 后端处理。
- 用户在系统 B 前端界面点击"查看系统 A 订单"按钮,触发业务流程。系统 B 前端并不直接请求系统 A(由于 CORS 跨域限制及安全考量),而是向 系统 B 后端 的代理接口(如
-
4.2 本地鉴权与令牌注入转发
-
系统 B 后端拦截前端发起的代理请求,执行以下关键操作:
- 本地会话校验 :首先验证请求头中 AccessTokenB 的有效性,确保当前操作者是系统 B 的合法登录用户。
- 凭证置换与请求构建 :校验通过后,根据用户 ID 从服务端存储(Redis/DB)中提取出之前托管的 授权服务器 AS_AccessToken(即访问系统 A 的真实凭证)。
- 上游调用 :构建指向系统 A 的 HTTP 请求(
GET systemA/orders),将提取出的 AS_AccessToken 注入到 HTTP Header 的Authorization字段中,正式向资源服务器(系统 A)发起数据调用。
-
第五部分 ------ 令牌内省
核心逻辑:系统 A 不认识这个 Token,必须找权威机构验票。
-
5.1 资源服务器拦截与远程令牌内省
- 系统 A 后端(资源服务器)接收到请求后,首先从 HTTP Header 中提取出 Bearer Token。由于该 Token 对于系统 A 而言是 不透明令牌 (或者为了确保 Token 未被实时吊销),系统 A 无法仅凭本地算法完成验证。因此,系统 A 需构建 POST 请求调用授权服务器的 内省接口 (如
auth/oauth2/check-token)。在此请求中,系统 A 除了发送待验证的 AS_AccessToken 外,必须 同时附带自身的client_id和client_secret,以向授权服务器证明自己是合法的资源服务器,从而获准查询令牌详情。
- 系统 A 后端(资源服务器)接收到请求后,首先从 HTTP Header 中提取出 Bearer Token。由于该 Token 对于系统 A 而言是 不透明令牌 (或者为了确保 Token 未被实时吊销),系统 A 无法仅凭本地算法完成验证。因此,系统 A 需构建 POST 请求调用授权服务器的 内省接口 (如
-
5.2 鉴权与数据交付
-
资源服务器(系统 A)收到授权服务器的内省响应后,执行严格的策略执行逻辑:
- 元数据解析 :解析响应体,确认令牌状态为
active: true,识别出请求方为 System B ,授权主体为 Zhangsan ,且被授予的权限范围包含user.read和user.write。 - 权限裁决 :系统 A 将令牌携带的
scope与当前接口(查询订单)所需的最小权限进行比对。确认user.read满足读取要求,通过鉴权。 - 业务履约:系统 A 后端解除拦截,执行数据库查询操作,获取 Zhangsan 的订单列表,并以 JSON 格式返回给系统 B 后端(随后逐级返回给前端展示),至此闭环完成。
- 元数据解析 :解析响应体,确认令牌状态为
-
Q&A
在上面的示例中,三个服务器,有几个user表,几个accesstoken表,几个client表,分别在哪个服务器上,什么关系。
在分布式/微服务架构中,物理上隔离,逻辑上关联是核心。
| 表名 | 总数量 | 分布位置 | 核心作用 |
|---|---|---|---|
| User 表 | 3 个 | AS, System B, System A 各有一个 | AS 存账号密码;B/A 存业务关联数据 |
| Client 表 | 1 个 | 仅 AS 有 | AS 需要管理谁有资格接入 |
| AccessToken 表 | 2 个 | AS (核心), System B (会话) | AS 存 OAuth 令牌;B 存本地会话令牌 |
系统B怎么查该用户在系统A的信息,是向授权服务器查还是系统A查
- 如果查的是"基础身份信息" ,如账号 (
zhangsan)、全局 UserID (1001)、邮箱、手机号、头像 -> 找授权服务器,因为这些信息是所有系统公用的,由 AS 统一托管。比如,系统 B 刚拿到 Token,想知道"现在的登录用户叫什么名字,我要在右上角显示'欢迎,张三'"。 - 如果查的是"系统A的业务用户信息" 比如系统 A 的收货地址 、VIP 等级 、订单历史 、收藏夹-> 找系统 A,因为授权服务器只管"登录",不管"业务"。它根本不知道张三在系统 A 买了什么书,或者住在哪里。这些数据只存在于系统 A 的数据库里。
流程中出现多个accesstoken,分别是什么作用,区别在哪
| 特性 | 1. AccessToken1 |
2. AS_AccessToken |
3. AccessToken_B |
|---|---|---|---|
| 通俗叫法 | SSO 登录态 / 身份证 | 资源令牌 / 门票 | 本地 Token / 会员卡 |
| 谁生成的? | 授权服务器 | 授权服务器 | System B 后端 |
| 谁拿着? | 用户浏览器 | System B 后端 (Redis) | 用户浏览器 |
| 发给谁检查? | 授权服务器 (/authorize) |
System A (/orders) |
System B 后端 (/api/*) |
| 存什么信息? | UserID | UserID + Scope + Audience | UserID + B系统业务角色 |
| 核心目的 | 让你不需要重复输密码 | 让 B 系统能合法操作 A 系统 | 维持 B 系统的网页登录状态 |
scope是什么,有什么作用,举例说明
Scope 是一个字符串(比如 user.read, order.write),一般配合Resourceid(指目标资源服务器是谁,比如 systemA 的clientid),AS服务器的code表,accesstoken表,approve表中可能有userid,clientid,resourceid,scope字段它代表了第三方应用系统 B 的用户张三 在资源服务器系统 A 上的具体权限范围scope。
假如系统B修改了前端文字"系统B需要您在系统A的xx权限",修改为其它文字,用户勾选了之后点击提交,那系统B岂不是骗过了用户
产生这个疑虑,可能是因为混淆了 "页面是谁渲染的" 这个问题。
- 用户在系统 B 点击登录。
- 浏览器跳转(Redirect) 到了授权服务器(
sso.vue)。
- 此刻,浏览器地址栏显示的域名是 授权服务器的域名 (例如
auth.aliyun.com),而不是系统 B 的域名(system-b.com)。 - 用户看到的这个"同意授权页面",其 HTML、CSS、JavaScript 代码,全部来自授权服务器。
- 系统 B 在这一步,已经彻底失去了对浏览器的控制权。
第二部分,系统B后端拿code向AS后端换token时,传参code,clientid,clientsecret,resourceid。为什么没有userid和scope
没有userid:授权服务器后端判断用户信息并不是依靠请求体传参userid,而是靠code在自己数据库上与用户的绑定。如果靠传参userid,那么系统B拿到zhangsan的code,却传参userid=lisi,岂不是伪造让lisi获得accesstoken了。
没有scope:传参的code已经在AS数据库中与用户信息绑定了,包括scope
Clientsecret有什么作用,数据流向是什么
client_secret 是 OAuth2 协议中保障 "系统级安全" 的最后一道防线,也是最重要的防线之一。
其它服务器后端向授权服务器发送请求时,一定要带上自己的clientsecret,才能表明自己是真的服务器后端而不是被伪造的。也因此Clientsecret不能暴露给前端,始终在后端待着。
自动授权又是什么,在流程中扮演什么角色
自动授权 是 OAuth2 流程中的一种绿色通道机制。也就是当用户登录后,授权服务器不再弹出"是否同意某某系统访问您的信息"的询问页面,而是直接默认用户同意,静默发放授权码(Code)。一般AS只向内部授权应用开自动授权。