4. 成功响应结构:成功刷新后返回的 access_token, refresh_token, expires_in, token_type 字段详解
当客户端使用有效的刷新令牌(Refresh Token)向令牌端点发起刷新请求时,授权服务器在验证通过后会返回一个标准的成功响应。该响应体通常采用 JSON 格式,包含四个核心字段:access_token、refresh_token、expires_in 和 token_type。理解每个字段的语义、约束和最佳实践,是正确实现刷新流程和进行故障定位的基础。
核心原则:刷新令牌流程的本质是“用旧凭证换取新凭证”。服务器在返回新令牌的同时,可以选择返回一个新的刷新令牌(轮换策略)或保持原有刷新令牌不变(非轮换策略)。响应结构中的
refresh_token 字段行为取决于服务器的策略配置。
4.1 标准成功响应示例
以下是一个典型的成功刷新响应体(JSON 格式):
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "dGhpcyBpcyBhIHJlZnJlc2ggdG9rZW4..."
}
4.2 字段详解与约束
| 字段名 | 类型 | 必需 | 说明与约束 |
|---|---|---|---|
access_token |
字符串 | 是 | 授权服务器颁发的新访问令牌。客户端使用此令牌访问受保护资源。通常为 JWT 或不透明令牌格式。 |
token_type |
字符串 | 是 | 令牌类型,固定为 Bearer(RFC 6750)。客户端在后续请求中需将令牌放入 Authorization: Bearer <token> 头。 |
expires_in |
整数 | 是 | 访问令牌的有效期,单位为秒。客户端应据此决定何时需要再次刷新。推荐值:3600(1小时)至 86400(24小时)。 |
refresh_token |
字符串 | 条件必需 | 新的刷新令牌。当服务器采用轮换策略时必返回;采用非轮换策略时可能不返回或返回原值。客户端应始终使用最新收到的刷新令牌。 |
调试提示:在故障定位时,首先检查
expires_in 值是否合理。如果服务器返回的 expires_in 为 0 或负数,说明令牌端点配置异常或响应被篡改。同时,token_type 必须严格等于 Bearer(大小写敏感),任何变体(如 bearer、BearerToken)都应视为错误。
4.3 字段间的逻辑关系
四个字段并非孤立存在,它们共同构成了客户端后续行为的依据。以下关系图展示了字段之间的依赖与约束:
4.4 字段行为对比:轮换策略 vs 非轮换策略
刷新令牌的返回行为直接决定了客户端的安全模型。下表对比了两种常见策略下的字段差异:
| 对比维度 | 轮换策略(Rotation) | 非轮换策略(Non-Rotation) |
|---|---|---|
refresh_token 是否返回 |
每次刷新都返回新值 | 可能不返回,或返回与请求时相同的值 |
| 旧令牌有效性 | 旧刷新令牌立即失效 | 旧刷新令牌持续有效,直到过期 |
| 安全风险 | 低(令牌泄露窗口短) | 高(令牌可被长期重用) |
| 客户端实现复杂度 | 中等(需管理令牌版本) | 低(只需存储一个令牌) |
| 推荐场景 | 高安全要求的生产环境 | 低风险内部系统或原型开发 |
故障定位要点:如果客户端在刷新后未收到新的
refresh_token,但服务器实际采用了轮换策略,客户端将丢失后续刷新能力。此时应检查服务器响应中是否包含 refresh_token 字段,以及客户端是否正确存储了该字段。常见错误是客户端只解析 access_token 而忽略了 refresh_token。
4.5 字段解析与验证清单
在客户端实现中,建议对成功响应执行以下验证步骤:
- 验证 JSON 结构完整性:确保响应体是合法 JSON,且包含
access_token、token_type、expires_in三个必需字段。 - 验证
token_type值:严格检查是否为Bearer(区分大小写)。 - 验证
expires_in合理性:检查值是否为正整数,且大于 0。建议设置最小阈值(如 60 秒),防止服务器返回异常短的有效期。 - 处理
refresh_token:如果响应中包含此字段,立即替换本地存储的旧刷新令牌;如果不包含,保留现有刷新令牌。 - 记录时间戳:记录收到响应时的本地时间戳,结合
expires_in计算令牌的实际过期时间点,用于后续的预刷新调度。
// 客户端验证伪代码示例
function handleRefreshResponse(response) {
if (!response.access_token || !response.token_type || !response.expires_in) {
throw new Error('Invalid refresh response: missing required fields');
}
if (response.token_type !== 'Bearer') {
throw new Error('Invalid token_type: expected Bearer');
}
if (response.expires_in <= 60) {
console.warn('Warning: expires_in is too short:', response.expires_in);
}
// 更新存储
storage.set('access_token', response.access_token);
storage.set('expires_at', Date.now() + response.expires_in * 1000);
if (response.refresh_token) {
storage.set('refresh_token', response.refresh_token);
}
}
4.6 常见响应异常与排查方向
| 异常现象 | 可能原因 | 排查方向 |
|---|---|---|
缺少 access_token |
服务器内部错误或响应格式错误 | 检查 HTTP 状态码是否为 200;检查响应 Content-Type 是否为 application/json |
token_type 为 bearer(小写) |
服务器实现未严格遵循 RFC 6749 | 联系服务提供商修正;客户端可做大小写容错处理 |
expires_in 为 0 或负数 |
服务器时钟异常或配置错误 | 检查服务器时间同步;检查令牌端点配置 |
未收到 refresh_token 但后续刷新失败 |
服务器采用轮换策略但客户端未更新 | 确认服务器策略;检查客户端是否忽略了响应中的 refresh_token 字段 |
最佳实践:在开发阶段,建议在客户端日志中完整记录每次刷新响应的原始 JSON 体(注意脱敏处理)。这有助于在故障发生时快速比对字段是否存在、值是否异常,是定位“响应结构问题”最直接的手段。