跳转至

第七章:安全最佳实践

本章介绍 OAuth2/OIDC 的安全最佳实践。

传输安全

强制 HTTPS

# Keycloak 配置
KC_HOSTNAME: auth.example.com
KC_HOSTNAME_STRICT: "true"
KC_HTTPS_CERTIFICATE_FILE: /etc/ssl/certs/tls.crt
KC_HTTPS_CERTIFICATE_KEY_FILE: /etc/ssl/certs/tls.key

HSTS Header

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

Token 安全

短期令牌

# Keycloak 配置
accessTokenLifespan: 300          # 5分钟
ssoSessionMaxLifespan: 36000      # 10小时
ssoSessionIdleTimeout: 1800       # 30分钟

Refresh Token 安全

refreshTokenMaxReuse: 3           # 最多重用3次
revokeRefreshToken: true          # 使用后撤销
offlineSessionMaxLifespan: 2592000 # 30天

Token 存储

// 不推荐:存储在 localStorage
localStorage.setItem('token', token);  // 易受 XSS 攻击

// 推荐:存储在 HttpOnly Cookie
// 服务端设置
res.cookie('token', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict',
  maxAge: 3600000
});

PKCE 保护

// 始终使用 PKCE
const userManager = new UserManager({
  authority: 'https://auth.example.com',
  client_id: 'my-client',
  redirect_uri: 'https://app.example.com/callback',
  response_type: 'code',
  scope: 'openid profile',
  code_challenge_method: 'S256'  // 启用 PKCE
});

State 和 Nonce

State 参数

import secrets

def login():
    state = secrets.token_urlsafe(32)
    session['oauth_state'] = state

    auth_url = f"{auth_server}/authorize?client_id={client_id}&redirect_uri={redirect_uri}&state={state}"
    return redirect(auth_url)

def callback(state, code):
    if state != session.pop('oauth_state'):
        raise ValueError("Invalid state")
    # 继续处理

Nonce 参数

def login():
    nonce = secrets.token_urlsafe(32)
    session['oauth_nonce'] = nonce

    auth_url = f"{auth_server}/authorize?...&nonce={nonce}"
    return redirect(auth_url)

def verify_id_token(id_token, nonce):
    payload = jwt.decode(id_token, ...)
    if payload.get('nonce') != nonce:
        raise ValueError("Invalid nonce")

CORS 配置

# Keycloak Web Origins
webOrigins:
  - "https://app.example.com"
  - "https://admin.example.com"

输入验证

Redirect URI 验证

ALLOWED_REDIRECT_URIS = [
    "https://app.example.com/callback",
    "https://admin.example.com/callback"
]

def validate_redirect_uri(redirect_uri):
    if redirect_uri not in ALLOWED_REDIRECT_URIS:
        raise ValueError("Invalid redirect URI")

Scope 验证

ALLOWED_SCOPES = ["openid", "profile", "email"]

def validate_scopes(scopes):
    for scope in scopes:
        if scope not in ALLOWED_SCOPES:
            raise ValueError(f"Invalid scope: {scope}")

审计日志

import logging

logger = logging.getLogger('auth')

def log_auth_event(event_type, user_id, client_id, ip_address):
    logger.info({
        "event_type": event_type,
        "user_id": user_id,
        "client_id": client_id,
        "ip_address": ip_address,
        "timestamp": datetime.utcnow().isoformat()
    })

# 使用
log_auth_event("login", user_id, client_id, request.remote_addr)
log_auth_event("token_issued", user_id, client_id, request.remote_addr)
log_auth_event("logout", user_id, client_id, request.remote_addr)

常见攻击防护

CSRF 防护

from flask_wtf.csrf import CSRFProtect

csrf = CSRFProtect(app)

# 或使用 State 参数

XSS 防护

from flask import escape

@app.route('/profile')
def profile():
    name = escape(user.name)  # 转义用户输入
    return f"<h1>Hello, {name}</h1>"

Token 泄露检测

def detect_token_leak(token):
    """检测 Token 是否泄露"""
    # 检查 Token 是否在已知泄露列表中
    # 或使用在线检测服务
    pass

安全检查清单

  • 强制 HTTPS
  • 使用短期令牌
  • 启用 PKCE
  • 验证 State 和 Nonce
  • 严格验证 Redirect URI
  • 限制 Scope
  • 记录审计日志
  • 定期轮换密钥
  • 启用 MFA
  • 监控异常行为

小结

安全最佳实践要点:

  • 传输安全:HTTPS、HSTS
  • Token 安全:短期令牌、安全存储
  • PKCE:防止授权码劫持
  • 输入验证:Redirect URI、Scope
  • 审计日志:记录关键事件

下一章我们将学习生产实践。