欢迎光临
我们一直在努力

JSON Web Token(JWT)完全指南

JWT(JSON Web Token)是现代 Web 应用中最广泛使用的身份验证与信息交换标准之一。本文从原理到实践,系统性地介绍 JWT 的方方面面。


一、什么是 JWT?

JWT(JSON Web Token)是一种开放标准(RFC 7519),定义了一种紧凑且自包含的方式,以 JSON 对象的形式在各方之间安全地传输信息。由于数字签名的存在,这些信息是可验证且可信的。

两种核心使用场景:

授权(Authorization):这是 JWT 最常见的用途。用户登录后,每个后续请求都携带 JWT,允许用户访问令牌许可的路由、服务和资源。SSO(Single Sign-On)大量使用 JWT,因为其开销很小,且可跨域使用。

信息交换(Information Exchange):JWT 是在各方之间安全传输信息的好方法。由于可以对 JWT 签名(例如使用公/私钥对),你可以确认发送者的身份。此外,由于签名是用 header 和 payload 计算的,还可以验证内容未被篡改。


二、JWT 的结构

JWT 由三部分组成,用点号(.)分隔:

Header.Payload.Signature

一个真实的 JWT 看起来像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
```### 2.1 Header(头部)

Header 通常由两部分组成:令牌的类型(`typ`,即 `JWT`)以及所使用的签名算法(`alg`),例如 HMAC SHA256 或 RSA。

```json
{
"alg": "HS256",
"typ": "JWT"
}

然后对该 JSON 进行 Base64URL 编码,形成 JWT 的第一部分。

2.2 Payload(载荷)

Payload 包含声明(Claims)。声明是关于实体(通常是用户)及附加数据的陈述。JWT 定义了三种类型的声明:

注册声明(Registered Claims)——预定义的、非强制性但推荐使用的声明集:

字段全称说明
iss Issuer 签发方
sub Subject 主题(通常为用户 ID)
aud Audience 接收方
exp Expiration Time 过期时间(Unix 时间戳)
nbf Not Before 生效时间
iat Issued At 签发时间
jti JWT ID JWT 唯一标识,可防重放

公共声明(Public Claims)——由使用者自由定义,但应在 IANA JSON Web Token Registry 中注册或使用带命名空间的 URI,以避免冲突。

私有声明(Private Claims)——通信双方约定的自定义字段,例如 "role": "admin" 或 "permissions": ["read", "write"]。

{
"sub": "1234567890",
"name": "Alice",
"role": "admin",
"iat": 1516239022,
"exp": 1516242622
}

重要:Payload 是经过 Base64URL 编码的,而非加密的。任何持有令牌的人都可以解码并读取内容。绝对不要将密码、敏感个人信息等放入 Payload。

2.3 Signature(签名)

签名用于验证消息在传输过程中未被篡改,对于使用私钥签名的令牌,还可以验证 JWT 的发送者身份。

以 HS256 算法为例,签名的计算方式如下:

HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)


三、JWT 的工作流程整体流程分为两个阶段:认证阶段(一次性)和授权阶段(每次请求)。

在实际 HTTP 请求中,JWT 通常放在 Authorization 请求头中,格式为:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…


四、签名算法详解

JWT 支持多种签名算法,选择哪种取决于你的安全需求和架构模式。

4.1 对称算法(HMAC)

使用同一个密钥进行签名和验证。

HS256 → HMAC + SHA-256
HS384 → HMAC + SHA-384
HS512 → HMAC + SHA-512

适用场景:签发方与验证方是同一个服务(例如单体应用,或内部微服务之间的信任通信)。

风险:所有需要验证 JWT 的服务都必须持有密钥,一旦泄露,所有令牌都可伪造。

4.2 非对称算法(RSA / ECDSA)

使用私钥签名,公钥验证。

RS256 → RSA + SHA-256(最常用)
RS384 / RS512
ES256 → ECDSA + P-256(更小的密钥,更快的计算)
ES384 / ES512

适用场景:Auth Server 持有私钥签发令牌,各 Resource Server 只需公钥即可验证,无需信任链扩散。这是微服务架构中的推荐方案。

私钥(Auth Server 独有)→ 签名 JWT
公钥(所有 Resource Server 共享)→ 验证签名


五、实战代码示例

5.1 Node.js(jsonwebtoken)

const jwt = require('jsonwebtoken');

// 签发 JWT
function issueToken(userId, role) {
const payload = {
sub: userId,
role: role,
iat: Math.floor(Date.now() / 1000),
};

return jwt.sign(payload, process.env.JWT_SECRET, {
algorithm: 'HS256',
expiresIn: '2h', // 过期时间
issuer: 'my-app',
audience: 'my-api',
});
}

// 验证 JWT
function verifyToken(token) {
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256'], // 白名单,防止 alg:none 攻击
issuer: 'my-app',
audience: 'my-api',
});
return { ok: true, payload: decoded };
} catch (err) {
return { ok: false, error: err.message };
}
}

// Express 中间件示例
function authMiddleware(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing token' });
}

const token = authHeader.slice(7);
const result = verifyToken(token);

if (!result.ok) {
return res.status(401).json({ error: result.error });
}

req.user = result.payload; // 注入用户信息
next();
}

5.2 Python(PyJWT)

import jwt
import os
from datetime import datetime, timedelta, timezone

SECRET_KEY = os.environ["JWT_SECRET"]
ALGORITHM = "HS256"

# 签发 JWT
def create_token(user_id: str, role: str) > str:
payload = {
"sub": user_id,
"role": role,
"iat": datetime.now(timezone.utc),
"exp": datetime.now(timezone.utc) + timedelta(hours=2),
"iss": "my-app",
}
return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)

