DevToolBox免费
博客

不用库解码 JWT Token:JavaScript、Python、Bash 一行代码

6 分钟阅读作者 DevToolBox

每个 JWT 都只是三段 Base64URL 编码的字符串,用点号分隔。你不需要 jsonwebtokenPyJWT 或任何库就能读取其中的内容。本指南提供 JavaScript、Node.js、Python、Bash、Go 和 PHP 的可用单行命令,让你在任何环境中即时解码 JWT 令牌。

1. JWT 结构:header.payload.signature

JWT 由三个部分组成,用点号分隔。前两部分(header 和 payload)是 Base64URL 编码的 JSON 对象。第三部分是加密签名。你只需要解码前两部分就能读取令牌内容。

┌─────────────────────────────────────────────────────────────────────────────────┐
│                              JWT Token Layout                                   │
├──────────────────┬──────────────────────────────────┬──────────────────────────┤
│     HEADER       │           PAYLOAD                │       SIGNATURE          │
│  (algorithm)     │        (your data)               │    (verification)        │
├──────────────────┼──────────────────────────────────┼──────────────────────────┤
│ eyJhbGciOiJIUz   │ eyJzdWIiOiIxMjM0NTY3ODkwIiwi   │ SflKxwRJSMeKKF2QT4fw    │
│ I1NiIsInR5cCI6   │ bmFtZSI6IkpvaG4gRG9lIiwiaWF0   │ pMeJf36POk6yJV_adQss    │
│ IkpXVCJ9         │ IjoxNTE2MjM5MDIyfQ              │ w5c                      │
├──────────────────┼──────────────────────────────────┼──────────────────────────┤
│ Base64URL({      │ Base64URL({                      │ HMACSHA256(              │
│   "alg": "HS256" │   "sub": "1234567890",           │   header + "." +         │
│   "typ": "JWT"   │   "name": "John Doe",            │   payload,               │
│ })               │   "iat": 1516239022              │   secret                 │
│                  │ })                               │ )                        │
└──────────────────┴──────────────────────────────────┴──────────────────────────┘

Full token (dots separate the three parts):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

2. JavaScript 单行代码(浏览器)

浏览器提供 atob() 来解码 Base64 字符串。配合 JSON.parse(),你可以用一个表达式解码任何 JWT 负载。注意 atob() 期望标准 Base64,所以我们必须先替换 URL 安全字符。

// Decode JWT payload in the browser — one line
JSON.parse(atob('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'.split('.')[1].replace(/-/g,'+').replace(/_/g,'/')))

// Result: { sub: "1234567890", name: "John Doe", iat: 1516239022 }

// Reusable function (handles UTF-8 payloads too):
const jwtDecode = (t) => JSON.parse(decodeURIComponent(atob(t.split('.')[1].replace(/-/g,'+').replace(/_/g,'/')).split('').map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join('')));

// Decode the header:
JSON.parse(atob('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'.split('.')[0].replace(/-/g,'+').replace(/_/g,'/')))

3. Node.js 单行代码

旧版本的 Node.js 没有 atob(),但 Buffer.from() 从 Node 15.7+ 开始原生支持 Base64URL。这是服务端 JavaScript 最简洁的方式。

// Node.js — decode JWT payload in one line
JSON.parse(Buffer.from('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'.split('.')[1], 'base64url').toString())

// Result: { sub: '1234567890', name: 'John Doe', iat: 1516239022 }

// Decode header:
JSON.parse(Buffer.from('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'.split('.')[0], 'base64url').toString())

// For Node < 15.7 (no 'base64url' encoding):
JSON.parse(Buffer.from('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'.split('.')[1].replace(/-/g,'+').replace(/_/g,'/'), 'base64').toString())

4. Python 单行代码

Python 的 base64 模块有 urlsafe_b64decode,直接处理 URL 安全字母表。唯一的问题是需要添加填充,因为 JWT 会去掉尾部的 = 字符。

# Python — decode JWT payload in one line
import json, base64; print(json.loads(base64.urlsafe_b64decode('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'.split('.')[1] + '==')))

# Result: {'sub': '1234567890', 'name': 'John Doe', 'iat': 1516239022}

# Proper padding (handles any token length):
import json, base64; p = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'.split('.')[1]; print(json.loads(base64.urlsafe_b64decode(p + '=' * (-len(p) % 4))))

# Decode header:
import json, base64; h = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'.split('.')[0]; print(json.loads(base64.urlsafe_b64decode(h + '=' * (-len(h) % 4))))

# Result: {'alg': 'HS256', 'typ': 'JWT'}

5. Bash 单行命令

使用标准 Unix 工具(cutbase64jq),你可以直接在终端解码 JWT。这在 CI/CD 流水线、Docker 容器和 SSH 会话中调试时非常有用。

