228|认证与授权体系:OAuth2.0/OIDC、JWT、SSO、RBAC与权限模型设计

安全架构约 20 分钟阅读11,405 字符
知识库文档 229 / 251

阅读时间:约 45 分钟 | 分类:15 安全架构


一、先搞清楚两个概念:认证 vs 授权

很多开发者把"认证"和"授权"混为一谈,但它们在安全体系里是两个完全不同的环节。

**认证(Authentication)**回答的问题是:你是谁? 验证用户身份的过程。比如输入用户名密码登录、扫码登录、短信验证码登录,这些都是认证。

**授权(Authorization)**回答的问题是:你能做什么? 在已经确认身份之后,决定这个用户能访问哪些资源、执行哪些操作。比如普通用户不能访问管理后台、部门 A 的员工看不到部门 B 的数据。

用一个生活化的类比:你去住酒店,前台让你出示身份证——这是认证;前台给你一张房卡,这张卡只能开你房间的门,开不了别人的房间,也开不了健身房(如果你没买服务)——这就是授权。

搞清这两个概念,是理解整个安全体系的基础。下面我们逐一拆解现代认证授权的核心技术。


文章配图
文章配图

图1:认证 vs 授权概念对比

二、OAuth 2.0:不是认证协议,是授权协议

这是 OAuth 2.0 最常见的一个误解。很多人用它来做登录,但 OAuth 2.0 的设计初衷是授权委托——让第三方应用在用户授权下,访问用户在服务提供商上的资源,而不需要用户把密码告诉第三方。

2.1 核心角色

OAuth 2.0 定义了四个角色:

角色 说明 举例
Resource Owner(资源所有者) 用户本人 你
Client(客户端) 第三方应用 一个"打印你的 Google 相册"的应用
Authorization Server(授权服务器) 颁发令牌的服务器 Google 的 OAuth 服务器
Resource Server(资源服务器) 存放用户资源的服务器 Google Photos API

文章配图
文章配图

图2:OAuth 2.0 授权码模式 + PKCE 完整时序图

2.2 四种授权模式

授权码模式(Authorization Code)——最主流、最安全:

code
用户 → Client:我要用 Google 登录
Client → 浏览器:重定向到 Google 授权页面
用户 → Google:输入账号密码,同意授权
Google → 浏览器:重定向回 Client,带上 authorization code
Client → Google 授权服务器:用 code + client_secret 换 access_token
Google → Client:返回 access_token(+ 可选的 refresh_token)
Client → Google 资源服务器:用 access_token 请求用户数据

关键设计点:authorization code 通过**前端(浏览器)传递,但 access_token 通过后端(服务器到服务器)**传递。code 换 token 这一步需要 client_secret,而 client_secret 只有后端知道,所以即使 code 被中间人截获,没有 client_secret 也换不到 token。这就是为什么授权码模式比隐式模式安全。

隐式模式(Implicit)——已废弃,不要用。token 直接在 URL fragment 中返回,暴露在浏览器端,安全风险大。OAuth 2.1 已将其移除。

密码模式(Resource Owner Password Credentials)——用户直接把用户名密码给 Client,Client 去换 token。仅适用于高度信任的场景(如第一方应用),OAuth 2.1 也已移除。

客户端凭证模式(Client Credentials)——用于服务器到服务器的通信,没有用户参与。比如一个微服务调用另一个微服务,用 service account 的凭证去拿 token。

文章配图
文章配图

图3:OAuth 2.0 四种授权模式对比

2.3 Token 类型

Access Token:访问资源的凭证,通常短期有效(几分钟到几小时)。本身不携带用户信息,只是个"凭据"。

Refresh Token:用于在 access token 过期后获取新的 access token,通常长期有效(几天到几个月)。Refresh token 泄露的风险比 access token 更大,因为它存活时间更长。最佳实践是支持 refresh token rotation——每次使用 refresh token 时,同时签发一个新的 refresh token 并废弃旧的。

2.4 PKCE:为公共客户端加固

