JWT结构组成
JWT结构组成
概述
JWT(JSON Web Token)由三部分组成:头部(Header)、负载(Payload)和签名(Signature)。这三部分通过Base64编码后用.
连接形成完整的JWT。本章节将详细解析JWT的结构组成,帮助你理解每个部分的作用和格式。
知识要点
1. 头部(Header)
Header是JWT的第一部分,用于描述JWT的基本信息,主要包括所使用的加密算法和Token类型。
1.1 格式与内容
Header通常是一个JSON对象,包含以下字段:
alg
:表示JWT使用的签名算法,如HS256(HMAC-SHA256)、RS256(RSA-SHA256)等typ
:表示Token类型,固定为"JWT"
示例:
{
"alg": "HS256",
"typ": "JWT"
}
1.2 Base64编码
Header部分会经过Base64编码(实际上是Base64Url编码, slightly modified for URL safety),形成JWT的第一部分。
// Java中对Header进行Base64Url编码的示例
String headerJson = "{\"alg\": \"HS256\", \"typ\": \"JWT\"}";
String encodedHeader = Base64.getUrlEncoder().withoutPadding().encodeToString(headerJson.getBytes());
// encodedHeader结果: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
2. 负载(Payload)
Payload是JWT的第二部分,用于存储实际需要传输的数据。它包含了一些标准声明和自定义声明。
2.1 标准声明
JWT定义了一些标准声明(Claims),这些声明是推荐使用的,但不是必须的:
iss
:Issuer,签发者sub
:Subject,主题(通常是用户ID)aud
:Audience,受众exp
:Expiration Time,过期时间(Unix时间戳)nbf
:Not Before,生效时间(Unix时间戳)iat
:Issued At,签发时间(Unix时间戳)jti
:JWT ID,唯一标识符
2.2 自定义声明
除了标准声明外,还可以添加自定义声明来存储额外的信息。例如:
username
:用户名role
:用户角色permissions
:用户权限
示例:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 1516242622,
"role": "admin"
}
2.3 安全注意事项
需要注意的是,Payload部分只经过Base64编码,相当于明文存储,因此不要在Payload中存储敏感信息,如密码、信用卡号等。
3. 签名(Signature)
Signature是JWT的第三部分,用于验证JWT的真实性和完整性。通过对编码后的Header和Payload进行签名,可以确保数据在传输过程中没有被篡改。
3.1 生成过程
签名的生成过程如下:
- 将编码后的Header和Payload用点(.)连接起来,形成字符串
encodedHeader.encodedPayload
- 使用Header中指定的算法和密钥对该字符串进行签名
- 对签名结果进行Base64 URL编码
- 将编码后的签名添加到JWT的第三部分
// 生成JWT签名示例
String encodedHeader = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
String encodedPayload = "eyJzdWIiOiIxMjM0NTY3ODkwIn0";
String secretKey = "your-secret-key";
// 构建待签名字符串
String data = encodedHeader + "." + encodedPayload;
// 使用HMAC-SHA256算法生成签名
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secretKey.getBytes(), "HmacSHA256"));
byte[] signatureBytes = mac.doFinal(data.getBytes());
// Base64 URL编码签名
String encodedSignature = Base64.getUrlEncoder()
.withoutPadding()
.encodeToString(signatureBytes);
// 完整JWT
String jwt = encodedHeader + "." + encodedPayload + "." + encodedSignature;
3.2 验证过程
当接收方收到JWT时,会进行以下验证步骤:
- 提取Header和Payload
- 使用相同的算法和密钥重新生成签名
- 比较重新生成的签名与JWT中的签名是否一致
- 验证Payload中的声明(如过期时间、发行者等)
4. 完整JWT示例
一个完整的JWT如下所示:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w4I3ZVX5nz5-HXMrjGOppEv8
这个JWT由三部分组成:
- Header:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
- Payload:
eyJzdWIiOiIxMjM0NTY3ODkwIn0
- Signature:
dozjgNryP4J3jVmNHl0w4I3ZVX5nz5-HXMrjGOppEv8
5. Base64编码与解码
JWT的Header和Payload部分使用的是Base64 URL编码,这是标准Base64编码的变体,主要区别在于:
- 使用
-
代替+
- 使用
_
代替/
- 去除末尾的填充字符
=
以下是Java中Base64 URL编码和解码的示例:
// Base64 URL编码
String encode(String input) {
return Base64.getUrlEncoder()
.withoutPadding()
.encodeToString(input.getBytes());
}
// Base64 URL解码
String decode(String encoded) {
return new String(Base64.getUrlDecoder().decode(encoded));
}
知识扩展
设计思想
JWT的结构设计体现了以下思想:
- 模块化:将不同功能的信息分离到不同部分
- 自包含:所有必要信息都包含在Token中
- 高效:使用Base64编码确保数据紧凑
- 安全:通过签名机制确保数据完整性
- 可扩展:支持自定义声明,满足不同业务需求
避坑指南
- 不要存储敏感信息:Payload部分是Base64编码的,相当于明文,不应存储敏感信息
- 选择合适的签名算法:根据业务需求选择合适的签名算法,分布式系统推荐使用非对称算法
- 注意Base64编码的特殊性:JWT使用的是Base64 URL编码,不是标准的Base64编码
- 验证全部声明:除了验证签名外,还应验证Payload中的所有声明(如过期时间、发行者等)
- 处理编码错误:在解码JWT时,应处理可能出现的编码错误
深度思考题
深度思考题: 为什么JWT的Header和Payload只进行Base64编码而不加密?
思考题回答:
JWT的Header和Payload只进行Base64编码而不加密,主要有以下原因:
设计目的:JWT的主要目的是认证和信息交换,而不是数据加密。JWT的安全性主要依赖于签名,而不是加密。
性能考虑:加密和解密会增加性能开销,而Base64编码是一种简单高效的编码方式。
透明性:Header和Payload中的信息通常需要被客户端或中间服务读取和使用,加密会增加信息获取的复杂性。
分离关注点:JWT将数据完整性(通过签名保证)和数据保密性(通过其他机制保证)分开处理。如果需要传输敏感信息,可以使用JWE(JSON Web Encryption)对JWT进行加密。
标准设计:JWT规范(RFC 7519)明确规定了Header和Payload的编码方式为Base64 URL编码,而不是加密。
需要强调的是,虽然Header和Payload不加密,但JWT的签名机制确保了这些信息不能被篡改。如果需要传输敏感信息,应该使用JWE或其他加密机制对整个JWT或其部分内容进行加密。 Signature的生成过程如下:
- 将编码后的Header和编码后的Payload用
.
连接起来,形成字符串encodedHeader.encodedPayload
- 使用Header中指定的签名算法,结合一个密钥(Secret),对上述字符串进行签名
- 对签名结果进行Base64Url编码,形成最终的Signature
3.2 验证机制
当接收到JWT时,验证方会:
- 提取Header和Payload
- 使用相同的算法和密钥重新生成签名
- 比较重新生成的签名与JWT中的Signature是否一致
- 若一致,则JWT有效;若不一致,则JWT可能被篡改
3.3 代码示例
// Java中生成JWT签名的示例
String encodedHeader = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
String encodedPayload = "eyJzdWIiOiIxMjM0NTY3ODkwIn0";
String secret = "your-secret-key";
// 组合Header和Payload
String headerPlusPayload = encodedHeader + "." + encodedPayload;
// 生成签名
Mac sha256Hmac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
sha256Hmac.init(secretKey);
byte[] signatureBytes = sha256Hmac.doFinal(headerPlusPayload.getBytes());
// 对签名进行Base64Url编码
String encodedSignature = Base64.getUrlEncoder().withoutPadding().encodeToString(signatureBytes);
// 完整的JWT
String jwt = encodedHeader + "." + encodedPayload + "." + encodedSignature;
知识扩展
设计思想
JWT的结构设计体现了以下思想:
- 模块化:将不同功能的信息分离到不同部分
- 简洁性:使用Base64编码减少数据大小,便于传输
- 安全性:通过数字签名确保数据的完整性和真实性
- 可扩展性:支持自定义声明,满足不同场景需求
避坑指南
- 选择合适的签名算法:HS256对称算法简单但密钥需要安全共享;RS256非对称算法更安全但性能较低
- 不要在Payload中存储敏感信息:Base64编码不是加密,可轻易解码
- 设置合理的过期时间:避免Token长期有效
- 保护签名密钥:特别是对称算法中,密钥泄露意味着可以伪造JWT
- 使用Base64Url编码:而不是标准Base64,以确保在URL中传输安全
深度思考题
深度思考题: 对称加密算法(如HS256)和非对称加密算法(如RS256)在JWT签名中有什么区别?各自适用什么场景?
思考题回答:
对称加密算法(如HS256)和非对称加密算法(如RS256)在JWT签名中的主要区别:
密钥管理:
- HS256使用同一个密钥进行签名和验证
- RS256使用私钥签名,公钥验证
安全性:
- HS256中,密钥需要在所有验证方之间共享,增加泄露风险
- RS256中,只需共享公钥,私钥仅由签发方保管,更安全
性能:
- HS256计算速度快
- RS256计算速度相对较慢
适用场景:
- HS256适用于单服务或信任度高的服务间通信
- RS256适用于分布式系统、第三方集成等场景,特别是当验证方不应具有签名能力时
选择建议:对于分布式系统或需要与第三方集成的场景,优先选择RS256;对于性能要求高且信任度高的内部系统,可以选择HS256。