JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全地传输信息作为 JSON 对象。由于其紧凑、自包含和跨域友好的特性,JWT 已成为现代 Web 应用中身份认证和信息交换的主流方案。本文将从 JWT 的基本概念出发,深入剖析其结构原理、工作机制、应用场景、安全性分析及最佳实践,帮助开发者全面掌握 JWT 技术。
一、JWT 概述
JWT 是一种基于 JSON 的开放标准,用于在网络应用环境间传递声明。它由三部分组成:Header(头部)、Payload(负载)和 Signature(签名)。JWT 的主要特点包括:
- 紧凑性:JWT 体积小,可以通过 URL、POST 参数或 HTTP Header 发送
- 自包含:JWT 包含了用户所需的所有信息,避免了多次查询数据库
- 跨域友好:JWT 天然支持跨域认证,适合分布式系统和微服务架构
- 无状态:服务器不需要存储会话信息,易于水平扩展
二、JWT 结构详解
JWT 由三个部分组成,用点(.)分隔,格式为:Header.Payload.Signature。下面分别详细介绍每个部分的作用和内容。
2.1 Header(头部)
Header 通常由两部分组成:令牌的类型(即 JWT)和所使用的签名算法(如 HMAC SHA256 或 RSA)。然后,这个 JSON 对象会被 Base64Url 编码,形成 JWT 的第一部分。
// JWT Header 示例
{
"alg": "HS256",
"typ": "JWT"
}
// Base64Url 编码后:
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
2.2 Payload(负载)
Payload 部分包含声明(Claims),声明是关于实体(通常是用户)和其他数据的陈述。声明分为三种类型:注册声明、公共声明和私有声明。同样,Payload 也会被 Base64Url 编码,形成 JWT 的第二部分。
声明类型
注册声明(Registered Claims):这些是一组预定义的声明,不是强制的,但推荐使用,以提供一组有用的、可互操作的声明。
公共声明(Public Claims):这些可以由使用 JWT 的人随意定义。但为了避免冲突,它们应该在 IANA JSON Web Token Registry 中定义,或者定义为包含抗冲突命名空间的 URI。
私有声明(Private Claims):这些是为了在同意使用它们的各方之间共享信息而创建的自定义声明,既不是注册声明也不是公共声明。
| 声明类型 | 名称 | 缩写 | 说明 |
|---|---|---|---|
| 注册声明(Registered Claims) | 签发者 | iss | JWT 的签发者 |
| 主题 | sub | JWT 的主题(通常是用户 ID) | |
| 受众 | aud | JWT 的接收方 | |
| 过期时间 | exp | JWT 的过期时间戳 | |
| 生效时间 | nbf | JWT 在此时间之前不可用 | |
| 签发时间 | iat | JWT 的签发时间戳 | |
| 编号 | jti | JWT 的唯一标识,用于防止重放攻击 |
// JWT Payload 示例
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 1516242622,
"role": "admin"
}
// Base64Url 编码后:
// eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyNDI2MjIsInJvbGUiOiJhZG1pbiJ9
2.3 Signature(签名)
Signature 部分用于验证消息在传输过程中没有被篡改。对于使用私钥签名的令牌,它还可以验证 JWT 的发送方的身份。签名的生成过程是:将编码后的 Header、编码后的 Payload 和一个密钥,使用 Header 中指定的算法进行签名。
签名公式:HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
| 算法 | 类型 | 密钥 | 安全性 |
|---|---|---|---|
| HS256 (HMAC-SHA256) | 对称加密 | 共享密钥 | 高(密钥需保密) |
| RS256 (RSA-SHA256) | 非对称加密 | 公钥/私钥对 | 高 |
| ES256 (ECDSA-SHA256) | 非对称加密(椭圆曲线) | 公钥/私钥对 | 高(更短密钥) |
三、JWT 工作原理
在身份认证场景中,JWT 的工作流程如下:
- 用户登录:用户使用用户名和密码(或其他认证方式)进行登录
- 服务器验证:服务器验证用户凭据,验证成功后生成 JWT
- 返回令牌:服务器将 JWT 返回给客户端
- 存储令牌:客户端将 JWT 存储在本地(如 localStorage、sessionStorage 或 Cookie)
- 发送请求:客户端在后续请求的 Authorization Header 中携带 JWT
- 验证令牌:服务器验证 JWT 的签名和有效性,确认用户身份
- 返回数据:验证通过后,服务器返回受保护的资源
💡 提示:流程图示意:用户 → 登录请求 → 服务器验证 → 生成 JWT → 返回 JWT → 客户端存储 → 携带 JWT 请求 → 服务器验证 → 返回资源
前端生成 JWT 示例
// 使用 JavaScript 生成 JWT(HMAC-SHA256)
const base64UrlEncode = (obj) => {
return btoa(JSON.stringify(obj))
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
};
const generateJWT = async (payload, secret) => {
const header = { alg: 'HS256', typ: 'JWT' };
const encodedHeader = base64UrlEncode(header);
const encodedPayload = base64UrlEncode(payload);
const data = encodedHeader + '.' + encodedPayload;
const encoder = new TextEncoder();
const keyData = encoder.encode(secret);
const messageData = encoder.encode(data);
const cryptoKey = await crypto.subtle.importKey(
'raw',
keyData,
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const signature = await crypto.subtle.sign('HMAC', cryptoKey, messageData);
const signatureArray = Array.from(new Uint8Array(signature));
const encodedSignature = btoa(String.fromCharCode(...signatureArray))
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
return data + '.' + encodedSignature;
};
// 使用示例
const payload = {
sub: 'user123',
name: '张三',
role: 'admin',
exp: Math.floor(Date.now() / 1000) + 3600
};
const token = await generateJWT(payload, 'your-secret-key');
console.log('JWT:', token);
四、常见应用场景
JWT 凭借其独特的优势,在多种场景中得到广泛应用:
1. 身份认证
这是 JWT 最常见的应用场景。用户登录后,每个后续请求都将包含 JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是目前广泛使用 JWT 的一个功能,因为它的开销小,并且能够在不同域之间轻松使用。
2. 信息交换
JWT 是在各方之间安全传输信息的好方法。因为 JWT 可以被签名(例如使用公钥/私钥对),所以可以确保发送者是他们所称的人。此外,由于签名是使用 Header 和 Payload 计算的,还可以验证内容是否被篡改。
3. 单点登录(SSO)
由于 JWT 的跨域特性,它非常适合实现单点登录系统。用户在一个系统登录后,可以无缝访问其他关联系统,无需重新登录。
4. 微服务架构
在微服务架构中,每个服务都可以独立验证 JWT,无需共享会话状态,降低了系统的耦合度,提高了可扩展性。
5. 移动应用认证
JWT 适合移动应用的身份认证,因为它不依赖于 Cookie,可以轻松存储在移动设备的本地存储中。
五、安全性分析
虽然 JWT 提供了很多便利,但在使用过程中需要注意以下安全问题:
1. 签名算法安全
选择安全的签名算法至关重要。HMAC 算法需要保护好密钥,而 RSA/ECDSA 算法则需要妥善保管私钥。永远不要使用 'none' 算法,这会导致 JWT 可以被任意伪造。
2. 令牌存储安全
JWT 的存储方式直接影响安全性。存储在 localStorage 中容易受到 XSS 攻击,而存储在 Cookie 中如果设置不当则容易受到 CSRF 攻击。推荐使用 HttpOnly 和 Secure Cookie 来存储 JWT。
3. 令牌过期机制
设置合理的过期时间(exp)非常重要。较短的过期时间可以降低令牌被盗用的风险。同时,可以使用刷新令牌(Refresh Token)机制来延长用户会话时间,同时保持安全性。
4. 敏感信息保护
JWT 的 Payload 只是经过 Base64 编码,并不是加密的。因此,绝对不要在 Payload 中存储敏感信息,如密码、信用卡号等。
六、常见攻击与防护
了解 JWT 面临的常见攻击及其防护措施,对于确保系统安全至关重要:
1. XSS 攻击
跨站脚本攻击(XSS)可以窃取存储在 localStorage 中的 JWT。防护措施包括:对所有用户输入进行转义、使用内容安全策略(CSP)、使用 HttpOnly Cookie 存储 JWT。
2. CSRF 攻击
如果 JWT 存储在 Cookie 中,则可能受到跨站请求伪造(CSRF)攻击。防护措施包括:使用 SameSite Cookie 属性、验证 Referer/Origin Header、使用 CSRF Token。
3. 算法混淆攻击
攻击者可能将签名算法从 RS256 改为 HS256,然后使用公钥作为密钥来伪造签名。防护措施是:在验证时明确指定预期的算法,不依赖 JWT Header 中的 alg 字段。
4. 重放攻击
攻击者截获 JWT 后,可以重放该令牌来访问受保护资源。防护措施包括:设置较短的过期时间、使用一次性令牌、记录已使用的令牌(jti)。
5. 令牌窃取
如果 JWT 在传输过程中被窃取,攻击者可以冒充用户。防护措施包括:始终使用 HTTPS 传输、设置合理的过期时间、实现令牌撤销机制。
七、最佳实践
以下是使用 JWT 的一些最佳实践:
1. 选择安全的算法
使用 HS256 或更高强度的算法。对于需要非对称加密的场景,使用 RS256 或 ES256。永远不要使用 'none' 算法。
2. 设置合理的过期时间
访问令牌(Access Token)的过期时间应该较短(如 15-30 分钟),刷新令牌(Refresh Token)的过期时间可以较长(如 7 天)。
3. 安全存储令牌
优先使用 HttpOnly + Secure + SameSite Cookie 存储 JWT。如果必须存储在 localStorage 中,确保网站没有 XSS 漏洞。
4. Payload 最小化
只在 Payload 中存储必要的信息,不要存储敏感数据。JWT 越大,每次请求的开销也越大。
5. 严格验证令牌
验证签名、过期时间(exp)、生效时间(nbf)、签发者(iss)、受众(aud)等所有必要的声明。
6. 使用刷新令牌机制
使用短期访问令牌 + 长期刷新令牌的组合,既保证了安全性,又提供了良好的用户体验。
7. 实现令牌撤销机制
虽然 JWT 是无状态的,但在某些场景下(如用户登出、密码修改)需要能够立即撤销令牌。可以使用令牌黑名单或缩短过期时间来实现。
Node.js 验证 JWT 示例
// 在 Node.js 中验证 JWT
const jwt = require('jsonwebtoken');
// 验证中间件
const authMiddleware = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: '未提供认证令牌' });
}
const token = authHeader.substring(7);
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256'],
issuer: 'your-app',
audience: 'your-app-audience'
});
req.user = decoded;
next();
} catch (error) {
if (error.name === 'TokenExpiredError') {
return res.status(401).json({ error: '令牌已过期' });
}
return res.status(401).json({ error: '无效的令牌' });
}
};
// 生成访问令牌和刷新令牌
const generateTokens = (user) => {
const accessToken = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_ACCESS_SECRET,
{ expiresIn: '15m', issuer: 'your-app' }
);
const refreshToken = jwt.sign(
{ userId: user.id },
process.env.JWT_REFRESH_SECRET,
{ expiresIn: '7d', issuer: 'your-app' }
);
return { accessToken, refreshToken };
};
刷新令牌示例
// 刷新令牌示例
const refreshTokenHandler = async (req, res) => {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(401).json({ error: '未提供刷新令牌' });
}
try {
const decoded = jwt.verify(
refreshToken,
process.env.JWT_REFRESH_SECRET,
{ algorithms: ['HS256'] }
);
const user = await User.findById(decoded.userId);
if (!user) {
return res.status(401).json({ error: '用户不存在' });
}
const newAccessToken = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_ACCESS_SECRET,
{ expiresIn: '15m', issuer: 'your-app' }
);
const newRefreshToken = jwt.sign(
{ userId: user.id },
process.env.JWT_REFRESH_SECRET,
{ expiresIn: '7d', issuer: 'your-app' }
);
res.json({
accessToken: newAccessToken,
refreshToken: newRefreshToken
});
} catch (error) {
res.status(401).json({ error: '无效的刷新令牌' });
}
};
八、JWT 与 Session 对比
JWT 和 Session 是两种常见的身份认证方案,各有优缺点:
| 特性 | JWT | Session |
|---|---|---|
| 状态 | 无状态 | 有状态 |
| 存储位置 | 客户端(localStorage/Cookie) | 服务器端 |
| 可扩展性 | 优秀,无需共享会话 | 一般,需要共享 Session |
| 跨域支持 | 天然支持 | 需要额外配置 |
| 性能 | 每次需要验证签名 | 需要查询 Session 存储 |
| 移动应用 | 友好,不依赖 Cookie | 不友好,依赖 Cookie |
| 立即撤销 | 困难(需黑名单) | 容易,直接删除 Session |
| 安全性 | 需注意 XSS/CSRF 攻击 | 需注意 CSRF 攻击 |
九、JWT 类型
根据用途和安全级别,JWT 可以分为以下几种类型:
1. JWS(JSON Web Signature)
这是最常见的 JWT 类型,使用签名来确保消息的完整性和发送者的身份验证。JWS 是大多数身份认证场景的首选。
2. JWE(JSON Web Encryption)
JWE 不仅提供签名,还提供加密功能,可以保护 Payload 的机密性。适用于需要在 JWT 中传输敏感信息的场景。JWE 的结构比 JWS 更复杂,体积也更大。
JWS vs JWE 对比
| 特性 | JWS | JWE |
|---|---|---|
| 主要用途 | 身份认证、完整性验证 | 敏感信息加密传输 |
| 加密 | 否(仅 Base64 编码) | 是(Content Encryption) |
| 体积 | 较小 | 较大 |
| 复杂度 | 简单 | 复杂 |
| 适用场景 | 大多数身份认证场景 | 需保护 Payload 机密性的场景 |
十、土豆丝 JWT 工具
为了帮助开发者更方便地使用和调试 JWT,土豆丝工具提供了专业的 JWT 在线工具,支持以下功能:
- JWT 编码与解码
- 多种签名算法支持(HS256、HS384、HS512、RS256、RS384、RS512、ES256、ES384、ES512)
- 实时验证签名有效性
- 可视化展示 Header 和 Payload 内容
- 支持自定义密钥和公钥/私钥
十一、总结
JWT 作为一种现代的身份认证和信息交换方案,凭借其紧凑性、自包含性和跨域友好等特点,在 Web 应用、移动应用和微服务架构中得到了广泛应用。理解 JWT 的结构原理、工作机制和安全特性,是正确使用 JWT 的基础。
在实际应用中,开发者需要根据具体场景选择合适的签名算法、存储方式和过期策略,严格遵循安全最佳实践,以确保系统的安全性。同时,配合使用专业的 JWT 工具,可以大大提高开发效率和调试便利性。