PKCE(Proof Key for Code Exchange,发音 "pixy")是授权码模式的增强。原生的授权码模式有一个安全漏洞:恶意应用可以拦截回调 URL 中的 code。PKCE 的做法是:

  1. Client 生成一个随机的 code_verifier
  2. Client 对 code_verifier 做 SHA-256 哈希得到 code_challenge
  3. 授权请求时带上 code_challenge
  4. 换 token 时带上 code_verifier
  5. 授权服务器验证 code_challenge 和 code_verifier 是否匹配

这样即使 code 被拦截,攻击者没有 code_verifier 也换不到 token。OAuth 2.1 要求所有授权码模式都必须使用 PKCE。


三、OpenID Connect(OIDC):在 OAuth 2.0 之上做认证

OAuth 2.0 能拿到 access token,但 access token 本身不告诉你"这个用户是谁"。OIDC 在 OAuth 2.0 的基础上加了一层身份认证,它引入了一个新的 token 类型:ID Token。

3.1 ID Token 是什么

ID Token 是一个 JWT(JSON Web Token),包含用户身份信息。一个典型的 ID Token payload 看起来像这样:

json
{
  "iss": "https://accounts.google.com",      // 签发者
  "sub": "1234567890",                         // 用户唯一标识
  "aud": "my-app-client-id",                   // 受众(必须是你的 client_id)
  "exp": 1735689600,                           // 过期时间
  "iat": 1735686000,                           // 签发时间
  "name": "张三",
  "email": "zhangsan@example.com",
  "picture": "https://example.com/avatar.jpg"
}

关键点:ID Token 是给客户端看的,不是给资源服务器看的。验证 ID Token 是客户端的事,不是 API 的事。

文章配图
文章配图

图4:OIDC 在 OAuth 2.0 之上的叠加关系

3.2 OIDC 的三种流程

OIDC 在 OAuth 2.0 授权码模式的基础上叠加了三种身份认证流程:

Authorization Code Flow:与 OAuth 2.0 授权码模式一致,token endpoint 返回 access_token + id_token。这是推荐的安全方案。

Hybrid Flow:authorization endpoint 直接返回部分 token(id_token 在前端,access_token 在后端),适合需要前端立即获得用户信息但不暴露 access_token 的场景。

Implicit Flow:已废弃,不要用。

3.3 OAuth 2.0 vs OIDC 一句话总结

OAuth 2.0 给你一把"钥匙"(access token),让你能打开用户的家门拿东西;OIDC 在钥匙上挂了一个"名牌"(ID Token),告诉你这个家是谁的。


四、JWT:无状态的令牌标准

JWT(JSON Web Token)是 OAuth 2.0 和 OIDC 中最常用的 token 格式。理解 JWT 的结构和安全边界,是正确使用它的前提。

4.1 结构

JWT 由三部分组成,用 . 分隔:

code
header.payload.signature

Header:声明 token 类型和签名算法

json
{"alg": "RS256", "typ": "JWT"}

Payload:存放声明(claims),包括标准声明(iss、sub、exp、iat、aud)和自定义声明

json
{"sub": "123", "name": "张三", "role": "admin", "exp": 1735689600}

Signature:对 header + payload 的签名,防止篡改

code
RS256(base64UrlEncode(header) + "." + base64UrlEncode(payload), privateKey)

文章配图
文章配图

图5:JWT 结构拆解

4.2 签名算法:对称 vs 非对称

算法 类型 密钥 适用场景 风险
HS256 对称(HMAC) 同一个 secret 签名和验证 单体应用内 secret 泄露后任何人都能伪造 token
RS256 非对称(RSA) 私钥签名,公钥验证 微服务架构 私钥管理是关键
ES256 非对称(ECDSA) 私钥签名,公钥验证 性能敏感场景 同 RS256
none 无签名 无 调试用 生产环境绝对禁止

生产环境建议:用 RS256 或 ES256。为什么?因为 HS256 的 secret 需要同时分发给签名方和验证方,secret 泄露的范围更大。而非对称算法中,只有授权服务器持有私钥,所有资源服务器只需要公钥来验证,安全边界更清晰。

