OAuth2.0协议:授权码模式、客户端模式、刷新令牌与安全实践

OAuth2.0 这东西,说白了就是一个「令牌分发协议」。它不关心你是谁,只关心你有没有令牌。我在审计 SDK 的越权漏洞时,发现 90% 的问题都出在对这个协议的理解不深,或者实现上有偏差。今天我们就来把它的几种核心流程掰开了讲清楚。

授权码模式:最安全的首选

这是 OAuth2.0 里最安全、也是最常用的模式。它专门为有后端服务器的应用设计。

核心流程(简化版)

  1. 用户点击“第三方登录”,浏览器跳转到授权服务器。
  2. 用户同意授权,授权服务器返回一个临时的「授权码」到你的后端回调地址。
  3. 你的后端拿着这个授权码,去授权服务器换「访问令牌」。
  4. 之后用访问令牌请求资源。

我的习惯

我在审计 SDK 时,特别关注第 2 步和第 3 步之间。授权码必须是一次性的,而且要绑定客户端的 client_id 和回调地址。为什么?因为如果在传输中授权码被截获,攻击者用它也换不到令牌——因为他的回调地址不匹配。这就是授权码模式的安全精髓。

我在项目中遇到过一种情况,某个 SDK 的回调地址参数没有做严格校验,导致攻击者可以通过修改 redirect_uri 来劫持授权码。嗯,这其实是一个经典的「回调地址注入」漏洞。

客户端模式:适合内部服务

这种模式没有用户参与,是服务与服务之间的授权。很多微服务架构中会用这个。

  • 适用场景:服务器之间的通信、定时任务、后台批处理
  • 特点:直接使用 client_id 和 client_secret 换取令牌,没有用户交互
  • 安全风险:client_secret 一旦泄露,攻击者就能伪造请求

避坑指南

我曾经在一个项目里看到,开发人员把客户端模式的 client_secret 硬编码在移动端 App 里。你想想看,App 是可以被反编译的。这种模式下,secret 一旦被提取,整个服务接口就相当于裸奔了。

我建议:客户端模式的 secret 只能放在后端配置中心或密钥管理服务里,不能出现在前端或客户端代码中。

刷新令牌:保持长尾会话

访问令牌通常有过期时间,短则 15 分钟,长则 1 小时。刷新令牌就是用来续命的。

对比项 访问令牌 刷新令牌
用途 访问资源 换新的访问令牌
有效期 短(分钟/小时级) 长(天/周级)
泄露后风险 高(直接可用) 中(需要配合 client_secret)
是否可撤销 通常不可 可以(服务端主动使失效)

我的实践

我建议在实现刷新逻辑时,一定要做「刷新令牌轮换」(Refresh Rotation)。也就是说,每次刷新后,旧的刷新令牌立即失效。我曾经审计过一个 SDK,它允许同一个刷新令牌反复使用——这意味着一旦泄露,攻击者可以长时间接管用户会话。

安全实践:那些踩过的坑

我在安全实践中做过一个统计,OAuth2.0 实现中的常见问题可以分为以下几类:

  • CSRF 攻击:授权码模式没有使用 state 参数,导致攻击者可以伪造授权回调。
  • 令牌泄露:访问令牌通过 URL 参数传递(你该用 POST body 或者 Header)。
  • 重定向 URI 校验不严:允许通配符或子路径匹配,这很危险。
  • 令牌生命周期管理缺失:没有黑名单机制,泄露的令牌无法立即作废。
  • 权限范围过大:申请了 scope,但没有在服务端做最小权限校验。

我常说一句话:授权码是临时凭证,访问令牌是通行证,刷新令牌是 VIP 续期卡。你对待它们的安全等级应该逐级升高。

OAuth2.0 四种核心流程关系图

OAuth2.0 四种核心流程及安全边界 授权码模式 后端换令牌 客户端模式 服务直连 刷新令牌 续期会话 安全实践 漏洞防御 授权码模式关键安全点 1. 授权码必须一次性使用 2. 绑定 client_id 与 redirect_uri 3. 使用 state 参数防 CSRF 4. 令牌仅在后端交换 5. 验证响应类型为 code 常见越权漏洞 1. 回调地址注入 2. 访问令牌泄露 3. 刷新令牌未轮换 4. 权限范围未校验 5. 令牌生命周期过长 提示 端点校验 最小权限 HTTPS 日志审计

我个人一直坚持三条铁律

  1. 永远不要在客户端存储刷新令牌。一旦 App 被反编译,长期令牌暴露,用户会话就永久可被劫持。
  2. 授权码模式必须校验 state 参数。这能直接防御 CSRF 类型的越权攻击。我在审计时,第一件事就是看 state 有没有被正确传递和校验。
  3. 客户端模式的 secret 只能放在服务端环境变量或密钥管理服务中。不要问能不能放数据库——那也不行,除非做了加密且密钥分开管理。

其实 OAuth2.0 本身是一个中立的协议,安全性高度依赖实现。我在项目中遇到过最典型的错误,就是把客户端模式用在了需要用户授权的场景里,结果所有操作都变成了同一个身份——这就是越权漏洞的温床。