对接第三方开放平台(如登录授权、内容服务、支付服务等)时,OAuth 2.0 授权流程是必经之路。不同平台的实现细节略有差异,但核心流程一致。本文总结对接过程中的关键步骤与常见坑。

一、OAuth 2.0 授权码模式流程

目前绝大多数开放平台采用授权码模式(Authorization Code),完整流程如下:

  1. 客户端拼接授权 URL,携带 app_idredirect_uristate 参数,引导用户跳转到授权页
  2. 用户在授权页确认授权,平台跳转回 redirect_uri 并携带 codestate
  3. 服务端用 code 换取 access_tokenrefresh_token
  4. 服务端携带 access_token 调用业务 API
  5. token 过期后用 refresh_token 刷新,避免用户重新授权

二、授权 URL 与 redirect_uri

// 构造授权 URL 示例
const authUrl = 'https://open.example.com/oauth/authorize'
  + '?app_id=' + encodeURIComponent(APP_ID)
  + '&redirect_uri=' + encodeURIComponent(REDIRECT_URI)
  + '&response_type=code'
  + '&scope=' + encodeURIComponent('user_info')
  + '&state=' + state;   // 防 CSRF 随机串

有几个关键点:

  • redirect_uri 必须白名单化:在开放平台后台配置的域名必须与请求中的回调地址完全一致,包括协议和路径
  • state 参数必带:用于防止 CSRF,回调时校验 state 是否与发起时一致
  • scope 按需申请:只申请用到的权限,避免不必要的审核门槛

三、code 换 token 的坑

// 服务端用 code 换 token
const tokenRes = await fetch('https://open.example.com/oauth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    app_id: APP_ID,
    app_secret: APP_SECRET,
    code: code,
  }),
});

常见的坑:

  • code 一次性有效:兑换失败后不能重试同一个 code,需让用户重新授权
  • code 有效期极短:通常几分钟,收到后要立即兑换
  • 换 token 要在服务端做:app_secret 绝不能暴露在前端
  • 注意 token URL 的 Content-Type:很多平台要求 application/x-www-form-urlencoded 而不是 JSON

四、token 管理与刷新

// 刷新 access_token
async function refreshAccessToken(refreshToken) {
  const res = await fetch('https://open.example.com/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'refresh_token',
      app_id: APP_ID,
      app_secret: APP_SECRET,
      refresh_token: refreshToken,
    }),
  });
  return res.json();
}

建议:

  • token 加密存储,或至少做好权限隔离
  • 维护 expires_in,在过期前提前刷新,避免调用失败
  • 刷新逻辑加并发控制,防止多个请求同时刷新导致 token 失效

五、回调与验签

部分平台(如支付、消息通知类)会主动向你的服务器推送回调。务必做好验签,防止伪造请求:

// 验签逻辑示意
function verifySignature(params, signature, secret) {
  const sorted = Object.keys(params).sort();
  const raw = sorted.map(k => k + '=' + params[k]).join('&');
  const expected = crypto.createHmac('sha256', secret).update(raw).digest('hex');
  return expected === signature;
}

回调处理要求:

  • 收到回调先验签,再处理业务,最后返回成功应答
  • 回调可能重复推送,业务处理要保证幂等
  • 回调接口响应要及时(平台通常有超时重试机制)

六、错误码排查思路

对接中最消耗时间的是联调阶段,常见错误码分几类:

  • 参数错误(4xx):检查参数名、编码、必填项,尤其是 access_token 是否传在正确的位置(header 还是 query)
  • 权限错误:scope 未申请、应用未上线、接口白名单未配置
  • 频率限制:超出调用 QPS,需要退避重试或升级配额
  • token 失效:先刷新再重试一次,仍失败则重新授权

小结

开放平台对接的本质是"规范地处理状态与错误"。把授权、验签、token 管理这几个环节做扎实,大部分联调问题都能迎刃而解。建议在代码中埋好关键节点的日志,遇到问题能快速定位。