4.3 JWT 的安全使用原则

不要在 JWT 中放敏感信息。JWT 的 header 和 payload 只是 Base64 编码,不是加密。任何人都能解码看到里面的内容。JWT 只保证"没被篡改",不保证"没人能看到"。

"无状态"不等于"无法撤销"。JWT 签发后,在过期时间之前服务器无法主动让它失效。这是 JWT 最大的设计权衡。常见的补救方案:

  • 短过期时间:access token 5-15 分钟过期,用 refresh token 续期
  • Token 黑名单:维护一个已撤销 token 的集合(但这就引入了状态,违背了 JWT 的初衷)
  • 版本号机制:在用户表里加一个 token_version 字段,修改密码时递增,JWT 中携带版本号,验证时比对

验证要严格:验证 JWT 时,必须检查 exp(是否过期)、iss(签发者是否可信)、aud(受众是否匹配)、nbf(是否已生效)。缺少任何一个检查都可能导致安全漏洞。


五、SSO(单点登录):一次认证,多处通行

SSO 的核心思想:用户在一个地方登录,就可以访问多个关联系统,无需重复输入密码。

5.1 SSO 的三种实现方式

基于 Cookie 的 SSO(同域):最原始的方式。多个子系统共享同一个父域(如 a.example.com、b.example.com),Cookie 域名设为 .example.com,session 信息存在共享的 Redis 中。简单但受限于同域。

基于 CAS 协议的 SSO:经典的 SSO 协议。核心流程:

code
用户访问 app1.com → 无 session → 重定向到 cas.com/login
用户在 cas.com 登录 → cas.com 设置 TGC(Ticket Granting Cookie)
cas.com → 重定向回 app1.com?ticket=ST-xxx
app1.com → 后端拿 ST 去 cas.com 验证 → 验证通过,创建本地 session
用户再访问 app2.com → 无 session → 重定向到 cas.com/login
cas.com 检测到 TGC 已存在 → 直接签发 ST 给 app2.com
app2.com → 后端验证 ST → 创建本地 session

CAS 中有两个核心票据:

  • TGT(Ticket Granting Ticket):存放在 CAS Server 的 session 中,通过浏览器 Cookie(TGC)关联
  • ST(Service Ticket):一次性票据,用于 CAS Client 验证用户身份

基于 OIDC 的 SSO:现代 SSO 的主流方案。与 CAS 类似,但用标准的 OIDC 协议。认证中心就是 OIDC Provider(OP),各个子系统是 Relying Party(RP)。

文章配图
文章配图

图6:SSO 单点登录完整流程时序图

5.2 单点注销(SLO)

单点登录解决了"登录"的问题,但单点注销同样重要。用户在一个系统登出,所有系统都应该登出。

实现方式:

  • 反向通道通知:认证中心在用户登出后,主动通知所有已登录的子系统清除 session
  • 前端 iframe 登出:在登出页面嵌入各个子系统的登出 iframe,利用浏览器自动携带 Cookie 完成登出
  • OIDC RP-Initiated Logout:OIDC 标准的登出流程,用户登出后被重定向到 OP 的 end_session_endpoint

5.3 SSO 的安全考量

  • 认证中心是单点:认证中心挂了,所有系统都无法登录。需要高可用部署。
  • Cookie 安全:TGC 这类 Cookie 必须设置 HttpOnly、Secure、SameSite 属性
  • 跨域问题:不同域之间的 SSO 需要处理跨域 Cookie 传递(SameSite=Lax 策略下,从 A 域跳转到 B 域时,A 域的 Cookie 不会被发送)

六、RBAC 与权限模型设计

认证解决"你是谁",授权解决"你能做什么"。权限模型设计是授权体系的核心。

6.1 RBAC(基于角色的访问控制)

RBAC 是最经典的权限模型,核心思想是用户 → 角色 → 权限的间接关联。

code
用户(User)──多对多──→ 角色(Role)──多对多──→ 权限(Permission)

RBAC 的三个层次:

