OAuth2.0安全:授权流程的攻击面
OAuth2.0 是现代 Web 应用最常用的授权协议,但它也是攻击者的重点目标。理解 OAuth2.0 的攻击面,对保护用户数据至关重要。
OAuth2.0 基础流程
标准的授权码流程(Authorization Code Flow)包含四个角色:
- 资源所有者(用户)
- 客户端(第三方应用)
- 授权服务器(颁发 Token)
- 资源服务器(托管用户数据)
- 用户访问客户端,点击"用 XX 登录"
- 客户端将用户重定向到授权服务器
- 用户登录并授权
- 授权服务器返回授权码(Authorization Code)
- 客户端用授权码换取 Access Token
- 客户端用 Access Token 访问资源服务器
常见攻击手段
1. 授权码劫持(Authorization Code Interception)
攻击场景: 恶意应用注册与合法应用相似的 redirect_uri,截获授权码。
攻击步骤:
- 合法应用的 redirect_uri 是
https://example.com/callback - 恶意应用注册
https://example.com.evil.com/callback - 诱导用户点击恶意链接
- 授权码被发送到恶意服务器
- 严格验证 redirect_uri,必须完全匹配
- 使用 PKCE(Proof Key for Code Exchange)扩展
- 注册 redirect_uri 时使用精确匹配,而非前缀匹配
2. 隐式流程的 Token 泄露
攻击场景: 隐式授权流程(Implicit Flow)将 Access Token 直接放在 URL 片段中传递。
https://example.com/callback#access_token=xxx&token_type=Bearer风险:
- Token 被浏览器历史记录保存
- 通过 Referer 头泄露到第三方资源
- 被恶意 JavaScript 读取
- 避免使用隐式流程,改用授权码流程 + PKCE
- 设置 Token 短期有效期
- 使用严格的 Scope 限制权限
3. CSRF 攻击(State 参数缺失)
攻击场景: 攻击者诱导已登录用户访问恶意构造的授权链接,将攻击者的账号绑定到用户的客户端上。
攻击步骤:
- 攻击者用自己的账号获取授权码
- 构造链接:
https://example.com/callback?code=ATTACKER_CODE - 诱导受害者点击
- 受害者的客户端用攻击者的授权码换取 Token
- 受害者客户端访问的是攻击者的数据
- 必须使用
state参数,并验证其值 state应该是随机生成的不可猜测值- 在服务器端存储
state并验证
import secrets
生成 state
state = secrets.token_urlsafe(32)
session['oauth_state'] = state
验证回调中的 state
if request.args.get('state') != session.get('oauth_state'):
raise ValueError("State 验证失败,可能是 CSRF 攻击")4. Token 重放攻击
攻击场景: 截获 Access Token 后重复使用。
防御:
- 使用短期 Token(15分钟)
- 结合 Refresh Token 轮换机制
- 在资源服务器端验证 Token 有效期和签发者
5. Scope 提升攻击
攻击场景: 客户端请求比实际需要更广泛的权限。
防御:
- 用户授权时明确显示请求的权限范围
- 资源服务器严格验证 Scope
- 使用最小权限原则
# 验证 Scope
def check_scope(required_scope):
token_scope = request.headers.get('X-Token-Scope', '').split()
if required_scope not in token_scope:
return False, "权限不足"
return True, None6. Open Redirect 漏洞
攻击场景: redirect_uri 验证不严格,导致授权码被发送到任意域名。
防御:
- 使用白名单机制,只允许预注册的 redirect_uri
- 禁止通配符和动态 redirect_uri
- 对 redirect_uri 进行标准化处理(去除路径遍历、URL 编码等)
from urllib.parse import urlparseALLOWED_URIS = [
'https://example.com/callback',
'https://app.example.com/oauth/callback'
]
def validate_redirect_uri(uri):
parsed = urlparse(uri)
# 禁止任意 redirect
if parsed.scheme not in ['https']:
return False
# 精确匹配
if uri not in ALLOWED_URIS:
return False
return True
PKCE:保护授权码流程
PKCE(RFC 7636)是 OAuth2.0 的扩展,用于防止授权码劫持攻击。
原理:
- 客户端生成随机
code_verifier(43-128字符) - 计算
code_challenge = BASE64URL(SHA256(code_verifier)) - 授权请求携带
code_challenge和code_challenge_method=S256 - 换取 Token 时携带
code_verifier - 授权服务器验证
code_challenge
import secrets
import hashlib
import base64
生成 code_verifier
code_verifier = base64.urlsafe_b64encode(
secrets.token_bytes(32)
).rstrip(b'=').decode('ascii')
计算 code_challenge
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode()).digest()
).rstrip(b'=').decode('ascii')
授权请求
auth_url = f"https://auth.example.com/authorize?" \
f"response_type=code&" \
f"client_id=xxx&" \
f"redirect_uri=https://app.example.com/callback&" \
f"scope=read_profile&" \
f"state={state}&" \
f"code_challenge={code_challenge}&" \
f"code_challenge_method=S256"
换取 Token 时
token_data = {
'grant_type': 'authorization_code',
'code': auth_code,
'redirect_uri': 'https://app.example.com/callback',
'client_id': 'xxx',
'client_secret': 'xxx',
'code_verifier': code_verifier # 必须携带
}安全最佳实践
1. 使用授权码流程 + PKCE
所有客户端(包括移动应用和单页应用)都应该使用授权码流程 + PKCE,而非隐式流程。
2. 严格的 redirect_uri 验证
- 精确匹配,不使用通配符
- 只允许 HTTPS
- 禁止 localhost 在生产环境使用
3. 短期 Token + 刷新机制
# Token 配置
ACCESS_TOKEN_LIFETIME = 900 # 15分钟
REFRESH_TOKEN_LIFETIME = 86400 # 24小时
Refresh Token 轮换
class TokenManager:
def refresh_access_token(self, refresh_token):
# 验证 Refresh Token
user_id = self.validate_refresh_token(refresh_token)
if not user_id:
raise ValueError("无效的 Refresh Token")
# 生成新的 Token 对
new_access = self.create_access_token(user_id)
new_refresh = self.create_refresh_token(user_id)
# 使旧 Refresh Token 失效
self.revoke_refresh_token(refresh_token)
return new_access, new_refresh4. 安全的 Token 存储
| 客户端类型 | Access Token 存储 | Refresh Token 存储 |
|---|---|---|
| 服务端 Web | 内存/Redis | 加密数据库 |
| 移动应用 | Keychain/Keystore | Keychain/Keystore |
| 单页应用 | 内存(不推荐 localStorage) | 不推荐长期存储 |
5. 监控和审计
- 记录所有 Token 颁发和刷新事件
- 监控异常的授权模式(短时间内大量授权失败)
- 设置地理位置和设备指纹验证
漏洞检测工具
- OAuth.io - OAuth 流程测试
- Burp Suite OAuth 插件 - 拦截和修改 OAuth 请求
- OWASP OAuthTester - 自动化安全测试
总结
OAuth2.0 的安全核心在于:
- 保护授权码 - 使用 PKCE 防止劫持
- 验证 redirect_uri - 防止 Token 泄露到恶意站点
- 使用 State 参数 - 防止 CSRF 攻击
- 短期 Token - 减少泄露后的影响范围
- 最小权限 - 限制 Scope 范围