当前位置: 首页 > news >正文

JWT的token泄露要如何应对

文章目录

  • 前言
    • ✅ 一、预防措施(防泄露)
    • 🚨 二、应急响应机制(发现已泄露)
      • 🔒 1. **启用 Token 黑名单机制**
      • 🔁 2. **启用 Refresh Token 机制 + 旋转令牌**
      • 📍 3. **强制下线机制**
      • 🛡️ 4. **异常行为检测 + 风控联动**
      • 🔁 5. **轮换密钥 / Key Rotation**
    • 🧩 推荐 Token 架构模式(实践)
    • ✅ 实践建议总结
  • **基于 NestJS 实现 Refresh Token + 黑名单机制的方案**
    • 📦 技术栈概览
    • 🧠 架构设计核心思想
    • ✅ 1. Token 签发逻辑(AuthService)
    • ✅ 2. 登录 & 返回 Refresh Token(设置 HttpOnly Cookie)
    • ✅ 3. 刷新接口:/auth/refresh
    • ✅ 4. 黑名单机制(用于注销 / 撤销)
      • 👉 添加黑名单
      • 👉 在 JWT 策略中校验黑名单
    • ✅ 5. 登出接口:清除 refresh_token + 加入 access_token 黑名单
    • ✅ .env 示例
    • ✅ 补充安全建议


前言

涉及到实际线上安全应急策略。在处理 JWT Token 泄露 事件时的标准应对方案,分为【预防】和【应急响应】两部分讲解。


✅ 一、预防措施(防泄露)

措施说明
1. 设置合理的过期时间通常设置 access_token 有效期为 15分钟~1小时,使用 refresh_token 进行续期
2. 使用 HttpOnly Cookie 存储将 token 存在 HttpOnlySecure Cookie,避免被 JS 获取
3. 绑定设备或 IP在 Token 中绑定用户的设备指纹 / IP,泄露后在其他环境验证失败
4. 使用 RSA 非对称签名(JWT RS256)避免密钥泄露后伪造签名
5. 限制高敏感接口访问例如:只允许白名单设备访问后台管理接口
6. 检测 token reuse多端同 token 出现可疑行为时,自动封禁并记录日志

🚨 二、应急响应机制(发现已泄露)

🔒 1. 启用 Token 黑名单机制

JWT 的默认机制是“无状态”,不易手动失效,但大厂都会通过下面方式实现「黑名单」:

  • 在数据库或 Redis 中维护一个 revoked_token_list
  • 每次请求都校验 JWT 是否在黑名单中;
  • 一旦检测泄露(如同一 token 被不同 IP 使用),就添加进黑名单。
// Redis 中记录已禁用 token ID(jti)
await redis.set(`blacklist:${jti}`, 'revoked', { EX: tokenTTL });