RBAC0(基础模型):用户-角色-权限三元关系。一个用户可以有多个角色,一个角色可以包含多个权限。

RBAC1(角色继承):角色之间可以有继承关系。比如"部门主管"继承"普通员工"的所有权限,再额外拥有审批权限。角色继承树让权限管理更简洁。

RBAC2(约束模型):增加约束规则,包括:

  • 互斥角色:一个用户不能同时拥有"采购员"和"审批人"角色(防止自己审批自己)
  • 基数约束:一个角色最多分配给 N 个用户
  • 先决条件:要获得"高级工程师"角色,必须先拥有"工程师"角色

RBAC3(统一模型):RBAC1 + RBAC2 的组合。

文章配图
文章配图

图7:RBAC 权限模型实体关系

6.2 权限的粒度

权限设计需要覆盖三个维度:

功能权限(菜单/按钮/API):用户能看到哪些菜单、能点击哪些按钮、能调用哪些 API。这是最粗粒度的权限。

数据权限(行/列/字段):用户能看到哪些数据行、哪些列。比如"部门主管只能看到本部门的数据"、"普通员工看不到薪资字段"。

操作权限(CRUD):用户能对数据执行什么操作。比如"只读"、"可编辑"、"可删除"。

6.3 权限模型的数据表设计

一个典型的 RBAC 权限系统需要这些核心表:

sql
-- 用户表
CREATE TABLE users (id, username, ...);
-- 角色表
CREATE TABLE roles (id, name, parent_id, ...);  -- parent_id 支持角色继承
-- 权限表
CREATE TABLE permissions (id, resource, action, ...);
-- 关联表
CREATE TABLE user_roles (user_id, role_id);
CREATE TABLE role_permissions (role_id, permission_id);

6.4 权限校验的两种模式

前置校验(Pre-check):在请求处理前检查权限。比如 Spring Security 的 @PreAuthorize("hasRole('ADMIN')")。优点是快速失败,缺点是如果权限规则复杂,注解会变得很长。

后置校验(Post-check):在请求处理过程中检查权限。比如在 SQL 查询中拼接数据权限条件。优点是可以处理复杂的数据权限逻辑,缺点是权限逻辑散落在业务代码中。

最佳实践:功能权限用前置校验(注解/拦截器),数据权限用后置校验(AOP + SQL 注入器)。


七、认证授权的系统工程实践

7.1 网关层的统一认证

在微服务架构中,不应该在每个微服务中重复实现认证逻辑。正确的做法是:

code
用户请求 → API Gateway → 认证服务(验证 token)→ 路由到业务服务

网关负责:

  • 解析和验证 token(JWT 解析 + 公钥验签)
  • 将用户信息(user_id、roles、permissions)写入请求头传给下游
  • 拒绝未认证或 token 过期的请求

业务服务只需要从请求头中读取用户信息,不需要关心 token 的格式和验证逻辑。

文章配图
文章配图

图8:微服务网关统一认证架构

7.2 Token 刷新的最佳实践

Access token 短过期 + Refresh token 长过期是最推荐的模式:

code
Access Token:15 分钟过期
Refresh Token:7 天过期,支持 rotation

静默刷新:客户端在 access token 过期前几分钟主动用 refresh token 换新的 access token。如果 refresh token 也过期了,才跳转到登录页。

Refresh Token Rotation:每次使用 refresh token 时,授权服务器同时签发一个新的 refresh token 并使旧的失效。如果攻击者盗取了 refresh token 并使用成功,真正的用户下次使用旧的 refresh token 时会失败,系统可以检测到异常并撤销授权。

文章配图
文章配图

图9:Token 刷新生命周期

7.3 权限缓存的策略

权限数据是典型的"读多写少"场景,非常适合缓存。但缓存有延迟,带来一个经典问题:管理员修改了某用户的权限,这个用户什么时候才能看到新权限?

常见策略:

  • 缓存 TTL 5-10 分钟:权限变更在 10 分钟内生效,对大多数系统可接受
  • 主动失效:修改权限时发消息通知所有服务清除缓存
  • 用户重新登录:让用户登出后重新登录,强制刷新权限

