概述
- 目标:在前后端协作中使用PKCE与`state/nonce`防护授权码拦截与重放,确保令牌签名与声明校验。
- 适用:SPA/移动端与后端配合的登录授权;涉及OpenID Connect的身份信息获取。
核心与实战
- 生成`code_verifier`与`code_challenge`(S256):
```
// 浏览器/前端
const codeVerifier = [...crypto.getRandomValues(new Uint8Array(64))]
.map(b => ('0' + b.toString(16)).slice(-2)).join('');
const data = new TextEncoder().encode(codeVerifier);
const digest = await crypto.subtle.digest('SHA-256', data);
const base64url = btoa(String.fromCharCode(...new Uint8Array(digest)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const codeChallenge = base64url;
```
- 授权请求(OIDC):
```
GET https://idp.example.com/authorize?
response_type=code&
client_id=app-web&
redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&
scope=openid%20profile%20email&
state=af32c9...&
nonce=9b21d1...&
code_challenge=...&
code_challenge_method=S256
```
- 令牌交换:
```
curl -s -X POST https://idp.example.com/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=authorization_code&client_id=app-web&code=AUTH_CODE&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&code_verifier=CODE_VERIFIER'
```
- 刷新令牌:
```
curl -s -X POST https://idp.example.com/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'grant_type=refresh_token&client_id=app-web&refresh_token=REFRESH_TOKEN'
```
示例
- 解码并校验ID Token:
```
// 步骤:
// 1) 获取 IdP 的 JWKS
// 2) 使用`kid`选择公钥验证JWT签名
// 3) 校验 iss/aud/exp/nonce 与授权请求一致
curl -s https://idp.example.com/.well-known/openid-configuration | jq '.jwks_uri'
curl -s https://idp.example.com/keys | jq
```
- 防重放与CSRF:
```
// 回调校验:比较返回state与本地存储state一致;ID Token中的nonce与请求一致
```
验证与监控
- 令牌与会话:
- 校验`exp`/`iat`与时钟偏差;短期Access Token+长期Refresh Token分离存储。
- 安全头与TLS:
- 强制HTTPS与`Strict-Transport-Security`;禁止令牌出现在URL查询参数与日志。
- 授权日志:
- 监控授权失败率、刷新成功率与`invalid_grant`错误比例。
常见误区
- 使用`plain`或缺失`code_challenge_method=S256`导致安全性下降;必须使用S256。
- 缺少`state/nonce`导致回调可被伪造;必须生成并校验两者。
- 在前端持久化Refresh Token导致泄露风险;应存后端或受保护存储并最小权限调用。
结语
- 采用PKCE与严格令牌校验可有效提升OAuth2/OIDC安全性与一致性,适配SPA与移动端等公开客户端场景。

发表评论 取消回复