一文搞懂:JWT(JSON Web Token)与Token认证——从结构剖析到签名算法,再到刷新与注销全攻略
写在前面在传统的Web开发中Session认证一直是主流用户登录成功服务器在内存中存一份Session记录客户端拿到一个Session ID后续请求带着这个ID来“认亲”。这套模式在单机时代运转良好但到了分布式、微服务、前后端分离的年代问题就来了——Session需要共享存储跨域请求麻烦扩展性受限。这时候JWTJSON Web Token走进了我们的视野。它是一种无状态的认证方案服务器不需要存储任何会话信息所有的用户凭证和权限都“打包”在Token里客户端自己带着。这个特性天然适配分布式系统和跨域场景一度被视为Session的终结者。但随着深入使用JWT的“坑”也渐渐浮出水面Token一旦签发就无法主动撤销注销成了难题用localStorage存Token容易遭到XSS攻击签名算法选错了可能直接让整个认证体系形同虚设……很多人照着网上教程“配好就跑”上线后埋下了巨大的安全隐患。这篇笔记我们从JWT的诞生背景出发逐步拆解它的结构、签名算法、安全存储方案、 Refresh Token 刷新机制以及注销的应对策略最后对比它与Session的优缺点。希望通过这篇文章你能既会用JWT也知道“为什么这么用”。1️⃣ JWT 核心概念与结构解析JWTJSON Web Token是由 RFC 7519 标准定义的一种紧凑且自包含的令牌格式用于在网络双方之间安全传输信息。紧凑CompactJWT 是一个很短的字符串可以轻松放在 URL 参数、HTTP Authorization 请求头或请求体中传输。自包含Self-containedJWT 的 Payload 部分包含了用户的所有必要信息如用户 ID、角色、权限等服务端收到 Token 后可以直接解析使用无需查询数据库或缓存。JWT 的三段式结构一个标准的 JWT 长这样Header.Payload.Signature三个部分分别用.隔开各自经过Base64URL 编码。第一段Header头部Header 是一个 JSON 对象包含两个主要字段alg签名算法和typ令牌类型通常固定为JWT。{ alg: HS256, typ: JWT }Header 经过 Base64URL 编码后变成了 JWT 的第一部分。第二段Payload载荷Payload 存储的是声明Claims也就是你想要附加的信息。JWT 标准预定义了一组“注册声明”Registered Claims在工程落地时有极高的规范和审查价值| 声明 | 全称 | 含义 | 强制性 || :--- | :--- | :--- | ||iss| Issuer | JWT 的签发者例如认证服务的域名 | 推荐校验 ||sub| Subject | JWT 的主题一般是用户的唯一标识如用户 ID | 推荐 ||aud| Audience | JWT 的接收方指定哪些服务可以使用该 Token | 高 ||exp| Expiration Time | 过期时间Unix 时间戳必须要校验否则 Token 永不过期 |必须||iat| Issued At | 签发时间 | 推荐 ||nbf| Not Before | 生效时间该时间之前 Token 不可用 | 按需 ||jti| JWT ID | 令牌的唯一标识用于防止重放攻击和黑名单吊销 | 按需 |{ sub: 1234567890, name: John Doe, admin: true, iat: 1516239022 }Payload 同样经过 Base64URL 编码成为 JWT 的第二部分。第三段Signature签名签名的作用是防篡改。它通过对 Header 和 Payload 进行签名运算生成任何人对 Header 或 Payload 的修改都会让签名失效。signature HMACSHA256( base64UrlEncode(header) . base64UrlEncode(payload), secret )服务端收到 JWT 后会用同样的密钥和算法重新计算签名与 JWT 附带的签名比对一致则证明 Token 未被篡改。⚠️关键提醒JWT 的 Payload 只是 Base64 编码不是加密。任何人都可以解码读取其中的内容严禁在 Payload 中存放密码、身份证号、信用卡号等敏感信息。2️⃣ JWT 签名算法详解与选型指南JWT 的安全性很大程度上取决于签名算法的选择。目前主流的算法分为两大类对称加密HMAC和非对称加密RSA/ECDSA/EdDSA。2.1 HS256HMAC with SHA-256——对称加密原理使用同一个密钥Secret进行签名和验证。签发者和验证者必须共享这个密钥。优点计算效率高签名和验证都很快验证环节甚至能达到 HS256 比 RS256 快一个数量级的程度。实现简单适合单服务或内部系统所有参与方在同一个信任边界内。缺点密钥分发困难系统中所有需要验证 Token 的服务都必须持有同一个密钥一旦密钥泄露任何人都可以签发伪造的 Token。不适合分布式/多服务架构在多服务场景下共享密钥会带来安全隐患和管理成本。适用场景单一服务或所有验证方都在同一信任边界内、性能要求极高的场景。2.2 RS256RSA with SHA-256——非对称加密原理使用一对公私钥——私钥用于签名公钥用于验证。签发者认证服务持有私钥验证者各业务服务只持有公钥。优点安全边界清晰即使公钥泄露攻击者也无法签发伪造的 Token因为私钥没有泄露。天然适合微服务架构认证服务统一签发 Token各个业务服务只需配置公钥即可验证无需共享同一个密钥。缺点性能略低于 HS256签名过程相对较慢。实现略复杂需要管理公私钥对。适用场景微服务架构、多服务协作、需要安全边界隔离的场景。2.3 更现代的推荐EdDSAEd255192025 年的安全实践逐渐将 EdDSA 列为 JWT 签名算法的首选。相比于 RS256Ed25519 在多服务分离的前提下还具备签名长度更短、签名验证性能更高的优势。2.4 签名算法选型对比表2026 年推荐方案除非在单一服务内对性能要求极致否则不推荐 HS256特别是微服务架构应首选 Ed25519 或 RS256。如果使用非对称算法务必在验证时限定只允许预期的算法防止攻击者在令牌 Header 中指定algnone绕过签名校验。3️⃣ JWT 与 Session 对比JWT 与 Session 是两种主流的认证方式本质上不是“谁更好”的问题而是“在不同的分布式和工程管道下谁更合适”。一句话总结Session 适合需要实时控制状态的场景如金融交易、后台管理系统JWT 适合分布式、高扩展性的场景如微服务、移动端 API。4️⃣ JWT 安全存储与风险防控JWT 的安全性不仅仅在于签名算法更在于Token 在客户端的存储方式。4.1 错误示范localStorage 存储很多前端项目为了方便直接将 JWT 存进localStorage。// ❌ 危险的做法 localStorage.setItem(token, jwtToken);这种方式有一个致命的弱点——localStorage里的数据可以被同一域名下的任何 JavaScript 代码读取。如果网站存在一个 XSS跨站脚本攻击漏洞攻击者可以轻而易举地把所有用户的 Token 偷走。4.2 推荐方案HttpOnly Cookie将 JWT 存储在带有HttpOnly属性的 Cookie 中可以最大程度地隔离 XSS 风险。// ✅ 安全做法服务器设置 Cookie res.cookie(token, jwtToken, { httpOnly: true, // JavaScript 无法访问 secure: true, // 仅 HTTPS 传输 sameSite: strict, // 防止 CSRF maxAge: 24 * 60 * 60 * 1000 });HttpOnly确保 Cookie 不能被客户端的 JavaScript 读取即使网站存在 XSS 漏洞攻击者也拿不到 Token。存储方案执行后理论上需要在验证时统一从 Cookie 中读取 JWT。2026 年更为前沿的选择在 SPA 单页架构中可利用Web Worker 的隔离沙箱将用户令牌与主渲染线程彻底分离大幅降低扩展内存注入的暴露面但这种模式下Token 的自动携带和透明刷新复杂度更高也引入了双向通信时序负担。5️⃣ Refresh Token 机制与无感刷新实战JWT 安全实践的第一准则是Access Token 的生命周期极短通常设为 15-30 分钟以减少 Token 泄露后的危害。但 Token 频繁过期会让用户体验极差——每 15 分钟就要重新登录一次。Refresh Token刷新令牌令牌自动续期流程解决了这个问题。5.1 双 Token 机制Access Token短期有效15分钟 - 2小时用于访问资源无状态验证。Refresh Token长期有效7天 - 30天存储在服务端或 HttpOnly Cookie 中仅在 Access Token 过期时换取新的 Access Token。5.2 自动刷新流程图5.3 前端无感刷新最佳实践在前端应用中以 Axios 为例可以在响应拦截器中自动处理 Token 过期与刷新axios.interceptors.response.use( response response, async error { const originalRequest error.config; if (error.response.status 401 !originalRequest._retry) { originalRequest._retry true; try { const refreshToken getRefreshToken(); // 从 Cookie 或存储中获取 const newToken await api.refreshToken(refreshToken); setAccessToken(newToken); originalRequest.headers[Authorization] Bearer ${newToken}; return axios(originalRequest); } catch (refreshError) { redirectToLogin(); } } return Promise.reject(error); } );整个过程对用户完全透明实现了“无感刷新”。5.4 Refresh Token 安全设计要点短有效期结合旋转策略Access Token 设计为 15 分钟Refresh Token 设置为 7-30 天但须限制 Refresh Token 不可用于业务接口访问。一次性使用 设备绑定每次使用 Refresh Token 换取新的 Access Token 后应将旧的 Refresh Token 废弃并生成新的 Refresh TokenToken 轮换同时限制单个 Refresh Token 只能绑定登录设备防止在多端窃取场景下通过 Refresh Token 横向扩散。存储安全Refresh Token 原则上不应放在 localStorage 中而是放入 HttpOnly Cookie 甚至服务端的“令牌仓库”中统一管理。6️⃣ 注销登出JWT 无法主动失效的应对方案JWT 最大的痛点就是标准 JWT 一旦签发在有效期内无法从服务端主动使其失效。6.1 方案一Redis 黑名单最推荐适合中小型系统原理用户登出时将该 Token 的唯一标识jti存入 Redis 黑名单过期时间与 Token 剩余有效期一致。每次请求验证 Token 时先检查黑名单。// 1. 生成 Token 时添加唯一标识 jti String jwt Jwts.builder() .setId(UUID.randomUUID().toString()) // 唯一 ID .setSubject(user123) .setExpiration(new Date(System.currentTimeMillis() 3600000)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); // 2. 登出时将 jti 加入 Redis 黑名单 public void logout(String jwt) { Claims claims Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(jwt) .getBody(); // jti 作为 key过期时间与 JWT 剩余有效期一致 redisTemplate.opsForValue().set( blacklist: claims.getId(), , getRemainingTTL(jwt), TimeUnit.MILLISECONDS ); } // 3. 请求拦截器中校验黑名单 public boolean isTokenBlacklisted(String jwt) { Claims claims parseJWT(jwt); return redisTemplate.hasKey(blacklist: claims.getId()); }核心要点Redis 中黑名单项的过期时间TTL必须与 JWT 本身的剩余有效期精确对齐否则会出现“Token 已过期但黑名单先清空”或“未过期却被误拦”的断裂。6.2 方案二用户版本号机制在用户表中维护一个token_version字段用户每次登录或注销时递增。JWT 的 Payload 中包含这个版本号每次请求时与数据库中的版本号比对。优点可以一性令某个用户的所有Token 同时失效如修改密码场景缺点每次请求都要查数据库给后续链带上了新的性能开销。6.3 方案对比与选型建议推荐对于大多数场景优先选择Redis 黑名单 短生命周期 Access Token组合如果非常看重“实时吊销能力”可以配合 Refresh Token 旋转。7️⃣ Spring Boot JWT 实战流程在 Spring Boot 中集成 JWT通常需要以下步骤基于jjwt依赖引入依赖在pom.xml中添加jjwt。编写 JWT 工具类实现 Token 的生成、解析、验证。配置 Spring Security设置哪些接口需要认证、哪些放行。实现认证过滤器从请求中提取 JWT 并验证。实现登录接口验证用户名密码后生成 JWT 并返回。核心过滤器OncePerRequestFilter的工作流程如下关于实现细节的补充Spring 生态中可借助spring-boot-starter-security组件配合JwtAuthenticationToken用 JWT 来承载用户凭证与权限集合。实践中应当避免在 Filter 链中进行过度的数据库查询例如在每次请求中读取用户全部角色建议将必要的权限声明精简后预置进 Token Payload。8️⃣ 总结与最佳实践2026 年的安全趋势业界正在超越传统 JWT比如PASETO平台无关安全令牌强制绑定了算法与协议版本从根本上杜绝了算法混淆。 不过 JWT 凭借 OAuth 2.0 / OIDC 生态和云服务商的广泛支持在大部分无状态加解密场景中仍被作为兜底方案广泛采用。在实际项目中假如你的团队使用 JWT 做认证Access Token 设置为 1 小时过期Refresh Token 设置为 14 天Refresh Token 存储在 HttpOnly Cookie 中。运营团队在 12 小时后发现某个用户的账号在另一个设备上被恶意登录要求立即踢掉该用户的所有端登录状态。如果利用 Refresh Token 轮换设计和 Redis 黑名单两个存储组件你该怎么在不动用额外时钟、不影响其他用户的前提下快速落地欢迎在评论区分享你的设计方案和踩坑经验