JWT 在线解码、验签、生成一篇讲透:附前端实现、工具架构与在线体验地址

在线玩:https://geekformat.com/zh-CN/encode/jwt

先说结论

如果你搜过「JWT 解码 」「JWT 在线验证 」「JWT 生成器 」「JWT PEM JWK 怎么用」,大概率不是想先看一堆抽象概念,而是想马上解决这几个问题:

  • 这个 token 里到底写了什么?
  • 为什么后端说签名不对?
  • HS256RS256ES256PS256 到底怎么切?
  • 公钥私钥是 PEM 还是 JWK,到底该贴哪种?
  • 改了 payload 之后,怎么重新生成一个能用的 JWT?

所以这篇不只讲 JWT 原理,我会直接结合这个实际的页面实现来拆:

  • 页面实际能做到什么;
  • 一次完整的 JWT 排查流程怎么走;
  • 前端页面是怎么实现解码、验签、生成的;
  • 哪些代码你可以直接 copy 过去自己用。

这页工具不是"只能 decode 一下"的简单版,而是一个 JWT 工作台

  • 支持 decode / encode 双模式
  • 支持 HS256 / HS384 / HS512 / RS256 / RS384 / RS512 / ES256 / ES384 / ES512 / PS256 / PS384 / PS512
  • 支持 PEM / JWK 两种密钥格式;
  • 支持自动生成 RSA / ECDSA / RSA-PSS 密钥对
  • 支持实时验签、过期判断、Claims 语义化展示
  • 基于浏览器原生能力完成解码、签名和验签,不需要额外起一个后端中转服务。

一、先看效果:这个 JWT 工具到底能做什么

1. 粘贴一个 token,立刻拆成三段

JWT 本质上就是三段 Base64URL 字符串:

text 复制代码
header.payload.signature

例如:

text 复制代码
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.KMUFsIDTnFmyG3nMiGM6H9FNFUROf3wh7SmqJp-QV30
└────────── header ──────────┘└────────── payload ─────────┘└──────── signature ────────┘

页面会马上把它拆开,并分别展示:

  • Header
  • Payload
  • Signature

而且 Header、Payload 会自动做 Base64URL 解码JSON 格式化

2. 自动识别算法,并显示 token 是否过期

页面会从 Header 中读出 alg,比如:

json 复制代码
{"alg":"HS256","typ":"JWT"}

然后:

  • 识别当前 token 使用的是哪种算法;
  • 读取 exp / iat / nbf 这些标准 Claims;
  • 自动把 Unix 时间戳转换成可读时间;
  • 如果 exp 已经过期,直接高亮提示。

这一步对接口联调很有用。很多时候你以为是签名错了,结果只是token 过期了。

3. 直接输入密钥,实时验证签名

这页真正有价值的地方,不是"能看 payload",而是 能验签

不同算法对应不同输入:

  • HS256 / HS384 / HS512 :输入 shared secret
  • RS256 / RS384 / RS512 / PS256 / PS384 / PS512 :输入 公钥
  • ES256 / ES384 / ES512 :输入 公钥

而且密钥格式既支持:

  • PEM
  • JWK

输入正确后,页面会实时显示:

  • 签名验证通过
  • 签名验证失败
  • 密钥格式错误

4. 不只是解码,还能直接生成新的 JWT

切到 encode 模式后,你可以直接:

  • 改 Header JSON
  • 改 Payload JSON
  • 选择算法
  • 填 Secret 或 Private Key
  • 实时生成新的 token

这对于下面这些场景非常实用:

  • 本地模拟登录态
  • 测试网关 / API 鉴权
  • 复现线上 bug
  • 验证第三方系统发来的 JWT 格式

5. 基于浏览器原生能力直接完成计算

