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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/77992.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

24.中医知识问答删除历史对话功能前端代码实现

前端实现对话删除功能的完整指南 功能概述 前篇文章介绍了删除历史对话的后端开发,本篇将介绍如何在前端实现一个完整的对话删除功能,包括用户确认、API调用、状态管理和错误处理等关键环节。 功能拆解 1. 用户确认机制 javascript const confirmDe…

如何在 Python 项目中引入 Rust 函数

目录 1. 初始化 Python 项目2. 添加 Rust 开发工具3. 初始化 Rust 项目4. 开发模式构建5. 验证模块是否成功安装6. 测试 Rust 函数总结 (封面pid: 129416070) Python 是一门非常流行的编程语言,具有易于使用和开发的特点。然而,随着项目需求的增长和性能…

Java基础系列-HashMap源码解析2-AVL树

文章目录 AVL树左旋右旋左旋右旋的4种情况LL 型RR 型LR 型RL 型 实际插入时怎么判断是那种类型?插入时注意事项删除节点 AVL树 为避免BST树退化成链表的极端情况, AVL 树应运而生。 平衡因子取值(-1,0,1)…

新书速览|Hadoop与Spark大数据全景解析(视频教学版)

《Hadoop与Spark大数据全景解析:视频教学版》 01 本书内容 《Hadoop与Spark大数据全景解析:视频教学版》结合作者多年在大数据领域的开发实践经验,采用“理论实战”的形式,以大量实例全面介绍Hadoop和Spark的基础知识及其高级应用。作者将丰富的教学经…

TapData × 梦加速计划 | 与 AI 共舞,TapData 携 AI Ready 实时数据平台亮相加速营,企业数据基础设施现代化

在实时跃动的数据节拍中,TapData 与 AI 共舞,踏出智能未来的新一步。 4月10日,由前海产业发展集团、深圳市前海梦工场、斑马星球科创加速平台等联合发起的「梦加速计划下一位独角兽营」正式启航。 本次加速营以“打造下一位独角兽企业”为目…

[密码学基础]密码学常用名词深度解析:从基础概念到实战应用

密码学常用名词深度解析:从基础概念到实战应用 密码学是信息安全的基石,但其专业术语常令人望而生畏。本文系统梳理密码学领域的核心名词,结合技术原理、实际应用与攻击场景,帮助开发者快速构建密码学知识框架。文中代码示例基于…

GD32H7单片机使用segger_rtt,rtt-viewer看不到输出的问题,怎样解决?

jlink版本目前是792,但估计只要能支持h7的jlink版本应该都可以。 将segger/JLink_V792n中,samples文件夹、RTT中四个文件拷贝出来放在单片机目录中 在任意代码部分引用segger_rtt.h,再调用函数 即可使用rtt打印功能,在rtt-viewe…

快速生成安卓证书并打包生成安卓apk(保姆教程)

一.生成安卓证书 目前市面上生成可以快速生成安卓证书的网站有很多个人推荐香蕉云编以下是网站链接 香蕉云编-app打包上架工具类平台 1.进入网站如下图 2.点击生成签名证书 3.点击立即创建证书 4.点击创建安卓证书 5.按照指引完成创建 6.点击下载就可使用 二.打包安卓apk …

前端面试场景题

目录 1.项目第一次加载太慢优化 / vue 首屏加载过慢如何优化 2.说说了解的es6-es10的东西有哪些 ES6(ES2015)之后,JavaScript 新增了许多实用的数组和对象方法,下面为你详细介绍: 3.常见前端安全性问题 XSS&#…

Spring JDBC 的开发步骤(注解方式)

Spring JDBC 的开发步骤主要包括以下关键环节&#xff0c;结合代码示例说明如下&#xff1a; 1. 添加依赖 在 pom.xml 中引入 Spring JDBC 和数据库驱动依赖&#xff08;以 HikariCP 连接池和 MySQL 为例&#xff09;&#xff1a; <!-- Spring JDBC --> <dependency…

Java面试:探索Spring Boot与微服务的深度挑战

场景&#xff1a;互联网大厂Java求职者面试 在一个阳光明媚的下午&#xff0c;赵大宝来到了知名互联网大厂的面试现场。他面临的是一个严肃的面试官&#xff0c;准备对他的技术能力进行全面考核。 第一轮提问&#xff1a;基础知识与Spring Boot应用 面试官&#xff1a;赵先生…

Spring Boot中的监视器:Actuator的原理、功能与应用

在 Spring Boot 应用中&#xff0c;监视器通常指 Spring Boot Actuator&#xff0c;一个内置的生产就绪工具&#xff0c;用于监控和管理运行中的应用。Actuator 提供了一系列 RESTful 端点&#xff0c;暴露应用的运行时信息&#xff0c;如健康状态、性能指标、日志配置和环境变…

GitHub创建远程仓库

使用GitHub创建远程仓库&#xff1a;从零开始实现代码托管与协作 前言 在当今软件开发领域&#xff0c;版本控制系统已成为开发者必备的核心工具。作为分布式版本控制系统的代表&#xff0c;Git凭借其强大的分支管理和高效的协作能力&#xff0c;已成为行业标准。而GitHub作为…

Manus技术架构、实现内幕及分布式智能体项目实战 线上高级实训班

Manus技术架构、实现内幕及分布式智能体项目实战 线上高级实训班 模块一&#xff1a;解密Manus分布式多智能体工作原理和架构内幕  基于Claude和Qwen的大模型智能体Manus为何能够迅速成为全球讨论热度最高、使用体验最好、产业界最火爆的大模型智能体产品&#xff1f;  Ma…

JS通过GetCapabilities获取wms服务元数据信息并在SuperMap iClient3D for WebGL进行叠加显示

获取wms服务元数据信息并在三维webgl客户端进行叠加显示 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><tit…

【刷题Day21】TCP(浅)

说说 TCP 的四次挥手&#xff1f; TCP的四次挥手事用于安全关闭一个已建立的连接的过程&#xff0c;它确保双方都能完成数据传输并安全地释放连接资源。 简述步骤&#xff1a; 第一次挥手&#xff08;FIN --> ACK&#xff09;&#xff1a;客户端主动关闭连接&#xff0c;…

Springboot整合Redis主从

Springboot整合Redis主从 前言原配置现配置测试LettuceConnectionFactory.setShareNativeConnection 方法的作用 前言 SpringBoot版本&#xff1a;2.3.2.RELEASE 原配置 原yml配置内容&#xff1a; spring:# Redis服务器配置redis:host: 127.0.0.1# Redis服务器连接端口por…

git撤销最近一次commit

在Git中&#xff0c;在撤销最近一次的提交时&#xff0c;有几种不同的方法&#xff0c;这取决于你想要的结果。下面是一些常见的方法&#xff1a; 1. 取消最近的提交&#xff08;但不删除改动&#xff09; 如果你想要取消最近的提交&#xff0c;但是保留这些改动&#xff0c;以…

解决Docker 配置 daemon.json文件后无法生效

vim /etc/docker/daemon.json 在daemon中配置一下dns {"registry-mirrors": ["https://docker.m.daocloud.io","https://hub-mirror.c.163.com","https://dockerproxy.com","https://docker.mirrors.ustc.edu.cn","ht…

QML--全局对象Qt

在 QML 中&#xff0c;Qt 是一个内置的全局对象&#xff0c;提供了许多核心功能、工具函数、环境信息和类型构造方法。以下是 Qt 全局对象的详细分类和常见用途&#xff1a; 1. 工具函数 1.1 格式化与转换 Qt.formatDate(date, format) / Qt.formatTime(date, format) 格式化…