核心思路
OAuth2 解决的是「委托授权」:你允许某应用以你的身份访问资源,但不把账号密码交给它。应用拿到的是受限的访问令牌,不是你的凭证。
授权码流程
- 应用把浏览器重定向到授权服务器,带上
client_id和回调地址; - 你在授权服务器登录并同意授权;
- 授权服务器带着一次性授权码跳回回调地址;
- 应用用授权码 +
client_secret换回 access token(这一步在服务端,不在浏览器)。
两种令牌
| 令牌 | 用途 | 寿命 |
|---|---|---|
| access token | 调用接口 | 短 |
| refresh token | 换新的 access token | 长 |
为什么需要 PKCE
移动端和单页应用无法安全保存 client_secret。PKCE 用一次性挑战值取代密钥:攻击者截到授权码也拿不到令牌,没有挑战值就换不了。
实战案例:三个常见的授权码用法错误
- 把 access_token 存在前端:一旦 XSS 就能直接盗用。更稳的做法是前端只拿授权码、由后端交换令牌,或对公共客户端启用 PKCE。
- 回调时不校验 state:攻击者可构造回调把受害者账号绑到自己的授权上(登录 CSRF)。
state必须一次性生成并随会话校验。 - redirect_uri 配成通配:开放重定向会让授权码被送到攻击者域名。回调地址必须逐条精确白名单,不允许前缀或通配匹配。
常见问题(FAQ)
授权码为什么还要再换一次令牌?授权码只在前端短暂出现,且必须由后端凭 client_secret 交换,避免令牌暴露在浏览器历史与日志中。PKCE 解决什么?让无法安全保存 client_secret 的公共客户端(移动 App、SPA)也能安全使用授权码流程。OAuth2 是认证协议吗?它解决“授权访问”,身份认证应叠加 OpenID Connect 的 id_token。refresh_token 该存在哪?只应存在后端,并支持轮换与吊销。
授权服务器与令牌的运维
接入 OAuth2 之后,长期成本主要在令牌与客户端的管理上:
- 客户端注册要留痕:每个回调地址、密钥、负责人都有记录;密钥支持轮换与双密钥并行,避免更换时全量中断;
- 令牌生命周期分级:访问令牌短(分钟级)、刷新令牌长但可吊销,刷新时轮换刷新令牌以降低被盗用的窗口;
- 权限范围最小化:按最小必要申请
scope,并允许用户随时查看与撤销已授权的应用; - 异常检测:同一刷新令牌在多地被使用、短时间内大量换发令牌,都可能是泄露信号,应告警并支持一键吊销;
- 退出登录联动:本地登出应同时撤销令牌,而不是只清掉前端存储,否则令牌仍然有效。
此外,授权码有效期应很短(通常 30–60 秒),且只能使用一次;重复使用应视为泄露并撤销该次授权产生的全部令牌。
与业务权限的衔接
- 区分“身份”与“权限”:令牌解决的是“你是谁、被授权访问什么”,而具体能操作哪些数据仍由业务权限决定,二者不应混在一起判断。
- 权限校验放在资源侧:每个资源接口都应独立校验调用方是否有权访问该资源,不能因为持有有效令牌就默认放行,否则越权问题会集中在少数接口爆发。
- 令牌内容不要承载可变权限:把权限写进令牌会让变更延迟到令牌过期才生效,权限频繁调整的系统应改为运行时查询或使用较短的令牌有效期。
- 审计与令牌关联:所有关键操作日志都应记录发起方标识,便于事后追溯是谁通过哪个应用完成了操作。
- 退出与撤销要联动:用户撤销某个第三方应用的授权后,应同时使该应用获得的令牌失效,而不是仅记录状态变化。
把这些边界划清后,授权协议解决的是“准入”,业务系统解决的是“能做什么”,职责清晰也更容易排查越权类问题。