探究JWT(JSON Web Token):如何确保安全性?
JSON Web Token(JWT)是一种用于在网络应用间安全传递声明的开放标准(RFC 7519)。它可以被用作授权令牌,用来验证客户端和服务器之间的信息,并确保用户在不同服务之间的身份认证,可实现无状态、分布式的Web应用授权。
JWT的结构
JWT由三部分组成:头部(Header)、载荷(Payload)和签名(Signature):
-
头部(Header): 包含了两部分信息:声明类型(typ)和使用的加密算法(alg)。
{ "typ": "JWT", "alg": "HS256" }
-
载荷(Payload): 包含了要传输的声明(claim),可以是一些标准的声明,也可以是自定义的声明。
{ "sub": "1234567890", "name": "John Doe", "admin": true }
-
签名(Signature): 是用来验证消息是否为可信来源的一段字符串。签名的生成需要使用头部中声明的加密算法以及密钥。签名不包含在JWT的头部和载荷中,通常用于验证JWT的完整性。
JWT以 Header.Payload.Signature
格式拼接, Header和Payload以Base64编码,最后一部分为 Signature ,由于采用Base64编码,特点是开头通常是eyJ.eyJ
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.Yl36HIPEB36NHwT3dUxmlg58ljmsprN0hNRA21V6Cz0
上文的加密密钥为 jwt
JWT的生成和验证
生成JWT:
- 将头部和载荷分别进行Base64编码,然后用
.
连接成一个字符串。 - 使用指定的加密算法和密钥对连接后的字符串进行签名,生成签名部分。
- 将编码后的头部、载荷和签名用
.
连接成最终的JWT。
验证JWT:
- 接收到JWT后,首先将其拆分为头部、载荷和签名三部分。
- 使用相同的加密算法和密钥对头部和载荷进行签名,生成一个新的签名。
- 将新生成的签名与JWT中的签名进行比较,如果一致,则说明JWT未被篡改。
以下为 JWT的加密算法示例
var header = {
alg: "HS256",
typ: "JWT",
};
// 使用base64编码header
var encoded_header = Buffer.from(JSON.stringify(header)).toString("base64");
var payload = {
sub: "1234567890",
name: "John Doe",
admin: true,
};
// 使用base64编码pyload
var encoded_payload = Buffer.from(JSON.stringify(payload)).toString("base64");
const crypto = require("crypto");
var secret = "$T697UaW6QTWsw}rrt%*P6)ia";
// 生成签名signature,并截断填充字符
var signature = crypto.createHmac("sha256", secret).update(
encoded_header + "." + encoded_payload,
).digest("base64").replace(/=+$/, "");
console.log(encoded_header + "." + encoded_payload + "." + signature);
验证签名算法
const jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.7gpTheSlCpPRzjB5C9qpPvQnFtPVwqHfIkcoSRAqbSY";
// 分解JWT字符串
const parts = jwt.split('.');
const encodedHeader = parts[0];
const encodedPayload = parts[1];
const signature = parts[2];
// 解码头部和载荷
const header = JSON.parse(Buffer.from(encodedHeader, 'base64').toString());
const payload = JSON.parse(Buffer.from(encodedPayload, 'base64').toString());
// 验证签名
const crypto = require("crypto");
const secret = "$T697UaW6QTWsw}rrt%*P6)ia";
const expectedSignature = crypto.createHmac("sha256", secret)
.update(encodedHeader + '.' + encodedPayload)
.digest('base64')
.replace(/=+$/, '');
if (signature === expectedSignature) {
console.log("JWT Signature is valid.");
console.log("Header:", header);
console.log("Payload:", payload);
} else {
console.log("JWT Signature is invalid!");
}
应用
由于JWT实现简单,且携带内容灵活,消息不可纂改,且不必考虑Cookie夹带等风险,现代分布式、跨平台的应用场景,JWT被广泛应用于 Web 开发中的身份验证和授权领域。值得注意的是,JWT其实是明文传输,但具有防纂改的特点。所以JWT不能用于传输机密信息,相反它只是一种Cookie的替代方案。
安全性
JWT的特点
-
轻量级和简单性:JWT 是一个轻量级的认证和授权机制,它使用 JSON 对象作为令牌传输的格式,使其易于理解和实现。它不需要在服务器端保存会话状态,因为令牌本身包含了所有必要的信息。
-
跨平台兼容性:JWT 是基于 JSON 格式的标准化令牌,在不同的平台和编程语言之间很容易传递和解析。这使得它成为了一个理想的身份验证解决方案,可以在各种应用和服务之间进行通信。
-
安全性:JWT 使用数字签名或加密机制来验证发送方的身份以及确保令牌的完整性。这样可以防止令牌被篡改或伪造。另外,JWT 也可以通过设置有效期限制令牌的生命周期,提高安全性。
-
无状态性:JWT 是无状态的,即服务器不需要在后端存储会话信息。所有的信息都包含在令牌本身中,这样可以减轻服务器的负担,并且使得系统更容易扩展。
-
分布式架构友好:由于 JWT 的无状态性和轻量级特性,它非常适合在分布式系统中使用。每个微服务或组件都可以验证 JWT 而不需要在本地存储任何状态信息。
-
可扩展性:JWT 令牌可以包含自定义的声明(claims),这些声明可以用于携带任何有关用户或其他信息的相关数据。这使得 JWT 非常灵活,并且可以根据实际需求进行定制化。
-
自包含性:JWT是自包含的,Token本身携带了验证信息,不需要借助其他工具就可以知道一个Token是否有效,以及载荷信息。
JWT用途
- 简化身份验证流程。
- 跨域通信和微服务架构中的消息传递。
- JWT规定以JSON的格式传递信息,负载payload的数据格式是JSON的,通常使用base64编码。
- 去中心化架构身份验证,借助RSA算法,每个节点都可以独立验证JWT令牌,从而实现去中心化身份验证和授权。
- JWT的某些实现比如黑名单机制、Token刷新等增强功能,可能也需要借助其他工具,但是这并不违背自包含特性。
JWT的安全性考虑
- 选择安全的签名算法如,HMAC、RSA、ECDSA
- 进行密钥管理与保护,避免密钥泄露导致JWT可被纂改
- 分布式身份认证系统采取RSA,ECDSA等公私钥算法,确保当一个节点被攻破时私钥仍不被泄露
- 定期刷新和轮换JWT,设置适当的JWT的过期时间,避免重放攻击
- 使用HTTPS协议传输JWT,不要在JWT中传递敏感信息
- 实施适当的用户认证和授权机制
- 仔细设计权限和访问控制,并做好监控和审计,减少 JWT 被滥用的风险
常见的JWT安全漏洞和攻击方式
- XSS(跨站脚本攻击)
- CSRF(跨站请求伪造)
- Token泄露
- Token过期管理不当
- 暴力破解和字典攻击
预防JWT安全问题的技术和工具
- 合理配置JWT库
- 实施 JWT 黑名单,定期清理失效令牌
- 结合OAuth、OpenID Connect、SSO等
Comments
Leave a comment