文章配图
文章配图

图10:数据权限完整执行流程

7.4 安全审计

认证授权系统必须记录审计日志,至少包括:

  • 谁(user_id)
  • 什么时间(timestamp)
  • 从哪里(IP、User-Agent)
  • 做了什么操作(action)
  • 操作了什么资源(resource)
  • 结果如何(success/fail)

审计日志是事后追溯和合规检查的基础,不可删除、不可篡改。


八、总结

技术 解决的问题 核心机制 关键注意点
OAuth 2.0 授权委托 四种授权模式 + Access Token 不是认证协议;用授权码模式 + PKCE
OIDC 身份认证 在 OAuth 2.0 上加 ID Token ID Token 是给客户端看的,不是给 API 的
JWT Token 格式标准化 签名防篡改 不加密;用非对称算法;验证要严格
SSO 一次登录多处通行 集中认证 + 票据传递 认证中心高可用;单点注销同样重要
RBAC 权限管理 用户-角色-权限 功能权限前置,数据权限后置

九、面试官视角提问

以下问题不计入 40 分钟阅读时间,建议先独立作答,再对照答案思考。

Q1:OAuth 2.0 的授权码模式为什么需要两步(先拿 code,再用 code 换 token)?为什么不直接返回 token?

Q2:如果系统已经用了 OAuth 2.0,还需要引入 OIDC 吗?什么场景下 OIDC 是不可替代的?

Q3:JWT 的"无状态"特性在实际项目中真的是优势吗?什么时候反而会成为问题?

Q4:设计一个支持"部门主管可以看到本部门及下级部门数据"的数据权限方案,你会怎么做?

Q5:SSO 单点登录中,如果认证中心宕机,正在使用中的用户是否会被影响?如何设计容灾方案?

Q6:Refresh Token Rotation 真的能完全防止 token 泄露吗?它有什么局限性?


A1: 授权码模式的两步设计是 OAuth 2.0 最核心的安全设计,本质上是前后端分离的安全模型。

第一步(前端→授权服务器):用户在浏览器中跳转到授权服务器,授权后授权服务器通过浏览器重定向将授权码返回给前端。这个重定向 URL 是公开的,可能被浏览器历史记录、Referer 头、日志等泄露。授权码是一次性的,且必须配合 client_secret 才能使用,单靠授权码本身毫无价值。

第二步(后端→授权服务器):后端服务拿到授权码后,在服务器端通过 HTTPS 直连授权服务器,用授权码 + client_id + client_secret 换取 access token。这一步发生在服务器端,token 不经过浏览器,彻底避免了前端泄露风险。

如果直接返回 token(这就是隐式模式),token 会暴露在浏览器 URL fragment 中,任何能读取浏览器地址栏的脚本都能窃取。这也是为什么 OAuth 2.1 草案已经废弃了隐式模式,强制要求使用授权码模式 + PKCE。

面试追问:为什么 PKCE 可以让纯前端应用(SPA)也能安全地使用授权码模式?答案是 PKCE 用 code_challenge/code_verifier 替代了 client_secret,让没有后端存储密钥能力的 SPA 也能完成"只有原发起者才能用 code 换 token"的验证。


A2: 需要。OAuth 2.0 和 OIDC 解决的是不同层次的问题,引入 OIDC 不是替代 OAuth 2.0,而是在其之上叠加一层身份认证。

必须引入 OIDC 的场景:

  1. 单点登录(SSO):OAuth 2.0 只告诉你"这个用户授权了某个 scope",但不会告诉你"这个用户是谁"。SSO 需要用户身份信息,这正是 OIDC 的 ID Token 提供的。
  2. 多应用共享用户身份:应用 A 和应用 B 需要确认它们拿到的是同一个用户的 token,OIDC 的 sub(subject)字段提供标准化的用户唯一标识。
  3. 需要用户 profile 信息:OIDC 的 UserInfo Endpoint 标准化了获取用户姓名、邮箱、头像等信息的接口,而不需要每个 OAuth 2.0 实现各自定义。
  4. 合规要求:GDPR、等保等合规场景需要标准化的身份认证协议,OIDC 是 ISO 认证的标准。

