3. 授权码请求与颁发:参数规范与凭证生命周期

授权码请求是 OAuth2.0 授权码模式中客户端与授权服务器之间的第一次关键交互。本节将深入解析请求参数的标准定义、授权码的生成策略以及存储合规要求,帮助审计人员建立参数级与凭证级的检查基线。

3.1 授权码请求参数详解

客户端在发起授权请求时,必须按照 RFC 6749 Section 4.1.1 构造请求。以下表格列出了五个核心参数及其审计关注点:

参数 是否必选 合法值/格式 审计检查要点
response_type 必选 固定值 code 是否被篡改为 token 或 id_token,防止隐式模式混用
client_id 必选 注册时颁发的唯一标识符 是否为空、是否在授权服务器白名单中、是否与 redirect_uri 绑定
redirect_uri 条件必选 已注册的 URI(支持精确匹配或模式匹配) 是否与注册 URI 完全一致(防止开放重定向攻击)
scope 可选 空格分隔的权限标识符(如 openid profile email) 是否超出客户端已授权的 scope 范围;是否包含敏感 scope
state 强烈推荐 不可预测的随机字符串(至少 128 位熵) 是否存在;是否在回调时被校验;是否使用固定值或可预测值
审计红线: 若授权服务器允许 redirect_uri 参数与注册 URI 存在差异(如仅验证域名),则构成开放重定向漏洞,属于严重不合规项。审计时应重点检查授权服务器的 URI 校验逻辑是否采用“完全匹配”策略。

3.2 授权码请求流程(SVG)

下图展示了客户端通过用户代理(浏览器)向授权服务器发起授权码请求的完整交互过程:

授权码请求交互流程 客户端 (Client) 用户代理 (浏览器) 授权服务器 (Authorization Server) ① 构造授权请求 ?response_type=code &client_id=xxx &redirect_uri=... &scope=openid &state=abc123 ② 302 重定向至 授权服务器 ③ 携带参数请求 GET /authorize ④ 参数校验 client_id / redirect_uri / scope ⑤ 生成授权码 code=随机字符串 ⑥ 302 重定向至 redirect_uri?code=xxx&state=abc123

3.3 授权码的生成策略

授权码(Authorization Code)是授权服务器颁发给客户端的临时凭证,其安全性直接影响整个协议的可信度。生成策略应满足以下合规要求:

  • 高熵随机性: 必须使用密码学安全的伪随机数生成器(CSPRNG),推荐长度至少 128 位(如 32 字节的随机字节序列经 Base64URL 编码)。
  • 不可预测性: 禁止使用自增序列、时间戳、用户 ID、客户端 ID 等可预测信息作为授权码的组成部分。
  • 一次性使用: 授权码被成功兑换令牌后必须立即失效,且失效记录应持久化以防止重放攻击。
  • 短生命周期: 授权码的有效期通常为 30 秒至 10 分钟,超过 TTL 后自动失效。审计时应检查授权服务器是否强制实施过期策略。
生成示例(Python):
import secrets
import base64

def generate_authorization_code() -> str:
    """生成符合 OAuth2.0 安全要求的授权码"""
    random_bytes = secrets.token_bytes(32)  # 256 位熵
    code = base64.urlsafe_b64encode(random_bytes).rstrip('=').decode('ascii')
    return code

# 示例输出: "5xP0mR8qL3vN9wK2yB7cJ4fA1gH6sD0e"

3.4 授权码的存储与生命周期管理

授权码在授权服务器端的存储方式直接影响审计追踪能力。以下是推荐的存储模型与审计字段:

字段名 类型 说明 审计用途
code VARCHAR(128) 授权码明文(或哈希值) 唯一标识一次授权请求
client_id VARCHAR(64) 发起请求的客户端标识 追溯客户端身份
redirect_uri VARCHAR(512) 请求中使用的重定向 URI 校验回调一致性
scope VARCHAR(256) 请求的权限范围 验证令牌颁发时的 scope 是否一致
state VARCHAR(128) 客户端提供的防伪造状态值 审计 CSRF 防护是否生效
user_id VARCHAR(64) 授权用户的唯一标识 用户级授权审计
expires_at DATETIME 授权码过期时间 检查是否在有效期内使用
used_at DATETIME NULL 授权码被兑换的时间 检测重复兑换行为
created_at DATETIME 授权码创建时间 审计时间窗口

3.5 授权码存储与校验流程(SVG)

下图展示了授权码从生成到兑换的完整生命周期,以及审计系统如何介入检查:

授权码生命周期与审计检查点 ① 授权码生成 CSPRNG 256位 Base64URL 编码 ② 持久化存储 数据库/Redis TTL=5分钟 ③ 令牌兑换 校验 code + client_secret 标记 used_at ④ 失效 审计检查点:检查授权码是否被重复兑换、是否在过期后仍被接受、是否使用可预测生成算法 审计日志记录字段 • 授权码哈希值(不记录明文) • 客户端 ID + 用户 ID + 请求 IP • 生成时间 / 兑换时间 / 过期时间 • 请求的 scope 列表 • state 参数值(用于事后 CSRF 审计) • 兑换结果(成功/失败及失败原因)

3.6 常见审计发现与整改建议

审计发现 #1:授权码使用可预测模式
某系统授权码格式为 CODE_20250321_0001,包含日期和自增序号。攻击者可枚举授权码,提前兑换他人授权码。
整改: 替换为 CSPRNG 生成的随机字符串,并确保授权码与用户会话强绑定。
审计发现 #2:授权码未设置过期时间
授权码在数据库中永久有效,攻击者可利用泄露的授权码在任意时间点兑换令牌。
整改: 设置 TTL 不超过 10 分钟,并在兑换时检查 expires_at 字段。
审计发现 #3:state 参数缺失或固定值
客户端未传递 state 参数,或使用固定字符串 "123456",导致授权码回调存在 CSRF 攻击风险。
整改: 强制要求客户端生成并校验 state 参数,授权服务器应在回调时验证 state 一致性。

3.7 审计检查清单

在进行授权码请求与颁发环节的合规审计时,建议逐项核对以下清单:

  1. 参数完整性: 授权请求是否包含 response_type=code 和有效的 client_id?
  2. 重定向 URI 校验: 授权服务器是否对 redirect_uri 执行完全匹配校验?
  3. Scope 边界: 授权服务器是否校验请求的 scope 未超出客户端注册时允许的范围?
  4. State 强制使用: 授权服务器是否要求客户端必须提供 state 参数?
  5. 授权码熵值: 授权码生成是否使用 CSPRNG,且长度不低于 128 位?
  6. 一次性使用: 授权码被兑换后是否立即标记为已使用,并拒绝重复兑换?
  7. 过期策略: 授权码是否设置了合理的 TTL(建议 ≤ 10 分钟)?
  8. 审计日志: 授权码的生成、兑换、失效事件是否被完整记录,且日志不可篡改?