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 状态转换:
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 中存储授权码时,建议使用哈希结构存储授权码的元数据(client_id、redirect_uri、scope、用户标识等),兑换时校验所有字段。同时设置合理的 TTL(建议 60~300 秒),过期后 Redis 自动删除键,无需额外清理逻辑。