侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1823 篇文章
  • 累计收到 0 条评论

设计安全的JWT认证系统并防御令牌泄露和重放攻击

2025-12-12 / 0 评论 / 4 阅读

题目

设计安全的JWT认证系统并防御令牌泄露和重放攻击

信息

  • 类型:问答
  • 难度:⭐⭐⭐

考点

JWT安全机制,令牌刷新策略,重放攻击防御,存储安全

快速回答

实现安全JWT系统的核心要点:

  • 使用短期访问令牌(15-30分钟)和长期刷新令牌(7天)分离机制
  • 刷新令牌存储于HttpOnly Secure Cookie,访问令牌存客户端内存
  • 实现刷新令牌轮换(每次刷新签发新令牌/撤销旧令牌)
  • 添加JWT唯一标识符(jti)和服务端令牌黑名单
  • 强制HTTPS传输并绑定令牌到用户设备指纹
## 解析

一、核心安全挑战

JWT常见安全风险:

  • 令牌泄露:XSS攻击或不当存储导致令牌被盗
  • 重放攻击:拦截有效令牌重复使用
  • 无法立即失效:JWT天然无状态导致注销困难

二、完整解决方案设计

1. 双令牌机制实现

// 生成令牌示例(Node.js)
const accessToken = jwt.sign(
  { userId: 'u123', jti: crypto.randomUUID() },
  process.env.SECRET,
  { expiresIn: '15m' }
);

const refreshToken = jwt.sign(
  { userId: 'u123', deviceId: 'device_fingerprint' },
  process.env.REFRESH_SECRET,
  { expiresIn: '7d' }
);

2. 安全存储策略

  • 访问令牌:仅存客户端内存(不持久化)
  • 刷新令牌:通过HttpOnly Secure Cookie传输(SameSite=Strict)
  • 服务端:记录刷新令牌与设备/用户的绑定关系

3. 刷新令牌轮换流程

# 伪代码示例(Python)
def refresh_token(old_refresh_token):
    # 验证令牌有效性
    payload = verify(old_refresh_token, REFRESH_SECRET)

    # 检查令牌是否已被使用
    if redis.get(f"revoked:{payload['jti']}"):
        abort(401, "Token revoked")

    # 生成新令牌并撤销旧令牌
    new_refresh_token = generate_token(payload['userId'])
    redis.setex(f"revoked:{payload['jti']}", 7*24*3600, "revoked")

    return {
        "access_token": new_access_token(),
        "refresh_token": new_refresh_token
    }

4. 重放攻击防御

  • 在JWT声明中添加唯一标识符jti
  • 服务端维护短期令牌黑名单(Redis实现):
    redis.setex("blacklist:"+jti, 15*60, "blocked")
  • 校验请求时检查黑名单:
    if redis.exists("blacklist:"+decoded.jti):
        return 401

三、最佳实践

  • 密钥管理:使用HS256或RS256算法,定期轮换密钥
  • 声明最小化:JWT payload仅包含必要信息
  • 设备绑定:在刷新令牌中嵌入设备指纹(如IP+UserAgent哈希)
  • 监控:记录异常刷新频率(如1分钟内多次刷新)

四、常见错误

  • ❌ 将访问令牌存localStorage(易受XSS攻击)
  • ❌ 使用单一长期令牌(无刷新机制)
  • ❌ 未实现jti黑名单导致无法即时注销
  • ❌ 未绑定设备信息允许令牌跨设备使用

五、扩展知识

  • OAuth 2.1:推荐PKCE扩展防御授权码拦截
  • Token Binding:将令牌与TLS会话绑定(RFC 8471)
  • 分布式黑名单:使用Redis Cluster跨服务同步状态
  • JWT替代方案:PASETO(更安全的令牌标准)