这页实现上比较实用的一点,是直接使用浏览器原生 Web Crypto API 做签名和验签,而不是再额外走一层后端服务。

  • 前端页面自己就能把解码、验签、生成这条链路跑通;
  • 不需要为了一个调试页再补一层服务端接口;
  • 算法切换、密钥格式切换都能实时反馈结果。

二、一个真实排障流程:为什么这个 JWT 验签失败

如果你是后端、前端、测试,最常见的问题通常不是"JWT 是什么",而是:

我手里明明有 token,为什么服务端还是说 unauthorized?

这时可以用这页工具按下面顺序排。

第一步:先确认 JWT 格式是不是完整

一个合法 JWT 必须有三段:

text 复制代码
header.payload.signature

如果只有两段,或者多了空格、换行、前后缀,页面会直接提示格式错误。这一步能先排除很多复制粘贴问题。

第二步:看 Header 里的算法是不是你以为的那个

很多问题不是"签名算错了",而是算法根本不一致。

比如你以为后端在用:

json 复制代码
{"alg":"RS256","typ":"JWT"}

结果 token 实际上是:

json 复制代码
{"alg":"PS256","typ":"JWT"}

RS256PS256 看起来只差两个字母,但它们不是同一个签名方案,直接混用一定验不过。

第三步:确认你喂进去的是对的密钥

这里是排障最常见的坑:

  • HS256 需要的是 secret,不是公钥
  • RS256 / ES256 / PS256 验签时需要的是 public key ,不是 private key
  • 某些系统给你的是 JWK ,不是 PEM
  • 某些 HMAC secret 实际是 base64 编码后的字节,不是普通字符串

这页工具里专门做了两件事来降低误用:

  • 提供 PEM / JWK 切换;
  • HMAC 提供 base64 encoded 开关。

这两个开关很小,但对真实联调非常关键。

第四步:看是不是过期,不要误判成签名错误

很多同学第一反应是"验签失败",但实际上问题可能是:

  • exp 已过期
  • nbf 还没到生效时间
  • 服务端时钟和客户端时钟有偏差

页面会把 exp 转成可读时间,并高亮"已过期 / 未过期",这个细节能少走很多弯路。

第五步:如果 payload 改过,必须重新签名

JWT 默认不是加密,而是签名。

这意味着:

  • Header、Payload 都能被任何人读出来;
  • 但只要你改了其中任意一个字节,原来的 Signature 就失效。

所以正确流程不是"我改完 payload 再拿原 token 去请求",而是:

  1. 修改 Header / Payload
  2. 用正确算法和密钥重新签名
  3. 拿新生成的 token 去请求

这也是为什么页面里一定要同时提供 decodeencode 两个模式。只做解码,排障链路是不完整的。


三、JWT 到底是什么:用「防伪火车票」讲明白

现在再回来看 JWT 原理,就会更容易理解。

很多人第一次接触 JWT,会误以为它是一段"加密后的字符串"。其实不准确。

JWT 更像一张带防伪码的车票

JWT 火车票
header 票种说明
payload 乘客、车次、座位、发车时间
signature 钢印 / 防伪码

JWT 不是为了"防偷看",而是为了"防伪造"。

也就是说:

  • Header 和 Payload 通常都能被解码看到;
  • Signature 的作用是证明前两段没有被篡改。

服务端收到 token 后,会把:

text 复制代码
header.payload

重新按约定算法签一遍,再去和 token 里自带的 signature 比较:

  • 一样:说明内容没被改过
  • 不一样:说明 token 被伪造或密钥不匹配

一句话总结:

JWT 默认是"可读取但不可篡改",不是"谁都看不懂"。

这也是为什么 payload 里绝对不能放密码、身份证号、银行卡号这类敏感明文。


四、页面实现思路:前端怎么把 JWT 工作台做完整

下面这部分才是和页面最贴合的内容。不是泛泛聊 JWT,而是拆我们这个工具页为什么能工作。

整体流程图