🔁 2. 启用 Refresh Token 机制 + 旋转令牌

  • access_token 只有 15 分钟有效期
  • refresh_token 有效期 7~30 天,保存在 HttpOnly Cookie 中
  • 每次 refresh 时颁发新的 access_token + refresh_token(旧的 refresh_token 作废

泄露后攻击者无法长期使用,因为旧 token 被撤销。


📍 3. 强制下线机制

当用户点击“退出登录”或检测异地登录时:

  • 在服务端废弃该 Token(加入黑名单)
  • 清空 Redis 中该用户的 session 或 token 映射
  • 通知所有前端客户端强制退出(WS / SSE 通知)

🛡️ 4. 异常行为检测 + 风控联动

检测如下行为触发封禁或风控:

行为应对
同一个 token 突然从多个 IP 访问加入黑名单、强制登出
同 IP 请求频率异常加验证码 / 限流 / 风控
后台管理接口被频繁尝试触发安全告警

🔁 5. 轮换密钥 / Key Rotation

泄露风险发生后:

  • 更新 JWT 签名密钥(HMAC / RSA 私钥)
  • 使用 kid(Key ID)字段支持旧 token 回溯校验
  • Gradually revoke tokens signed with old keys

🧩 推荐 Token 架构模式(实践)

名称描述
access_token有效期短,存在客户端,访问 API
refresh_token有效期长,存在 HttpOnly Cookie,用于续签 access_token
jti 字段JWT ID,唯一标识某个 Token,配合黑名单机制使用
device_id 字段标记发放 token 的设备,用于校验来源
Redis 存储记录用户 token 状态、是否登出、是否被撤销

✅ 实践建议总结

类型推荐做法
储存✅ 放 HttpOnly Cookie,而不是 localStorage
加密✅ 使用 RS256,私钥签名,公钥验证
续签✅ 实现 refresh_token 机制,支持滑动续期
风控✅ 实现黑名单、IP+设备检测、异地登录风控
运维✅ 支持密钥轮换、封禁通知、日志追溯

基于 NestJS 实现 Refresh Token + 黑名单机制的方案


📦 技术栈概览

  • @nestjs/jwt – 用于签发 access_token 和 refresh_token
  • bcrypt – 密码加密
  • Redis – 存储黑名单和 refresh_token 状态
  • Prisma – 用户存储
  • Passport – 认证中间件

🧠 架构设计核心思想

Token 类型生命周期存储方式用途
access_token15分钟Authorization header访问受保护接口
refresh_token7-30天HttpOnly Cookie续签 access_token
黑名单(Blacklist)access_token.jti 被吊销时记录在 Redis拒绝已撤销 token

✅ 1. Token 签发逻辑(AuthService)

import { JwtService } from '@nestjs/jwt';
import { randomUUID } from 'crypto';async generateTokens(user: User) {const jti = randomUUID(); // 每个 token 一个唯一 IDconst payload = { sub: user.id, email: user.email, jti };const accessToken = this.jwtService.sign(payload, {secret: process.env.JWT_SECRET,expiresIn: '15m',});const refreshToken = this.jwtService.sign(payload, {secret: process.env.REFRESH_TOKEN_SECRET,expiresIn: '7d',});// 存储 refreshToken 状态到 Redis(可选)await this.redis.set(`refresh:${user.id}:${jti}`, 'valid', 'EX', 7 * 86400);return { accessToken, refreshToken };
}

✅ 2. 登录 & 返回 Refresh Token(设置 HttpOnly Cookie)

@Post('login')
async login(@Body() dto: LoginDto, @Res({ passthrough: true }) res: Response) {const user = await this.authService.validateUser(dto.email, dto.password);if (!user) throw new UnauthorizedException();const { accessToken, refreshToken } = await this.authService.generateTokens(user);res.cookie('refresh_token', refreshToken, {httpOnly: true,secure: true,sameSite: 'lax',maxAge: 7 * 24 * 60 * 60 * 1000,});return { access_token: accessToken };
}

✅ 3. 刷新接口:/auth/refresh

@Post('refresh')
async refresh(@Req() req: Request, @Res({ passthrough: true }) res: Response) {const token = req.cookies?.refresh_token;if (!token) throw new UnauthorizedException();try {const payload = this.jwtService.verify(token, {secret: process.env.REFRESH_TOKEN_SECRET,});// 可选:校验 Redis 是否存在const tokenStatus = await this.redis.get(`refresh:${payload.sub}:${payload.jti}`);if (tokenStatus !== 'valid') throw new UnauthorizedException();// 签发新的 tokenconst user = await this.prisma.user.findUnique({ where: { id: payload.sub } });const { accessToken, refreshToken } = await this.generateTokens(user);// 返回新的 refresh_tokenres.cookie('refresh_token', refreshToken, {httpOnly: true,secure: true,sameSite: 'lax',maxAge: 7 * 24 * 60 * 60 * 1000,});return { access_token: accessToken };} catch (e) {throw new UnauthorizedException();}
}

✅ 4. 黑名单机制(用于注销 / 撤销)

👉 添加黑名单

async revokeAccessToken(jti: string, exp: number) {const ttl = exp - Math.floor(Date.now() / 1000); // 秒await this.redis.set(`blacklist:${jti}`, 'revoked', 'EX', ttl);
}

👉 在 JWT 策略中校验黑名单

async validate(payload: any) {const isBlacklisted = await this.redis.get(`blacklist:${payload.jti}`);if (isBlacklisted) {throw new UnauthorizedException('Token 已被吊销');}return { userId: payload.sub, email: payload.email };
}

✅ 5. 登出接口:清除 refresh_token + 加入 access_token 黑名单

@Post('logout')
async logout(@Req() req: Request, @Res({ passthrough: true }) res: Response) {const token = req.headers.authorization?.split(' ')[1];const payload = this.jwtService.decode(token) as any;await this.authService.revokeAccessToken(payload.jti, payload.exp);await this.redis.del(`refresh:${payload.sub}:${payload.jti}`); // 禁用 refresh_tokenres.clearCookie('refresh_token');return { message: 'Logout successful' };
}

✅ .env 示例

JWT_SECRET=access_token_secret
REFRESH_TOKEN_SECRET=refresh_token_secret

✅ 补充安全建议

项目推荐值
access_token 生命周期15分钟
refresh_token 生命周期7~30天
存储方式Cookie + Redis
签名算法RS256(可选)
Token 包含字段sub, email, jti, exp

相关文章:

  • win10 快速搭建 lnmp+swoole 环境 ,部署laravel6 与 swoole框架laravel-s项目3
  • QT 打包安装程序【windeployqt.exe】报错c000007d原因:Conda巨坑
  • CIFAR-10图像分类学习笔记(一)
  • 同样的接口用postman/apifox能跑通,用jmeter跑就报错500
  • HarmonyOS Grid 网格列表可长按 item 拖动移动位置
  • Shopee五道质检系统重构东南亚跨境格局,2025年电商游戏规则悄然改写
  • QT容器类控件及其属性
  • 文件属性隐写
  • 模型 观测者效应
  • Go协程的调用与原理
  • 被裁20240927 --- 视觉目标跟踪算法
  • go中redis使用的简单介绍
  • Spring Boot 请求参数接收控制指南
  • Python爬虫第18节-动态渲染页面抓取之Splash使用上篇
  • 武装Burp Suite工具:xia SQL自动化测试_插件
  • SQLMesh 通知系统深度解析:构建自动化监控体系
  • 机器学习基础 - 分类模型之朴素贝叶斯
  • 26-算法打卡-字符串-右旋字符串-第二十六天
  • 基于Quill的文档编辑器开发日志(上)——前端核心功能实现与本地存储管理
  • 【Unity笔记】Unity音效管理:ScriptableObject配置 + 音量控制 + 编辑器预览播放自动化实现
  • 云南富源回应“岔河水库死鱼”事件: 初步研判与水体缺氧有关
  • 魔都眼·上海车展④|奔驰宝马保时捷……全球豪车扎堆首秀
  • 主动权益基金一季度重仓股出炉:腾讯跃升至第一,阿里、比亚迪、中芯国际新进前十
  • 主刀完成3万余例手术,81岁神经外科学专家徐启武逝世
  • 特朗普激发加拿大爱国热情:大选提前投票人数创纪录,魁北克分离情绪被冲淡
  • 打造“朋友圈”,“淘书乐”为旧书找“新朋友”