如果没有 OIDC 会怎样:每个 OAuth 2.0 授权服务器需要自己定义如何获取用户信息——字段名、API 路径、Token 格式都是自定义的,导致客户端需要针对每个授权服务器做适配。OIDC 把这件事标准化了。

面试追问:OIDC 的 ID Token 和 Access Token 的区别是什么?ID Token 是 JWT 格式,受众是客户端,用于证明"用户是谁";Access Token 的受众是资源服务器,用于证明"客户端被授权做什么"。把 ID Token 当 Access Token 用是常见错误——ID Token 没有 scope 字段,资源服务器无法用它做权限判断。


A3: JWT 的"无状态"是一把双刃剑,它的优势在某些场景下会变成劣势。

优势侧:

  • 服务端不需要存储 session,减少存储和查询开销
  • 天然支持分布式——任何服务节点只要有公钥就能验证
  • 适合微服务间的服务间认证(service-to-service)

劣势侧——什么时候"无状态"会成为问题:

  1. 无法主动失效:这是最大的痛点。管理员想踢掉一个用户,或者发现 token 泄露需要立刻撤销——JWT 本身做不到。你只能等它过期。解决方案是引入 token 黑名单(Redis),但这又回到了"有状态"。
  2. Token 体积大:JWT 包含所有 claims,每次 HTTP 请求都要携带,相比 session cookie(只有 session ID),网络开销更大。
  3. 信息泄露风险:JWT 的 payload 只是 Base64 编码而非加密,任何人都能解码查看。如果把敏感信息(如用户手机号、权限详情)放进 payload,等于明文传输。
  4. 版本升级困难:如果 JWT 的 claims 结构发生变更,已在线的旧 token 还会继续使用旧结构,直到过期。短的 expiry 时间可以缓解,但增加了刷新频率。

实际项目中的最佳实践:JWT 适合"短期 + 读多写少"的场景。Access Token 15 分钟过期,用 JWT 无状态验证;Refresh Token 用不透明 token(opaque token),服务端存储,可以随时撤销。这样既享受了 JWT 的验证性能,又保留了控制能力。

面试追问:如果要实现"管理员修改某用户权限后,该用户立刻生效",你会怎么设计?答案:不能只靠 JWT。可以结合 Redis 存储用户权限版本号,每次鉴权时比较 JWT 中的版本号和 Redis 中的版本号,不一致则拒绝。


A4: 这是一个典型的层级数据权限问题,核心是"如何高效地查询一个用户能访问哪些部门的数据"。

方案设计:

第一步:部门树建模

sql
-- 部门表,用 parent_id 表示层级关系
CREATE TABLE departments (
    id BIGINT PRIMARY KEY,
    name VARCHAR(100),
    parent_id BIGINT,  -- 上级部门 ID
    path VARCHAR(500)  -- 物化路径,如 '/1/3/15/'
);

第二步:获取用户可访问的部门集合

  • 用户是部门 15 的主管 → 可以访问部门 15 及其所有子部门
  • 通过 path 字段快速查询:SELECT id FROM departments WHERE path LIKE '/1/3/15/%' OR id = 15
  • 这个结果可以缓存,因为部门结构变化不频繁

第三步:在 SQL 查询中注入数据权限

sql
-- 原始 SQL(业务层写的)
SELECT * FROM orders WHERE status = 'pending'

-- 注入数据权限后(权限拦截器自动拼接)
SELECT * FROM orders WHERE status = 'pending'
AND dept_id IN (15, 16, 17, 18, 19)  -- 用户可访问的部门 ID 列表

第四步:实现方式

  • 使用 MyBatis 拦截器或 AOP 切面,在 SQL 执行前自动注入数据权限条件
  • 在注解中标记哪些 Mapper 方法需要数据权限过滤:
