为什么需要 Access Token 和 ID Token 两个令牌
一句话概括:Access Token 用于"授权"(Authorization),解决"能访问什么资源"的问题;ID Token 用于"认证"(Authentication),解决"这个用户是谁"的问题。 两者对应的是两个不同的协议目标、不同的消费方、不同的生命周期,混用会直接破坏安全模型。
一、背景:OAuth 2.0 与 OpenID Connect 的分工
OAuth 2.0 生来只做一件事------委托授权:用户把自己的资源访问权委托给第三方应用,应用拿着 Access Token 去调用资源服务器(API)。它并不关心"用户是谁",甚至可以在完全没有用户参与的情况下(如客户端凭据模式)签发 Access Token。
但在实际场景中,应用往往还想知道"登录的人到底是谁"。早期开发者把 OAuth 2.0 曲解为认证协议(用 Access Token 去调用用户信息接口),结果催生了大量"令牌混淆"类的安全漏洞。OpenID Connect(OIDC)正是为了解决这个问题,在 OAuth 2.0 之上加了一层身份认证协议,其核心产物就是 ID Token------一个签名的 JWT,直接向客户端应用声明"认证已发生,用户是谁"。
所以这两个 Token 的并存,本质上是授权(OAuth 2.0)与认证(OIDC)两条职责线的分离。
二、两者对比
| 维度 | ID Token | Access Token |
|---|---|---|
| 所属协议 | OpenID Connect(认证层) | OAuth 2.0(授权层) |
| 核心用途 | 向客户端证明"用户是谁、已完成认证" | 允许客户端访问受保护的资源(API) |
| 消费方 | 客户端应用本身 | 资源服务器 |
| 受众标识 | 客户端的 Client ID | 目标 API 的标识 |
| 格式 | 固定为 JWT | JWT 或不透明字符串 |
| 关键载荷 | sub、iss、aud、auth_time、nonce、用户声明 |
scope、aud、exp、客户端标识等 |
| 典型操作 | 客户端本地验签、建立会话、渲染用户信息 | 随请求发给 API,由 API 校验 aud/scope/exp 后做授权决策 |
| 有效期/流转 | 通常较短,只在登录时消费,不向下游传递 | 会在客户端与多个 API 之间频繁传递,靠 Refresh Token 续期 |
三、ID Token:给客户端看的"身份证明"
ID Token 的定位是一份由身份提供方签名的 JWT,客户端拿到后本地验签即可确认三件事:令牌来自可信签发方、用户确实通过了认证、令牌没有被篡改。
它的正确用法仅限于客户端内部:
- 建立本地会话 :解析
sub确定用户身份,创建登录态; - 个性化展示 :直接读取
name、email等声明渲染界面,省去再调一次 userinfo 接口的开销; - 防重放 :配合请求时传入的
nonce声明,防止令牌被重放。
特别要注意aud声明------它写的是客户端自己的 Client ID。这个设计意味着:ID Token 从协议层面就只发给客户端自己,不是给 API 用的凭据。
四、Access Token:给 API 看的"通行证"
Access Token 是一份委托授权凭据:它代表"某个用户(或客户端)授权了某个应用,以特定的 scope 访问某个资源"。
它的正确用法是随请求发送到资源服务器,由 API 侧完成校验:
- 验证签名与签发方;
- 核对
aud是否指向本 API; - 检查
scope是否覆盖本次操作所需权限; - 检查
exp过期时间。
OAuth 2.0 核心规范对 Access Token 的格式没有任何约束,它可以是 JWT 也可以是不透明字符串------因为 API 与授权服务器之间可以约定如何解析令牌,客户端只需要原样转发即可。这种不透明性还有个附带好处:客户端拿到的令牌即使泄露,攻击者也读不出内部信息,只有通过 introspection 才能解析。
五、为什么必须拆成两个:深层设计逻辑
1. 认证与授权的生命周期不同。 用户登录的频率远低于 API 调用的频率。ID Token 在登录时消费一次、用于建立会话后基本不再流转;Access Token 则需要在客户端与各 API 之间高频传递、过期后通过 Refresh Token 无感续期。若合二为一,要么身份信息跟着令牌被反复传播扩大泄露面,要么强制用户频繁重新登录。
2. 受众隔离是安全边界。 ID Token 的 aud 是客户端,Access Token 的 aud 是 API。每个令牌只在"预期接收方"那里被验证通过,出了这个圈子就无效。这种设计使得即使令牌被意外转发,另一端也会因受众不匹配而拒绝它。
3. 职责分离让各参与方只需最小信息。 API 做授权决策时通常只需要 scope 和用户标识,不需要姓名、邮箱这类身份敏感数据;客户端做认证时只需要身份声明,不需要权限细节。把两类信息装进不同令牌,本质上是最小权限原则 在协议层的体现------令牌中泄露的信息越少,泄露造成的损害越小。
4. 灵活性。 有些流程只要认证不要授权(如纯前端登录),有些只要授权不要用户身份(如后台服务的客户端凭据模式)。两种令牌独立签发,才能按需组合。
六、典型的误用及其风险
实践中最常见的错误是把两者当成"同一个东西的两种格式":
- 拿 ID Token 去调 API :因为 ID Token 的
aud是客户端而非 API,且没有机制把它绑定到客户端与 API 之间的通道上------攻击者一旦窃取 ID Token,就能直接拿它去冒充调用 API。正确做法是 API 只接受 Access Token,并严格校验其aud与scope。 - 在 API 里解析 ID Token 做授权决策:ID Token 表达的是"认证发生了",不表达"被授权了什么操作"。授权依据必须来自 Access Token 的 scope。
- 把 Access Token 当用户信息来源 :Access Token 未必包含用户资料,获取用户资料应走 ID Token 或 userinfo 端点。
一个正确的调用链可以概括为:应用读 ID Token 确认"谁登录了",再把 Access Token 发给 API 去获取"能做什么",两条线各走各的,永不交叉。
小结
双 Token 设计不是协议的历史包袱,而是刻意为之的架构选择:ID Token 服务于 OIDC 的认证目标、面向客户端;Access Token 服务于 OAuth 2.0 的授权目标、面向资源服务器。受众不同、载荷不同、生命周期不同、验证方不同------正是这些差异共同构成了现代身份体系中认证与授权的清晰边界。