0赞
赞赏
更多好文
一、引言:为什么需要JWT?
在现代Web应用中,状态管理是核心挑战。传统Session-Cookie方案存在以下痛点:
- 服务器存储压力:Session数据需存储在内存/数据库
- 横向扩展困难:分布式系统需共享Session存储
- 跨域限制:移动端/前端无法直接使用Cookie
JWT(JSON Web Token) 作为RFC 7519标准的身份验证令牌,通过无状态、自包含的设计彻底解决了这些问题。它已成为API认证、单点登录(SSO)、微服务通信的行业标准。本文将深入剖析JWT的原理、安全机制及最佳实践。
关键提示:JWT不是加密令牌,而是签名令牌。敏感数据不应直接存储在Payload中。
二、JWT的三大核心组成
JWT由三部分组成,通过.连接(Base64Url编码):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
1. Header(头部)
描述令牌类型和签名算法:
{
"alg": "HS256", // 签名算法(常用:HS256/RS256)
"typ": "JWT" // 类型(固定值)
}
作用:告知服务器如何验证签名。
2. Payload(载荷)
包含声明(Claims),分为三类:
| 类型 | 说明 | 示例 |
|---|---|---|
| 注册声明 | 标准预定义字段 | iss(发行人), exp(过期时间), sub(主题) |
| 公开声明 | 可自定义字段 | user_id, role |
| 私有声明 | 服务端自定义字段 | company_id |
典型Payload:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"exp": 1735689600 // 2025-01-01T00:00:00Z
}
重要:不要存储密码、敏感信息!Payload是Base64编码(非加密),可被解码。
3. Signature(签名)
生成方式:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret_key )
作用:验证令牌完整性,防止篡改。
三、JWT工作流程:从登录到请求验证
sequenceDiagram
User->>Auth Server: 登录请求(用户名/密码)
Auth Server->>Auth Server: 验证凭证
Auth Server->>Auth Server: 生成JWT
Auth Server-->>User: 返回JWT
User->>API Server: 请求(携带JWT)
API Server->>API Server: 验证签名+解析Payload
API Server-->>User: 返回资源
详细步骤:
- 用户登录
前端发送用户名/密码到认证服务器(如/login) - 生成JWT
服务器生成包含用户信息的JWT,使用密钥签名 - 存储令牌
前端将JWT存储在localStorage或HttpOnly Cookie(推荐) - 请求携带
后续API请求在Authorization头中携带:Authorization: Bearer <JWT> - 服务端验证
服务器:- 验证签名(防止篡改)
- 检查过期时间(
exp) - 读取用户信息(
sub/role)
四、核心安全机制深度解析
1. 签名算法对比(关键!)
| 算法 | 说明 | 安全性 | 适用场景 |
|---|---|---|---|
| HS256 | 对称加密(密钥共享) | 中 | 内部服务(服务器间通信) |
| RS256 | 非对称加密(公钥签名/私钥验证) | 高 | 公开API(客户端与服务器) |
| ES256 | 椭圆曲线(更高效) | 高 | 移动端/物联网 |
为什么RS256更安全?
HS256需共享密钥,一旦泄露整个系统崩溃;RS256使用私钥签名,公钥验证,密钥泄露风险低。
2. 防御常见攻击的实践
| 攻击类型 | 防御措施 | 实现示例 |
|---|---|---|
| 令牌窃取 | 使用HttpOnly Cookie + Secure | Set-Cookie: token=...; HttpOnly; Secure; SameSite=Strict |
| 暴力破解 | 设置短过期时间(15-30分钟) | exp: System.currentTimeMillis() + 15*60*1000 |
| 签名伪造 | 严格验证算法(防止算法混淆) | 服务器端检查alg参数是否匹配 |
| 令牌重放 | 添加jti(唯一ID) + 服务端记录 | 生成唯一ID:jti: UUID.randomUUID().toString() |
安全警示:
永远不要使用alg: none!这是JWT标准中的安全漏洞,攻击者可伪造令牌。
五、代码实战:Java实现JWT生成与验证
1. 添加Maven依赖(使用jjwt库)
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.6</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.6</version>
<scope>runtime</scope>
</dependency>
2. 生成JWT(RS256示例)
import io.jsonwebtoken.*;
import java.security.*;
import java.util.*;
public class JwtGenerator {
private static final String PRIVATE_KEY = "MIIBUwIB..."; // 私钥(实际使用时从安全存储获取)
public static String generateToken(String userId) {
// 1. 创建密钥对(实际使用时从Keystore加载)
KeyPair keyPair = generateKeyPair();
// 2. 构建JWT
return Jwts.builder()
.setSubject(userId)
.claim("role", "admin")
.setExpiration(new Date(System.currentTimeMillis() + 15 * 60 * 1000)) // 15分钟过期
.signWith(keyPair.getPrivate(), SignatureAlgorithm.RS256)
.compact();
}
private static KeyPair generateKeyPair() {
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
return keyGen.generateKeyPair();
} catch (Exception e) {
throw new RuntimeException("Key generation failed", e);
}
}
}
3. 验证JWT(RS256示例)
public class JwtValidator {
private static final String PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."; // 公钥
public static Claims validateToken(String token) {
try {
// 1. 解析公钥
PublicKey publicKey = getPublicKeyFromPem(PUBLIC_KEY);
// 2. 验证并解析
return Jwts.parserBuilder()
.setSigningKey(publicKey)
.build()
.parseClaimsJws(token)
.getBody();
} catch (JwtException e) {
throw new SecurityException("Invalid JWT token", e);
}
}
private static PublicKey getPublicKeyFromPem(String pem) {
// 实际实现:从PEM字符串加载公钥
// 此处简化,实际项目用KeyFactory
return null;
}
}
六、JWT vs Session:关键对比
| 特性 | JWT | Session |
|---|---|---|
| 状态 | 无状态(服务端不存储) | 有状态(服务端存储Session) |
| 扩展性 | 高(适合微服务/分布式) | 低(需共享Session存储) |
| 跨域支持 | 原生支持(HTTP Header) | 需Cookie + CORS |
| 性能 | 低(每次解码验证) | 高(直接查内存) |
| 安全性 | 需严格配置(过期/签名) | 依赖Session存储安全 |
| 典型场景 | API网关、移动端、SSO | 传统Web应用 |
结论:
JWT适合无状态API(如RESTful服务),Session适合传统Web应用(如Spring MVC)。
七、常见误区与避坑指南
❌ 误区1:在Payload存储敏感数据
// 错误示例:存储密码
{"password": "123456", "role": "admin"}
正确做法:
仅存储用户ID、角色等非敏感信息,密码必须在服务端验证。
❌ 误区2:使用HS256且密钥硬编码
// 错误:密钥硬编码在代码中
String secret = "my_secret_key";
正确做法:
从环境变量/密钥管理服务(如AWS KMS)获取密钥。
❌ 误区3:忽略过期时间
// 错误:永不过期
.setExpiration(new Date(Long.MAX_VALUE))
正确做法:
设置合理过期时间(通常15-60分钟)。
✅ 最佳实践总结
| 实践 | 说明 |
|---|---|
| 使用RS256 | 避免密钥共享风险 |
| 短过期时间 | 15-30分钟(配合Refresh Token) |
| HTTPS强制 | 防止令牌窃取 |
| HttpOnly Cookie | 防止XSS攻击 |
| 令牌撤销机制 | 服务端维护黑名单(如Redis) |
八、总结:JWT的适用场景与价值
JWT是现代API架构的基石,其核心价值在于:
- 无状态设计 → 无缝支持微服务/分布式系统
- 自包含信息 → 减少数据库查询
- 标准化协议 → 跨语言、跨平台兼容
何时不使用JWT?
- 需要实时会话控制(如用户强制登出) → 用Session+服务端存储
- 移动端且需高安全级别 → 结合
Refresh Token机制
延伸学习:
记住:JWT不是万能钥匙,但安全使用时,它是构建现代API的黄金标准。在2026年,90%的API认证已采用JWT或其变体。