flowchart LR A"用户输入 Token / 编辑 Header Payload" --> B"拆分三段" B --> C"Base64URL 解码 Header 和 Payload" C --> D"JSON 格式化展示" B --> E"读取 header.alg" E --> F"选择 HMAC / RSA / ECDSA / RSA-PSS" F --> G"导入 Secret / PEM / JWK" G --> H"Web Crypto 签名或验签" C --> I"解析 exp iat nbf 等 Claims" H --> J"显示验签结果" I --> K"显示过期时间与状态" J --> L"前端直接完成计算" K --> L

对应到页面能力,大致可以拆成 5 层:

  1. Token 解析层
  2. Base64URL 编解码层
  3. 算法与密钥导入层
  4. 签名 / 验签层
  5. UI 展示层

1. Token 解析层

核心逻辑其实很直接:先按 . 拆分,再分别解码 Header、Payload。

javascript 复制代码
function parseJwt(token) {
  const parts = token.trim().split('.')
  if (parts.length !== 3) throw new Error('JWT 格式错误')

  const header = JSON.parse(b64UrlDecode(parts[0]))
  const payload = JSON.parse(b64UrlDecode(parts[1]))

  return {
    header: parts[0],
    payload: parts[1],
    signature: parts[2],
    headerObj: header,
    payloadObj: payload,
  }
}

这个阶段只做三件事:

  • 格式校验
  • Base64URL 解码
  • JSON 解析

如果这里报错,后面所有流程都不用继续。

2. Base64URL 编解码层

JWT 用的不是普通 Base64,而是 Base64URL:

  • + 换成 -
  • / 换成 _
  • 去掉结尾 =

浏览器里可以这样处理:

javascript 复制代码
function b64UrlDecode(str) {
  let s = str.replace(/-/g, '+').replace(/_/g, '/')
  while (s.length % 4) s += '='
  return decodeURIComponent(escape(atob(s)))
}

function b64UrlEncodeStr(str) {
  return btoa(unescape(encodeURIComponent(str)))
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '')
}

别小看这层。JWT 联调里非常多"明明看起来一样但验签失败"的问题,本质就是 base64base64url 混了。

3. 算法与密钥导入层

页面真正麻烦的地方,不是 decode,而是要统一处理不同算法族:

  • HMAC
  • RSA
  • ECDSA
  • RSA-PSS

而且每种算法需要的密钥格式又不一样。

页面里比较实用的做法是先把算法抽象成配置对象:

javascript 复制代码
const ALGORITHMS = [
  { value: 'HS256', type: 'hmac', hash: 'SHA-256' },
  { value: 'RS256', type: 'rsa', hash: 'SHA-256' },
  { value: 'ES256', type: 'ecdsa', hash: 'SHA-256', namedCurve: 'P-256' },
  { value: 'PS256', type: 'rsapss', hash: 'SHA-256' },
]

这样后面导入密钥、签名、验签时,就不需要写一堆零散 if else

对于非对称算法,还要同时兼容:

  • PEM
  • JWK

这一步很关键,因为很多 OAuth / OIDC / 网关系统 给出来的就是 JWK

4. 签名与验签层

浏览器端我们直接用原生 crypto.subtle

HMAC 的签名逻辑大致这样:

javascript 复制代码
async function signHmac(data, secret) {
  const key = await crypto.subtle.importKey(
    'raw',
    new TextEncoder().encode(secret),
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign']
  )
  const sig = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(data))
  return b64UrlEncode(sig)
}

RSA / ECDSA / RSA-PSS 则先导入公私钥,再调用不同参数的 signverify

这里有两个实现细节很值得写进文章:

  1. decode 模式下,非对称算法要用 public key 验签
  2. encode 模式下,非对称算法要用 private key 签名

很多实现做不全,就是卡在这里,最后只能" "不能""。

5. Claims 展示层

如果只是把 payload 原样打印出来,其实还不够好用。