java
@DataScope(deptColumn = "dept_id", userColumn = "create_by")
List<Order> selectOrderList(OrderQuery query);

关键细节:

  • 部门数量多时(如几百个部门),IN (...) 性能可能下降,可改用 EXISTS 子查询
  • 部门结构调整后,需要清除权限缓存
  • 部分接口可能需要"本人数据"权限而非"部门数据"权限,需要区分处理

面试追问:如果需求变成"部门主管可以看本部门数据,但某条数据被标记为敏感,只有高级主管能看",你的方案如何扩展?答案:引入"数据安全级别"字段,在权限过滤时增加安全级别条件。


A5: 分两个层面回答:

对已登录用户的影响:

  • 如果系统使用 JWT 做认证,已登录用户不受影响。因为 JWT 是无状态的,业务服务用本地公钥即可验证 token,不需要每次都访问认证中心。
  • 如果系统使用 session 机制(如基于 Redis 的分布式 session),一般也不受影响。因为 session 在认证中心宕机前已经创建并存放在 Redis 中,业务服务直接从 Redis 读取即可。
  • 受影响的操作:token 刷新(refresh token 过期时需要认证中心)、新增登录、权限变更。

容灾方案:

  1. 认证中心多活部署:至少两个实例,前面挂负载均衡。这是第一道防线。
  2. Access Token 缓存策略:认证中心将公钥(JWKS)发布到 CDN 或所有业务服务本地,业务服务不依赖认证中心来验证 token。
  3. 降级方案:认证中心完全不可用时,业务服务可以进入"宽容模式"——延长已有 token 的有效期,允许用户继续使用,但拒绝新增登录。等认证中心恢复后,再进行权限校验。
  4. Refresh Token 的容灾:将 Refresh Token 的存储(通常是数据库)部署为高可用集群,与认证中心应用层解耦。

面试追问:如果认证中心的数据库都宕机了怎么办?答案:这是极端场景,需要数据库层面的高可用——主从复制 + 自动故障转移。如果连数据库都不可用,系统应该进入只读降级模式,允许已认证用户继续使用,拒绝所有写操作和新增登录。


A6: Refresh Token Rotation 不能完全防止 token 泄露,它解决的是泄露被发现的问题,而非泄露本身。

Rotation 的工作原理:每次使用 refresh token 时,服务器同时签发新的 refresh token 并使旧的失效。如果攻击者偷了 refresh token 并使用,真正的用户下次使用旧 token 时会失败,系统检测到"旧 token 被使用但已失效"→ 判定为泄露 → 撤销该用户的所有授权。

局限性:

  1. 时间窗口:攻击者如果抢在用户之前使用 refresh token,用户才是那个"被发现"的一方。Rotation 只能检测,不能阻止攻击者先拿到新 token。
  2. 并发竞争:如果用户和攻击者几乎同时使用 refresh token,谁先到服务器谁赢。如果攻击者先到,攻击者拿到新 token 和新的 refresh token,用户反而被踢出。
  3. 不解决 access token 泄露:Rotation 只保护 refresh token,access token 仍然在每次请求中传输,如果被中间人截获,直到过期前攻击者都可以使用。
  4. 实现复杂度:真正的 Rotation 需要在服务端维护 refresh token 家族(family),记录每个 token 的父子关系。如果实现不当,可能会错误地撤销合法用户。

增强方案:

  • Token Binding:将 token 与 TLS 会话绑定,token 只能在创建它的 TLS 连接上使用
  • 检测异常行为:refresh token 从 IP A 使用,但 access token 从 IP B 使用 → 触发告警
  • Sender-Constrained Token:如 DPoP(Demonstration of Proof-of-Possession),要求每次使用 token 时提供持有证明

面试追问:如果要求"token 泄露后 0 秒内阻止攻击者使用",你怎么办?答案:实际上做不到"0 秒"。能做到的是:1)缩短 access token 有效期(如 5 分钟);2)使用 mTLS 做传输层绑定;3)使用 DPoP 做应用层绑定。终极方案是硬件安全模块(HSM)存储私钥,token 与设备绑定。