ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

JWT在线解析工具全解析:结构原理、常见坑位与安全实践

JWT在线解析工具全解析:结构原理、常见坑位与安全实践 JWT这东西日常开发里几乎绕不开。不管是做前后端分离的登录鉴权还是微服务之间传递身份信息token 的身影无处不在。但很多人对 JWT 的理解停留在“登录后拿个 token请求时带上就行”这个层面一旦遇到解析失败、签名不匹配、token 过期续签这些问题就开始抓瞎。我见过太多团队在联调阶段因为一个 JWT 格式问题卡半天也见过因为密钥管理不当导致的安全隐患。这篇内容就围绕 JWT 在线解析工具这个切入点把 JWT 的结构、解析原理、常见坑位、安全实践、续签方案这些事一次性讲透。不管你是刚接触 JWT 的新手还是已经用过一段时间但没深究细节的开发者都能从中找到可以直接复用的经验。1. JWT 在线解析工具到底在解析什么很多人第一次打开 JWT 在线解析工具把 token 粘进去看到三块内容被拆开显示觉得挺神奇。但如果你不知道这三块分别代表什么解析结果摆在你面前你也看不懂。所以咱们先把 JWT 的底层结构掰开揉碎讲清楚。1.1 三段式结构Header、Payload、Signature一个标准的 JWT 长这样eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c肉眼看上去就是一串乱码但用在线解析工具一拆立刻变成三部分中间用两个点号分隔。第一部分是Header第二部分是Payload第三部分是Signature。Header 部分经过 Base64Url 解码后通常是一个 JSON 对象里面最关键的两个字段是alg和typ。alg表示签名算法常见的有 HS256、RS256、ES256 等typ一般固定为 JWT。Payload 部分同样经过 Base64Url 解码里面放的是实际传递的数据也就是各种 claim比如sub主题、iat签发时间、exp过期时间、userId、role这些业务字段。Signature 部分是对前两部分的签名用来验证 token 有没有被篡改。在线解析工具做的事情本质上就是把 Base64Url 编码的 Header 和 Payload 还原成人类可读的 JSON然后把 Signature 原样展示出来。注意大多数在线工具不会帮你验证签名因为验证签名需要密钥而密钥不应该随便交给在线工具。这一点后面会详细说。1.2 Base64Url 编码不是加密别搞混了这是新手最容易踩的认知坑。很多人以为 JWT 的 Payload 是加密的看不到内容就以为安全。实际上 Base64Url 只是一种编码方式不是加密算法。任何人拿到 token都可以用在线工具或者几行代码把 Payload 解出来。你可以自己验证一下打开浏览器控制台输入atob(eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ)立刻就能看到明文 JSON。所以绝对不要在 JWT 的 Payload 里放密码、身份证号、银行卡号这类敏感信息。Payload 只适合放用户标识、角色、权限范围这类不敏感但需要传递的数据。那 JWT 的安全性靠什么保证靠 Signature。签名保证了 token 不能被篡改。如果有人改了 Payload 里的userId签名就会对不上服务端验证时就会拒绝。但前提是签名算法和密钥管理得当否则签名也可能被绕过。1.3 在线解析工具的典型使用场景我在实际工作中用在线解析工具主要集中在几个场景。一是联调阶段前端同学说 token 有问题我让他把 token 发过来粘到解析工具里一看发现exp已经过期了或者alg字段不对。二是排查第三方系统对接问题对方给的 token 解析出来发现 claim 结构和文档描述不一致。三是做安全审计时快速检查 token 里有没有泄露敏感字段。但要注意生产环境的真实 token 不要随便粘到公共在线工具里。虽然大多数工具声称在浏览器本地解析、不上传服务器但你无法完全确认。更稳妥的做法是本地跑一个解析脚本或者用浏览器开发者工具手动解码。后面我会给一个本地解析的完整方案。2. 手把手拆解 JWT 解析的完整技术链路理解了结构之后咱们来看看解析这件事在技术层面到底是怎么完成的。这部分内容对于想自己实现解析逻辑、或者想深入理解工具行为的读者来说很关键。2.1 从 token 字符串到可读 JSON 的每一步假设你拿到一个 token解析流程可以拆成以下步骤。第一步按点号分割。token 用两个点号分成三段所以直接用split(.)就能拿到三个部分。但要注意Payload 里如果包含点号比如某些特殊字符编码后可能出现分割结果可能不对。标准 JWT 的 Base64Url 编码不会产生点号所以正常情况没问题但做健壮性处理时最好校验分割后数组长度是否为 3。第二步Base64Url 解码。这里有个细节标准 Base64 用的字符集包含和/而 Base64Url 把它们替换成了-和_并且去掉了末尾的填充。所以解码前需要先做字符替换再补上填充。很多解析库内部帮你处理了这一步但如果你自己手写解析逻辑这一步漏了就会解码失败。第三步JSON 解析。解码出来的字节序列转成字符串后用 JSON 解析器转成对象。如果这一步报错说明 token 格式有问题可能是编码阶段就出了错。第四步展示或使用。解析工具会把 Header 和 Payload 格式化展示Signature 部分通常只展示原始字符串因为验证签名需要额外输入密钥。用 Python 实现的话核心代码大概是这样import base64 import json def decode_jwt(token): parts token.split(.) if len(parts) ! 3: raise ValueError(Invalid JWT format) def base64url_decode(data): padding 4 - len(data) % 4 if padding ! 4: data * padding return base64.urlsafe_b64decode(data) header json.loads(base64url_decode(parts[0])) payload json.loads(base64url_decode(parts[1])) signature parts[2] return header, payload, signature这段代码可以直接用但注意它只做解码不做签名验证。签名验证需要根据alg字段选择对应算法并用密钥计算比对。2.2 签名验证解析工具通常不做的关键一步为什么在线解析工具一般不帮你验证签名因为验证签名需要密钥。对于 HS256 算法密钥是一个字符串对于 RS256密钥是一对公私钥。你把密钥交给在线工具就等于把系统的安全凭证交出去了。所以正规的在线工具只做解码展示不做验证。但作为开发者你必须知道签名验证是怎么做的。以 HS256 为例签名计算方式是HMACSHA256( base64UrlEncode(header) . base64UrlEncode(payload), secret )服务端收到 token 后用同样的方式重新计算签名然后和 token 里的 Signature 比对。如果一致说明 token 没被篡改如果不一致直接拒绝。这里有个常见的坑Base64Url 编码后的 header 和 payload 必须和原始 token 中的完全一致不能先解码再重新编码因为重新编码可能产生不同的结果比如填充字符的处理差异。正确做法是直接用 token 中原始的前两段字符串拼接后计算签名。2.3 不同算法族的解析差异JWT 支持多种签名算法不同算法在解析和验证时有不同注意点。算法族典型算法密钥类型解析注意点HMACHS256/HS384/HS512对称密钥字符串密钥不能泄露否则可伪造 tokenRSARS256/RS384/RS512非对称密钥对验证用公钥签名用私钥ECDSAES256/ES384/ES512非对称密钥对签名长度和格式有特殊要求RSA-PSSPS256/PS384/PS512非对称密钥对填充模式不同需库支持解析工具在展示 Header 时alg字段告诉你用的是哪种算法。如果你看到alg: none要特别警惕。历史上出现过攻击者把alg改成none然后去掉签名某些实现不严谨的服务端会直接放行。所以服务端必须显式指定允许的算法不能信任 token 里的alg字段。3. 那些年我们踩过的 JWT 解析坑光讲原理不够实际开发中遇到的问题才是最有价值的。这一章我整理了几个高频坑位每个都附上排查思路和解决方案。3.1 解析报错 Invalid base64 的几种根因这个报错太常见了。你拿到一个 token粘到解析工具里工具告诉你 Base64 解码失败。可能的原因有几种。第一种token 在传输过程中被截断或修改。比如复制的时候少复制了几个字符或者 URL 传输时某些字符被转义了。JWT 里可能包含-和_这些在 URL 里是安全的但如果经过某些中间件处理可能被替换。排查方法是检查 token 长度是否符合预期对比原始 token 和接收到的 token。第二种token 本身就不是标准 JWT。有些系统自己造了一套类似 JWT 的格式但编码方式不同。这时候用标准解析工具当然解不出来。需要找对应系统的文档确认格式。第三种Base64Url 和标准 Base64 混用。有些实现编码时用了标准 Base64解码时却按 Base64Url 处理或者反过来。表现就是某些 token 能解某些不能解取决于内容里有没有、/、-、_这些字符。解决方案是统一编解码方式推荐全部使用 Base64Url。3.2 时间戳字段的时区与单位陷阱Payload 里的exp、iat、nbf这些时间字段单位是秒不是毫秒。我见过有团队用 JavaScript 的Date.now()生成时间戳那是毫秒结果 token 一签发就过期因为服务端按秒解析得到一个巨大的未来时间或者直接判定过期。另一个坑是时区。JWT 的时间戳是 Unix 时间戳本身不带时区信息表示的是 UTC 时间。但有些开发者在校验时用了本地时间做比较导致在不同时区的服务器上行为不一致。正确做法是全部用 UTC 时间处理。排查这类问题时在线解析工具很有用。把 token 解析出来看exp的值然后和当前时间戳对比。如果exp是一个 13 位的数字基本可以确定单位用错了。3.3 kid 字段引发的密钥选择问题kid是 Header 里的一个可选字段全称是 Key ID用来标识签名用的是哪个密钥。在密钥轮换场景下特别有用服务端可能同时存在多个密钥kid告诉验证方该用哪个。但kid也带来过安全问题。如果服务端直接用kid的值去拼接文件路径或者查询数据库而没有做校验攻击者可以构造恶意的kid值实现路径穿越或者注入。比如kid设为../../etc/passwd某些实现会去读这个文件当密钥。所以处理kid时必须做白名单校验只允许预定义的密钥 ID不能直接把用户输入拼接到敏感操作里。3.4 解析工具显示正常但服务端拒绝的排查链路这种情况最让人头疼在线工具解析出来一切正常Header 对、Payload 对、时间没过期但服务端就是返回 401。排查思路可以按以下顺序走。先确认签名算法。服务端配置的允许算法和 token 里的alg是否一致。有些库默认只允许 HS256你用了 RS256 就会被拒。再确认密钥。HS256 场景下服务端用的密钥和签发时用的密钥是否完全一致包括空格、换行这些不可见字符。我遇到过密钥末尾多了一个换行符导致验证失败的情况。然后确认 token 传递方式。是放在Authorization头里还是放在自定义头里还是放在 Cookie 里。服务端从哪个位置取 token格式是Bearer xxx还是直接xxx这些细节对不上都会导致取不到 token。最后确认时钟偏移。如果签发服务器和验证服务器的系统时间差了几分钟而nbf或exp的容差设置得很小就可能出现刚签发的 token 立刻被判定为未生效或已过期。一般建议设置几十秒的时钟容差。4. 从解析延伸到安全JWT 实践中的防护要点解析只是手段安全才是目的。这一章聊聊在实际项目中怎么把 JWT 用对、用好。4.1 密钥管理别把 secret 硬编码在代码里我见过太多项目把 JWT 密钥直接写在配置文件甚至代码里然后提交到代码仓库。这是非常危险的做法。密钥一旦泄露攻击者可以伪造任意用户的 token。正确的做法是把密钥放在环境变量或者专门的密钥管理服务里。对于 HS256密钥要足够长且随机建议至少 256 位。对于 RS256私钥严格保密公钥可以公开用于验证。另外不同环境开发、测试、生产必须使用不同的密钥。我见过开发环境的密钥泄露后攻击者用同一个密钥去攻击生产环境因为两个环境密钥一样。4.2 过期时间与续签机制的设计取舍JWT 的exp设置多长合适太短用户体验差频繁登录太长安全风险高token 泄露后有效窗口大。常见做法是 access token 设置较短过期时间比如 15 分钟到 2 小时配合 refresh token 做续签。续签的典型流程是access token 过期后客户端用 refresh token 调用续签接口服务端验证 refresh token 有效后签发新的 access token。refresh token 本身也有过期时间但比 access token 长得多比如 7 天到 30 天。这里有个设计细节refresh token 最好是一次性的每次续签后旧的 refresh token 失效签发新的。这样即使 refresh token 泄露攻击者用一次之后就会失效降低了风险。实现上可以用 Redis 记录 refresh token 的状态。还有一种方案是滑动过期每次请求都刷新 token 的过期时间。但这种方案在分布式环境下需要共享状态实现复杂度较高而且 token 频繁变化对客户端缓存不友好。我个人更倾向于 access token refresh token 的方案职责清晰实现简单。4.3 敏感信息不该放进 Payload前面提过Payload 是 Base64Url 编码不是加密。但实际项目中还是有人往里放手机号、邮箱、甚至密码。我猜测可能是因为觉得“反正编码了看不懂”。但编码和加密是两回事任何拿到 token 的人都能解出来。那 Payload 里应该放什么放用户 ID、角色、权限范围、token 签发时间、过期时间这些。用户 ID 用来定位用户角色和权限用来做鉴权判断。如果确实需要传递敏感信息应该额外做加密或者干脆不放在 token 里让服务端根据用户 ID 去数据库查。4.4 算法混淆攻击与 none 算法的防范算法混淆攻击是一种经典的 JWT 攻击方式。攻击者把 Header 里的alg从 RS256 改成 HS256然后用公钥作为 HMAC 的密钥来签名。如果服务端实现不严谨用公钥去验证 HS256 签名就会验证通过因为攻击者就是用公钥签的。防范方法很简单服务端在验证时显式指定允许的算法列表不读取 token 里的alg来决定用什么算法验证。比如import jwt payload jwt.decode( token, public_key, algorithms[RS256], # 显式指定不信任 token 里的 alg options{require: [exp, iat]} )对于none算法同样要显式拒绝。任何生产环境都不应该允许alg: none。5. 自己动手搭建一个本地 JWT 解析与验证环境在线工具虽然方便但涉及真实 token 时还是本地环境更放心。这一章给一个完整的本地解析方案包含解码和验证两部分。5.1 用 Python 快速实现解码与验证先安装依赖pip install pyjwt解码不验证签名import jwt token your.jwt.token decoded jwt.decode(token, options{verify_signature: False}) print(decoded)验证签名HS256import jwt token your.jwt.token secret your-secret-key try: payload jwt.decode(token, secret, algorithms[HS256]) print(Valid token:, payload) except jwt.ExpiredSignatureError: print(Token expired) except jwt.InvalidTokenError as e: print(Invalid token:, e)验证签名RS256import jwt token your.jwt.token with open(public.pem, r) as f: public_key f.read() payload jwt.decode(token, public_key, algorithms[RS256]) print(payload)这套代码可以直接集成到你的调试工具里比在线工具安全得多。5.2 浏览器端本地解析的轻量方案如果不想装 Python 环境浏览器控制台也能做基础解析。打开开发者工具在 Console 里粘贴function parseJwt(token) { const parts token.split(.); if (parts.length ! 3) { throw new Error(Invalid JWT); } const decode (str) { const base64 str.replace(/-/g, ).replace(/_/g, /); const padded base64 .repeat((4 - base64.length % 4) % 4); return JSON.parse(atob(padded)); }; return { header: decode(parts[0]), payload: decode(parts[1]), signature: parts[2] }; } console.log(parseJwt(your.jwt.token));这段代码只做解码不做验证适合快速查看 token 内容。注意atob对 Unicode 字符支持不好如果 Payload 里有中文需要用decodeURIComponent配合处理。5.3 验证环境的密钥配置注意事项本地验证时密钥的读取方式要注意。HS256 的密钥是字符串直接传入即可。RS256 需要 PEM 格式的公钥文件读取时注意换行符的处理。有些环境读取文件后需要去掉多余的空白字符。另外本地验证环境的时钟要和签发环境保持一致。如果本地机器时间不准可能导致验证结果异常。建议开启系统时间同步。6. 解析工具背后的 JWT 生态与常见集成场景JWT 不是一个孤立的技术它和很多框架、协议都有集成。了解这些集成场景能帮你在实际项目中更快定位问题。6.1 Spring Security 整合 JWT 的典型配置Java 生态里Spring Security 整合 JWT 是最常见的组合。典型配置包括一个JwtAuthenticationFilter用来从请求中提取 token 并验证一个JwtTokenProvider用来签发和解析 token。配置时有几个关键点。一是过滤器链的顺序JWT 过滤器要放在用户名密码认证过滤器之前。二是异常处理token 过期、签名无效这些情况要返回明确的错误码方便前端区分处理。三是密钥配置通过Value从配置文件读取不要硬编码。解析逻辑通常放在JwtTokenProvider里用io.jsonwebtoken库的Jwts.parserBuilder()来构建解析器。注意设置setSigningKey和允许的算法。6.2 微服务架构下的 token 传递与解析微服务架构下JWT 通常在网关层统一解析和验证然后把用户信息通过请求头传递给下游服务。这样做的好处是下游服务不需要重复验证 token减轻负担。但要注意网关传递给下游的请求头必须是内部可信的。如果下游服务直接暴露攻击者可以伪造请求头绕过网关。所以下游服务要么只允许内网访问要么对内部请求头做额外校验。另一种方案是每个服务都独立验证 token。这种方案安全性更高但每个服务都需要拿到密钥或公钥密钥管理复杂度增加。实际选型要看团队的安全要求和运维能力。6.3 前后端分离项目中的 token 存储位置选择前端把 token 存哪里这个问题争论了很久。常见方案有 localStorage、sessionStorage、Cookie。localStorage 的优点是容量大、使用方便缺点是容易受到 XSS 攻击恶意脚本可以直接读取。sessionStorage 类似但关闭标签页就清除。Cookie 可以设置HttpOnly防止 JavaScript 读取降低 XSS 风险但容易受到 CSRF 攻击需要配合SameSite属性和 CSRF token。我的建议是如果安全要求高用HttpOnlySecureSameSiteStrict的 Cookie 存储 refresh tokenaccess token 放在内存里比如 Vuex 或 Redux 的 state 中页面刷新后通过 refresh token 重新获取。这样兼顾了安全和体验。7. 关于 JWT 解析工具的几个实用心得写了这么多最后分享几个我在实际使用中总结的小经验都是踩过坑之后才明白的。第一解析工具的选择要看场景。快速看一眼 token 内容用在线工具没问题但别粘真实生产 token。要做签名验证必须用本地环境。要批量分析 token写脚本比手动粘贴高效得多。第二养成检查exp和iat的习惯。很多“token 无效”的问题根源就是过期了或者时间戳单位错了。解析出来先看这两个字段能省不少排查时间。第三alg字段要重点关注。看到none直接警惕看到算法和服务端配置不一致也要警惕。算法混淆攻击不是理论上的实际环境中出现过。第四Payload 里的字段命名要有规范。标准 claim 用标准名称sub、exp、iat自定义 claim 加前缀避免冲突。我见过两个系统集成时自定义字段名撞车导致数据覆盖的情况。第五token 长度要关注。Payload 里塞太多数据会让 token 变得很长某些服务器或中间件对请求头长度有限制可能导致请求被拒。一般建议 token 控制在几 KB 以内。第六测试环境用真实算法。有些团队测试时用none算法图方便上线前忘了改直接造成安全漏洞。测试环境也应该用和生产一致的算法只是密钥可以不同。这些经验看起来简单但每一条背后都有真实的踩坑故事。JWT 本身不复杂复杂的是各种边界情况和安全细节。把解析工具用对把原理搞清楚把安全实践落实到位才能真正发挥 JWT 的价值。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进