更实用的做法是做一层"语义增强":

  • exp 标成"过期时间"
  • iat 标成"签发时间"
  • nbf 标成"生效时间"
  • Unix 时间戳自动转人类可读时间
  • 过期状态直接高亮

这部分不复杂,但对排障体验很有帮助。用户不用自己再把时间戳复制出去换算,也更容易第一眼看出问题到底出在签名、时间还是 Claims


五、可以直接复制的核心代码

如果你想把这个能力放进自己的项目里,下面几段就够你起步。

1. 50 行 Node.js:手写 HS256 签发 + 验签

javascript 复制代码
// jwt-demo.js
const crypto = require('node:crypto')

function b64url(input) {
  return Buffer.from(input)
    .toString('base64')
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '')
}

function hmacSign(data, secret) {
  return b64url(crypto.createHmac('sha256', secret).update(data).digest())
}

function sign(payload, secret) {
  const header = b64url(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))
  const body = b64url(JSON.stringify(payload))
  const signature = hmacSign(`${header}.${body}`, secret)
  return `${header}.${body}.${signature}`
}

function verify(token, secret) {
  const [h, p, s] = token.split('.')
  const expected = hmacSign(`${h}.${p}`, secret)
  const a = Buffer.from(s)
  const b = Buffer.from(expected)
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}

const SECRET = 'my-super-secret-key'
const payload = { userId: 42, role: 'admin', exp: Math.floor(Date.now() / 1000) + 3600 }
const token = sign(payload, SECRET)

console.log(token)
console.log(verify(token, SECRET))       // true
console.log(verify(token, 'wrong-key'))  // false

这段代码的意义主要是把 JWT 最核心的机制讲明白:Header + Payload 先编码,再对 header.payload 做签名

2. 浏览器端:原生 Web Crypto 做 HMAC

html 复制代码
<!doctype html>
<html>
  <body>
    <pre id="out"></pre>
    <script>
      const out = document.getElementById('out')

      const b64url = buf =>
        btoa(String.fromCharCode(...new Uint8Array(buf)))
          .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')

      const b64urlStr = str => b64url(new TextEncoder().encode(str))

      async function sign(payload, secret) {
        const header = b64urlStr(JSON.stringify({ alg: 'HS256', typ: 'JWT' }))
        const body = b64urlStr(JSON.stringify(payload))
        const data = new TextEncoder().encode(`${header}.${body}`)

        const key = await crypto.subtle.importKey(
          'raw',
          new TextEncoder().encode(secret),
          { name: 'HMAC', hash: 'SHA-256' },
          false,
          ['sign']
        )

        const sig = await crypto.subtle.sign('HMAC', key, data)
        return `${header}.${body}.${b64url(sig)}`
      }

      ;(async () => {
        const token = await sign({ user: 'alice', role: 'admin' }, 'browser-demo-secret')
        out.textContent = token
      })()
    </script>
  </body>
</html>

这就是为什么这个页面可以直接在前端完成 HMAC 签名和验签,而不用再补一个服务端调试接口。

3. 为什么还要补非对称算法支持

如果你只做 HS256,会发现很多真实用户其实用不上。因为很多现代认证系统、OIDC、第三方平台、企业网关默认给你的往往是:

  • RS256
  • ES256
  • PS256
  • JWK

所以实现上不能只停在 HS256 decode ,而是要把非对称算法、密钥格式和验签流程一并补齐,不然覆盖到的只是很小一部分真实场景。


六、真实项目里最容易踩的 6 个坑

1. JWT 不是加密,是签名

Payload 能读,不代表不安全;Payload 能被随便看,才是默认状态。

所以别把下面这些放进去:

  • 密码
  • 身份证号
  • 银行卡号
  • access key / secret

2. 不要相信客户端传来的 alg

错误示例:

javascript 复制代码
function badVerify(token) {
  const header = JSON.parse(Buffer.from(token.split('.')[0], 'base64').toString())
  return verifyByAlg(token, header.alg)
}