# 验证 JWT
def verify_token(token: str) > dict:
try:
payload = jwt.decode(
token,
SECRET_KEY,
algorithms=[ALGORITHM], # 白名单
issuer="my-app",
)
return {"ok": True, "payload": payload}
except jwt.ExpiredSignatureError:
return {"ok": False, "error": "Token expired"}
except jwt.InvalidTokenError as e:
return {"ok": False, "error": str(e)}


六、安全最佳实践

这是 JWT 开发中最容易出错的地方。以下问题在真实漏洞报告中反复出现。

6.1 算法混淆攻击(alg:none)

危险代码:

// ❌ 危险:信任 Header 中的 alg 字段
const decoded = jwt.verify(token, secret);

攻击者可以将 Header 中的 alg 改为 none,然后发送一个没有签名的令牌,部分库会错误地接受它。

正确做法:

// ✅ 安全:在验证时显式指定允许的算法白名单
const decoded = jwt.verify(token, secret, { algorithms: ['HS256'] });

6.2 RS256/HS256 混淆攻击

若服务端使用 RS256,攻击者可以将 alg 改为 HS256,并用服务端的公钥(通常是公开的)作为 HMAC 密钥来签名伪造令牌。始终在验证时硬编码期望的算法,而不是读取令牌 Header 中的算法。

6.3 密钥强度

HMAC 密钥应至少 256 位(32 字节),且随机生成,绝不使用短字符串或常见词组:

# 生成强随机密钥
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"

6.4 过期时间(exp)

永远要设置过期时间。Access Token 建议 15 分钟到 2 小时,Refresh Token 可以更长(7 到 30 天)。

jwt.sign(payload, secret, { expiresIn: '15m' }); // 短期令牌

6.5 令牌存储

存储位置XSS 风险CSRF 风险推荐度
localStorage 高(JS 可读) ❌ 不推荐
sessionStorage 高(JS 可读) ❌ 不推荐
内存(JS 变量) ✅ 推荐
HttpOnly Cookie 无(JS 不可读) 需配合 CSRF Token ✅ 推荐

6.6 不可撤销性问题与解决方案

JWT 的一大设计特点是无状态——服务端不存储令牌,因此无法主动使其失效。这在以下场景下成为问题:

  • 用户主动登出
  • 账号被封禁
  • 权限变更需立即生效

常用解决方案:

方案一:短过期时间 + Refresh Token 机制
Access Token → 15 分钟过期(泄露影响窗口小)
Refresh Token → 存储在服务端,支持主动吊销

方案二:Token 黑名单(Blocklist)
Redis 存储已吊销的 jti,验证时检查(牺牲部分无状态性)

方案三:Token 版本号
用户表中存储 token_version,每次登出自增
JWT 中携带版本号,验证时与数据库比对


七、JWT vs Session:如何选择?

维度JWTSession
状态 无状态,服务端不存储 有状态,服务端存储 Session
水平扩展 天然支持,无需共享存储 需 Session 共享(Redis 等)
令牌吊销 困难(需额外机制) 简单(直接删除 Session)
跨域/跨服务 天然支持(Authorization Header) 受 Cookie 同源限制
网络开销 每次请求携带令牌(较大) Cookie 较小
微服务/SSO 非常适合 较难实现
即时权限变更 需要额外设计 立即生效

选择建议:微服务架构、需要 SSO(Single Sign-On)的场景、无状态 API,倾向 JWT;传统 Web 应用、需要立即吊销会话、更严格的安全控制,倾向 Session 或两者结合(使用 JWT 作为 Refresh Token 的凭证,Session 作为短期授权)。


八、Refresh Token 模式

生产环境中,通常结合使用短期 Access Token 和长期 Refresh Token:

登录成功

返回:
access_token(JWT,15 分钟过期)
refresh_token(不透明随机字符串,30 天,存储在 HttpOnly Cookie)

访问资源:携带 access_token
↓ 若 access_token 过期(401)

用 refresh_token 换取新 access_token(POST /auth/refresh)
→ Auth Server 验证 refresh_token(查库)
→ 若有效,颁发新 access_token(可选:Rotation,同时更新 refresh_token)

继续使用新 access_token

Refresh Token Rotation(轮换)是一种重要的安全机制:每次使用 Refresh Token 时,同时使旧令牌失效并颁发新令牌,若检测到旧令牌被重复使用,则认为令牌已被盗,立即吊销所有相关令牌。


九、常见误区总结

误区一:JWT 是加密的。 JWT 默认只做签名(JWS),不加密。Payload 是 Base64URL 编码,任何人都可以解码读取。若需加密,使用 JWE(JSON Web Encryption)。

误区二:JWT 比 Session 更安全。 JWT 和 Session 各有优劣,安全性取决于正确实现,不是 JWT 本身更安全。

误区三:在 JWT 中存储密码或敏感数据。 绝不这样做。

误区四:JWT 可以无限使用。 一定要设置合理的过期时间,并考虑令牌吊销机制。

误区五:alg: none 被某些库接受是小事。 这是一个严重的安全漏洞,一定要在验证时白名单指定算法。


JWT 凭借其无状态、自包含、跨语言的特性,成为了现代 API 认证的事实标准。但使用得当的关键在于理解它的设计边界:JWT 解决了"验证信息的真实性与完整性"的问题,而"令牌吊销"、"存储安全"等问题需要在架构层面补充解决。掌握这些,才能将 JWT 用好、用安全。

赞(0)
未经允许不得转载:171主机测评 » JSON Web Token(JWT)完全指南
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址