# Bash — decode JWT payload with base64 and jq
echo 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' | cut -d. -f2 | base64 -d 2>/dev/null | jq .

# Output:
# {
#   "sub": "1234567890",
#   "name": "John Doe",
#   "iat": 1516239022
# }

# macOS (uses -D flag for base64 decode):
echo 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' | cut -d. -f2 | base64 -D 2>/dev/null | jq .

# Decode header (change -f2 to -f1):
echo 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' | cut -d. -f1 | base64 -d 2>/dev/null | jq .

# Handle padding automatically (works on both Linux and macOS):
jwt='eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'; p=$(echo "$jwt" | cut -d. -f2); echo "$p"$(printf '%0.s=' $(seq 1 $((4 - ${#p} % 4)))) | base64 -d 2>/dev/null | jq .

# Without jq (Python fallback):
echo 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c' | cut -d. -f2 | base64 -d 2>/dev/null | python3 -m json.tool

6. Go 紧凑函数

Go 的标准库包含 encoding/base64,其 RawURLEncoding 可处理无填充的 Base64URL。无需第三方包。

package main

import (
    "encoding/base64"
    "encoding/json"
    "fmt"
    "strings"
)

func decodeJWT(token string) (header, payload map[string]interface{}, err error) {
    parts := strings.Split(token, ".")
    if len(parts) != 3 {
        return nil, nil, fmt.Errorf("invalid JWT: expected 3 parts, got %d", len(parts))
    }
    // Decode header
    hBytes, err := base64.RawURLEncoding.DecodeString(parts[0])
    if err != nil { return nil, nil, err }
    json.Unmarshal(hBytes, &header)
    // Decode payload
    pBytes, err := base64.RawURLEncoding.DecodeString(parts[1])
    if err != nil { return nil, nil, err }
    json.Unmarshal(pBytes, &payload)
    return header, payload, nil
}

func main() {
    token := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
    _, payload, _ := decodeJWT(token)
    fmt.Println(payload) // map[iat:1.516239022e+09 name:John Doe sub:1234567890]
}

// One-liner in Go playground:
// base64.RawURLEncoding.DecodeString(strings.Split(token, ".")[1])

7. PHP 单行代码

PHP 的 base64_decode() 对缺少填充比较宽容,通过简单的 strtr() 转换即可处理 URL 安全字符。适用于任何 PHP 版本。

<?php
// PHP — decode JWT payload in one line
$payload = json_decode(base64_decode(strtr(explode('.', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c')[1], '-_', '+/')), true);
print_r($payload);

// Output:
// Array ( [sub] => 1234567890 [name] => John Doe [iat] => 1516239022 )

// Decode header:
$header = json_decode(base64_decode(strtr(explode('.', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c')[0], '-_', '+/')), true);
print_r($header);

// Output:
// Array ( [alg] => HS256 [typ] => JWT )

// As a reusable function:
function jwt_decode_payload(string $token): array {
    return json_decode(base64_decode(strtr(explode('.', $token)[1], '-_', '+/')), true);
}
?>

8. 原理:Base64URL vs Base64

JWT 使用 Base64URL 编码(RFC 4648 第 5 节),与标准 Base64 只有两个字符的区别。理解这个区别是不依赖库解码 JWT 的关键。

标准 Base64 使用 +/ 作为第 62 和 63 个字符,= 作为填充。Base64URL 将 + 替换为 -/ 替换为 _,并去掉填充。这使得 JWT 令牌可以安全地嵌入 URL、HTTP 头和 Cookie 中,无需转义。

CharacterBase64 (Standard)Base64URL (JWT)
62nd character+-
63rd character/_
Padding= (required)Stripped (omitted)
URL-safe?No (needs percent-encoding)Yes
// Conversion formula (any language):
base64url_to_base64(s) = s.replace(/-/g, '+').replace(/_/g, '/') + '==='.slice(0, (4 - s.length % 4) % 4)
base64_to_base64url(s) = s.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')

9. 安全警告:解码 != 验证

解码 JWT 并不等于验证它。任何人都可以创建包含任意负载的 JWT。解码只能揭示令牌中的声明,并不能告诉你令牌是否真实、未过期或未被修改。

  • 在验证签名之前,永远不要信任解码后的负载数据。
  • 永远不要使用手动解码来做认证或授权决策。
  • 签名(第三部分)需要密钥或公钥才能验证。
  • 对于任何安全敏感操作,使用经过验证的库(jsonwebtoken、PyJWT、go-jwt)。
  • 手动解码仅适用于调试、日志记录和检查令牌内容。
// WRONG: Using decoded payload for auth decisions
const user = JSON.parse(atob(token.split('.')[1]));
if (user.role === 'admin') grantAccess(); // DANGEROUS — token could be forged!

// RIGHT: Verify first, then trust the payload
const verified = jwt.verify(token, SECRET_KEY);     // Throws if invalid
if (verified.role === 'admin') grantAccess();        // Safe — signature checked

10. 常见陷阱

缺少填充

JWT 去掉 Base64 填充(=)以保持令牌紧凑。大多数 Base64 解码器期望有填充。解决方法:追加 = 字符直到字符串长度是 4 的倍数,或使用处理无填充输入的解码器(如 Go 的 RawURLEncoding 或 Python 的 urlsafe_b64decode 加手动填充)。

URL 安全字符未替换

标准 atob() 和许多 Base64 解码器只接受 +/。如果忘记将 -_ 替换回 +/,解码器会报错或产生乱码。

UTF-8 编码问题

如果 JWT 负载包含非 ASCII 字符(如中文、阿拉伯文或表情符号),atob() 会出错,因为它返回 Latin-1 而不是 UTF-8。在浏览器中用 decodeURIComponent(escape(atob(...))) 包裹或使用 TextDecoder。Node.js 的 Buffer 默认正确处理 UTF-8。

试图解码签名

第三部分是二进制哈希,不是 JSON。不要尝试 JSON.parse() 签名部分,这会失败。签名只有在使用签名密钥验证 header + payload 时才有意义。

// Fix padding issue (JavaScript):
const pad = (s) => s + '='.repeat((4 - s.length % 4) % 4);
JSON.parse(atob(pad(token.split('.')[1].replace(/-/g,'+').replace(/_/g,'/'))))

// Fix UTF-8 issue (Browser):
const decodeJWT = (token) => {
  const base64 = token.split('.')[1].replace(/-/g,'+').replace(/_/g,'/');
  const bytes = Uint8Array.from(atob(base64), c => c.charCodeAt(0));
  return JSON.parse(new TextDecoder().decode(bytes));
};

11. 什么时候应该使用库

手动解码非常适合快速检查,但在以下情况下应该使用正规的 JWT 库:

  • 需要验证签名(认证和授权)。
  • 需要检查过期时间(exp)、生效时间(nbf)和其他时间相关的声明。
  • 处理使用公钥/私钥对的 RS256/ES256 令牌。
  • 需要创建(签名)新令牌,而不仅仅是读取现有令牌。
  • 构建保护路由访问的中间件。
LanguageLibraryInstall
Node.jsjsonwebtokennpm install jsonwebtoken
PythonPyJWTpip install PyJWT
Gogolang-jwt/jwtgo get github.com/golang-jwt/jwt/v5
PHPfirebase/php-jwtcomposer require firebase/php-jwt
JavajjwtMaven: io.jsonwebtoken:jjwt-api
Rustjsonwebtokencargo add jsonwebtoken

使用我们的 JWT 解码工具即时解码 JWT 令牌

常见问题

可以不用任何库解码 JWT 吗?

可以。JWT 负载只是 Base64URL 编码的 JSON 字符串。你可以用每种编程语言都有的内置 Base64 函数来解码。只有验证签名时才需要库。

在客户端解码 JWT 安全吗?

客户端解码用于展示目的是安全的(如显示用户名或在 UI 中检查过期时间)。但不要仅基于客户端解码做安全决策,因为令牌可能是伪造的。始终在服务器端验证签名。

为什么 atob() 对某些 JWT 令牌会失败?

atob() 期望标准 Base64,但 JWT 使用 Base64URL 编码,将 + 替换为 - ,/ 替换为 _。你必须在调用 atob() 之前转换这些字符。此外,atob() 不能正确处理 UTF-8 多字节字符,对于非 ASCII 负载请使用 TextDecoder。

Base64 和 Base64URL 有什么区别?

Base64URL(RFC 4648 第 5 节)将 + 替换为 -,/ 替换为 _,使输出 URL 安全。它还去掉填充(=)字符。JWT 使用 Base64URL,这样令牌可以出现在 URL、Cookie 和 HTTP 头中,不会有编码问题。

可以修改解码后的 JWT 然后使用吗?

你可以解码并修改负载,但修改后的令牌将有一个无效的签名。任何正确验证签名的服务器都会拒绝它。这正是 JWT 防止篡改的方式:签名覆盖了精确的 header 和 payload 字节。

𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

JWTJWT DecoderB64Base64 Encoder/DecoderJPJWT ParserJVJWT Validator

相关文章

JWT 工作原理:JSON Web Token 完全指南

了解 JWT 认证的工作原理,理解 header、payload 和 signature 的结构,安全地在应用中实现 JWT。

API 认证:OAuth 2.0 vs JWT vs API Key

比较 API 认证方式:OAuth 2.0、JWT Bearer Token 和 API Key。了解每种方式的适用场景、安全权衡和实现模式。