正确做法是服务端强制使用自己的配置算法,而不是跟着 token 头部走。

3. base64base64url 不能混

JWT 一定是 base64url ,不是普通 base64 。差别虽然只有几个字符,但一旦混用,签名必然不一致。

4. RS256PS256 不是一回事

很多人以为"都是 RSA 家族,应该能通用"。实际上不能。

  • RS256RSASSA-PKCS1-v1_5
  • PS256RSA-PSS

尤其 RSA-PSS 还要显式处理 saltLength,跨语言联调时很容易出问题。

5. HMAC secret 可能是原始字符串,也可能是 base64 字节

这也是为什么页面里要有 base64 encoded 开关。很多用户不是不会验签,而是把 secret 的表达形式用错了

6. 过期判断是业务校验,不只是签名校验

签名通过,只代表 token 没被改过;

不代表:

  • 没过期
  • 已生效
  • 受众正确
  • 签发者可信

所以真实服务端里,验签只是第一步。


七、为什么这种工具比纯解码器更好用

很多 JWT 页面只能做一件事:把 Payload 解出来给你看。

但真实项目里,光"看见内容"通常不够,你还会继续碰到这些问题:

  • 签名到底有没有通过
  • 算法是不是用错了
  • 这个 token 是不是已经过期
  • 改完 Payload 之后怎么重新生成
  • 公钥、私钥、Secret 到底该填哪个

所以更完整的做法应该是把几个动作连起来:

  • 先解码
  • 再验签
  • 再判断 Claims
  • 必要时重新生成

这样才更接近日常联调和排障场景,而不是只适合"看一眼内容"。


八、最后给一句最实用的判断标准

如果一个 JWT 页面只能把 Payload 解出来,它最多算"查看器"。

如果它还能:

  • 切算法
  • 验签
  • PEM / JWK
  • 处理 HMAC secret 的不同输入形式
  • 自动生成非对称密钥对
  • 识别 exp / iat / nbf
  • 前端直接完成计算

那它才更接近真实项目里能反复用到的 JWT 工作台

这也是我们这页想解决的核心问题:不是只让你"看见" JWT,而是让你 把 JWT 调通

如果你平时调试 JWT 的流程不只停留在"解码看看",通常还会连着处理这些步骤:

  • Base64 / Base64URL 编解码
  • HMAC 生成与验证
  • Authorization Header 构造
  • HTTP 请求调试

这些动作本来就经常会连在一起出现,所以实现上也不应该只做单点解析,而是尽量把整条排障链路补完整。

如果你正好在找一个能直接上手的在线 JWT 工具,可以直接试试这个页面:

适合平时调试 token、排查鉴权、验证签名和快速生成测试 JWT。

相关推荐
前端一小卒1 小时前
不手写代码的第 30 天,我才明白前端这个岗位还剩什么
前端·javascript·ai编程
Ajie'Blog1 小时前
Copilot Agent Tasks API 开放:AI 编程开始进入后台任务时代
服务器·前端·javascript·人工智能·copilot·ai编程
老毛肚2 小时前
jeecgboot vue TS & 模板化 04
前端·javascript·vue.js
晓13132 小时前
【Cocos Creator 2.x】篇——第二章 入门
javascript·游戏引擎
Electrolux4 小时前
[onlyoffice-v9]纯前端怎么实现编辑预览office
前端·javascript·github
VidDown4 小时前
Webhook 调试器:让第三方回调“原形毕露”
java·开发语言·javascript·编辑器·postman
kyriewen4 小时前
我读了一遍 Babel 编译后的 async/await,终于搞懂了它的原理(附 20 行手写实现)
前端·javascript·面试
半岛@少年6 小时前
都是JS,CJS和ESM有什么区别?
javascript·esm·前端模块化·cjs
想吃火锅10056 小时前
【leetcode】165.比较版本号js
javascript·算法·leetcode