4. 授权码错误码体系设计:常见错误码分类

在 OAuth 2.0 授权码流程中,授权服务器(AS)通过标准错误响应向客户端传递失败原因。错误码体系的设计直接影响客户端与资源服务器的容错逻辑、重试策略以及安全审计。本章系统梳理授权码模式下最常见的错误码分类,并给出各错误码的语义、触发场景与推荐处理方式。

4.1 错误码分类总览

OAuth 2.0 核心规范(RFC 6749)定义了 6 个标准错误码,授权码流程主要涉及其中 4 个。此外,实践中还扩展了若干与授权码生命周期、客户端状态相关的错误码。下表按错误性质分为四大类:

分类 错误码 HTTP 状态码 典型触发场景
授权码无效 invalid_grant 400 Bad Request 授权码过期、已使用、不匹配
请求被拒绝 access_denied 302 Found / 400 用户拒绝授权、资源拥有者取消
客户端认证失败 invalid_client 401 Unauthorized client_id 或 client_secret 错误
请求参数错误 invalid_request 400 Bad Request 缺少必填参数、参数格式错误
作用域无效 invalid_scope 400 Bad Request 请求的 scope 未注册或超出授权范围
授权码已撤销 authorization_revoked 400 Bad Request 用户或管理员主动撤销授权码
设计要点:错误码的 HTTP 状态码应与语义一致。invalid_grant 使用 400 而非 401,因为客户端凭证本身有效,只是授权码无效;invalid_client 使用 401 并应包含 WWW-Authenticate 头。

4.2 核心错误码详解

4.2.1 invalid_grant —— 授权码无效

这是授权码流程中最常见的错误码,表示授权服务器无法接受客户端提供的授权码。具体原因包括:

  • 授权码已过期:授权码默认有效期通常为 60~600 秒,超时后任何兑换请求均返回 invalid_grant。
  • 授权码已使用:授权码为一次性凭证,成功兑换 access_token 后立即失效,重复使用会触发此错误。
  • 授权码与客户端不匹配:授权码颁发时绑定了特定 client_id,其他客户端尝试兑换时拒绝。
  • redirect_uri 不匹配:兑换请求中的 redirect_uri 与授权请求时不一致。
安全提示:授权服务器应记录 invalid_grant 的触发次数,同一授权码连续失败超过阈值(如 3 次)应触发告警,防止暴力枚举攻击。

4.2.2 access_denied —— 访问被拒绝

该错误码表示资源拥有者(用户)或授权服务器明确拒绝了授权请求。与 invalid_grant 不同,access_denied 发生在授权码颁发阶段,而非兑换阶段。

  • 用户主动取消:用户在授权页面点击“拒绝”或关闭浏览器。
  • 策略限制:授权服务器根据风险评分、IP 黑名单、设备指纹等判定请求不合法。
  • 资源拥有者身份不足:用户未满足资源服务器要求的属性(如年龄、地域限制)。

4.2.3 invalid_client —— 客户端认证失败

当客户端使用 client_secret 或其他凭证向授权服务器认证时,若凭证无效则返回此错误。常见原因:

  • client_id 不存在或已停用。
  • client_secret 错误或已轮换。
  • 客户端认证方式不匹配(如要求 client_secret_basic 但使用了 client_secret_post)。

4.2.4 invalid_request —— 请求参数错误

这是一个通用错误码,表示请求缺少必要参数、包含非法参数或参数格式错误。在授权码流程中典型场景包括:

  • 缺少 response_type 或值不为 code。
  • redirect_uri 未注册或包含 fragment。
  • state 参数缺失(推荐强制校验)。
  • 重复参数或参数值包含非法字符。

4.3 错误码与 Redis 缓存联动关系

在授权码缓存方案中,错误码直接映射到 Redis 中的键状态。下图展示了授权码从颁发到兑换过程中,各错误码对应的 Redis 状态转换:

授权码错误码与 Redis 状态转换图 授权码生命周期与错误码映射 授权码颁发 Redis 存储 兑换请求 SET code:xxx GET code:xxx invalid_grant access_denied invalid_request 过期/已使用 用户拒绝 参数缺失 Redis 缓存联动规则: • 授权码颁发时写入 Redis,设置 TTL(如 300 秒) • 兑换成功时 DEL 键,防止重复使用 • 返回 invalid_grant 时检查键是否存在:不存在→已过期;存在但值不匹配→已使用

4.4 错误码响应格式与示例

授权服务器必须按照 RFC 6749 返回 JSON 格式的错误响应,并包含 error 字段。可选字段 error_description 和 error_uri 用于提供人类可读的说明和文档链接。

HTTP/1.1 400 Bad Request
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
  "error": "invalid_grant",
  "error_description": "The authorization code has expired or has been revoked.",
  "error_uri": "https://auth.example.com/docs/errors#invalid_grant"
}
安全警告:错误描述不应泄露过多内部信息。例如,避免返回“授权码已使用,剩余次数为0”或“授权码在 Redis 中不存在”等细节,防止攻击者利用错误信息进行枚举攻击。

4.5 错误码处理策略对比

客户端收到不同错误码时应采取差异化的处理策略。下表总结了推荐的处理方式:

错误码 是否重试 重试策略 用户提示
invalid_grant 否 重新发起授权请求 “授权已过期,请重新登录”
access_denied 否 引导用户重新授权 “您已取消授权,如需使用请重新授权”
invalid_client 有限重试 最多 1 次,检查凭证后重试 “客户端配置错误,请联系管理员”
invalid_request 否 修复请求参数后重试 “请求参数错误,请稍后重试”
invalid_scope 否 调整 scope 后重新发起 “请求的权限不可用”
authorization_revoked 否 重新获取用户授权 “授权已被撤销,请重新授权”

4.6 错误码与 Redis 缓存键设计

在 Redis 缓存方案中,错误码的判定依赖于缓存键的状态。下图展示了授权码缓存键的完整状态机:

授权码 Redis 缓存键状态机 未颁发 有效 已使用 已过期 颁发 兑换 TTL 到期 Redis 键状态与错误码对应关系: • 键不存在(未颁发或已过期)→ invalid_grant • 键存在但值不匹配(已使用)→ invalid_grant • 键存在且值匹配 → 正常兑换,返回 access_token
最佳实践:在 Redis 中存储授权码时,建议使用哈希结构存储授权码的元数据(client_id、redirect_uri、scope、用户标识等),兑换时校验所有字段。同时设置合理的 TTL(建议 60~300 秒),过期后 Redis 自动删除键,无需额外清理逻辑。