授权码模式的完整流程是怎样的?为什么需要 state 参数和 PKCE?
- 标签:Spring Security、认证授权、后端
🏛 业务架构师
授权码模式(Authorization Code + PKCE)的完整流程与两个安全机制:
流程(以"用微信登录某 App"为例):
- 客户端引导用户跳转授权服务器:
GET /authorize?client_id=xxx&redirect_uri=回调&scope=openid&state=随机值&code_challenge=PKCE摘要; - 用户在授权服务器登录并点击"同意授权";
- 授权服务器重定向回 redirect_uri,带回
code(授权码,短时效约 5 分钟)+state原样返回; - 客户端后端拿 code + client_secret(+ code_verifier)调用 Token 端点换 Access Token + ID Token(OIDC)/Refresh Token;
- 客户端用 Access Token 访问资源服务器。
为什么需要 state 参数(防 CSRF):没有 state,攻击者可以把自己的授权码回调"塞给"受害者的回调页——受害者不知不觉绑定了攻击者的账号(登录 CSRF/会话固定)。state 是客户端发起时生成的随机数、回调时必须原样校验——回调是"我发起的那次授权"的证明,对不上即拒绝。
为什么需要 PKCE(防授权码截获):移动 App/SPA 的回调可能被恶意 App 拦截(自定义 URL scheme 劫持)拿到 code。PKCE(Proof Key for Code Exchange):发起时生成随机 code_verifier,其哈希 code_challenge 随授权请求发出;换 Token 时必须出示原始 verifier——截获 code 的攻击者没有 verifier,换了也失败。等于给授权码加了一道"动态密码"。
业务建议:2026 年新项目一律"授权码+PKCE"(OAuth 2.1 强制公共客户端使用);state 校验别偷懒省略——这两行代码是防账号劫持的保险丝。
🏗 系统架构师
授权码模式完整流程(六步):
- 发起授权:应用把用户重定向到授权端点:
GET /authorize?response_type=code&client_id=xxx&redirect_uri=...&scope=openid profile&state=xyz&code_challenge=...&code_challenge_method=S256; - 用户认证与同意:授权服务器(IdP)认证用户(登录态/SSO)、展示授权同意页;
- 回调返回 code:重定向回
redirect_uri?code=abc&state=xyz——code 生命周期极短(~1~10 分钟)、一次性; - 换 token:应用后端 POST
/token:grant_type=authorization_code + code + client_id + client_secret(+ PKCE 的 code_verifier); - 签发令牌:返回 access_token(+ OIDC 的 id_token,+ refresh_token);
- 访问资源:Bearer 令牌调资源 API(资源服务器验签/内省)。
为什么需要 state 参数(防 CSRF):
- 攻击场景:攻击者用自己的账号走授权流程拿到 code,把含攻击者 code 的回调 URL 强加给受害者浏览器(钓鱼链接/ CSRF)→ 受害者应用用攻击者 code 换 token → 受害者的会话绑定到攻击者账号(攻击者随后用自己账号操作,行为被记在受害者头上——登录 CSRF);
- 防御:应用发起时生成随机 state(存会话/localStorage),回调时校验 state 一致——攻击者无法伪造属于受害者会话的 state。
为什么需要 PKCE(防授权码拦截):
- 攻击场景:公开客户端(SPA/移动端)的 redirect_uri 可被截持(恶意 app 注册同 scheme deep link、浏览器历史/代理截取)——code 在回调途中被偷,攻击者抢先用 code + (拿到的)secret/公开 client_id 换 token;
- 防御:发起方随机生成 code_verifier(本地保存),其 SHA256 哈希作为 code_challenge 发给服务器;换 token 时必须出示原始 verifier——拦截者有 code 无 verifier,兑换失败。动态秘密绑定"发起者=兑换者"。
架构结论:state 防"回调伪造",PKCE 防"code 被偷"——两者互补,现代实现(OAuth 2.1 / OIDC)强制 PKCE,state 必配。
💻 全栈开发专家
授权码模式完整流程(配 PKCE 的现代标准版):
1. 前端生成 code_verifier(随机串),code_challenge = BASE64(SHA256(verifier))
2. 浏览器 302 → 授权端点
/authorize?client_id=web&response_type=code
&redirect_uri=https://app.com/callback
&scope=openid+profile
&state=a8f3... ← 防 CSRF
&code_challenge=xxx&code_challenge_method=S256
3. 用户在授权服务器登录 → 同意页 → 302 回
https://app.com/callback?code=abc&state=a8f3...
4. 前端校验 state → 后端拿 code+verifier 调令牌端点
POST /token grant_type=authorization_code
code=abc&code_verifier=原始verifier&redirect_uri=...
(+ client_secret,机密客户端)
5. 授权服务器验 code、比对 challenge(verifier) → 发
access_token(+refresh_token+id_token[OIDC])
6. 后端持 access_token 调资源 APIstate 参数的作用(防 CSRF):步骤 2 生成随机 state 存前端(session/localStorage),回调时严格比对——攻击者伪造回调链接(塞自己的 code)时 state 对不上被拒。没有 state,攻击者可把「自己的授权 code」塞给受害者完成登录绑定(登录 CSRF,账号合并攻击)。
PKCE 的作用(防授权码拦截):code 经浏览器 URL 回传,可能被恶意 App 拦截(自定义 scheme)、Referer 泄露、代理日志截获。PKCE 让 code 单独没用:换 token 必须出示 code_verifier,其哈希必须在第一步登记过——拦截者拿不到 verifier,code 变废纸。SPA/移动端(无 client_secret 保管的 public client)必配 PKCE,OAuth 2.1 已强制所有客户端。
Spring 配置:ClientRegistration 设 client-authentication-method=none(public)+ PKCE only 即可;Spring Authorization Server 端 OAuth2AuthorizationServerConfigurer 默认要求 PKCE。