JWT令牌安全实践详解
一、JWT概述
JSON Web Token(JWT)是一种用于安全传输信息的开放标准(RFC 7519)。
1.1 JWT结构
┌─────────────────────────────────────────────────────────────┐ │ JWT Token │ ├─────────────────────────────────────────────────────────────┤ │ Header.Payload.Signature │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ {"alg":"HS256", {"sub":"123", HMACSHA256( │ │ "typ":"JWT"} "name":"John", base64Url(header). │ │ "exp":1704067200) + "." + │ │ base64Url(payload),│ │ secret) │ └─────────────────────────────────────────────────────────────┘
1.2 JWT组成部分
| 部分 | 说明 | 编码方式 |
|---|
| Header | 算法和类型 | Base64Url |
| Payload | 声明信息 | Base64Url |
| Signature | 签名 | HMAC/RSA |
二、JWT实现
2.1 创建JWT
import io.jsonwebtoken.*; import java.util.Date; public class JwtUtil { private static final String SECRET_KEY = "your-256-bit-secret-key"; private static final long EXPIRATION_TIME = 86400000; // 24小时 public static String generateToken(String userId, String username) { return Jwts.builder() .setSubject(userId) .claim("username", username) .claim("role", "admin") .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } }
2.2 验证JWT
public static Claims validateToken(String token) { try { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody(); } catch (JwtException | IllegalArgumentException e) { throw new RuntimeException("Invalid token", e); } }
2.3 刷新Token
public static String refreshToken(String token) { Claims claims = validateToken(token); claims.setIssuedAt(new Date()); claims.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)); return Jwts.builder() .setClaims(claims) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); }
三、JWT安全配置
3.1 密钥管理
// 使用256位以上的密钥 private static final String SECRET_KEY = generateSecureKey(); private static String generateSecureKey() { SecureRandom random = new SecureRandom(); byte[] key = new byte[32]; // 256 bits random.nextBytes(key); return Base64.getEncoder().encodeToString(key); }
3.2 使用RSA非对称加密
// 生成RSA密钥对 KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); keyGen.initialize(2048); KeyPair keyPair = keyGen.generateKeyPair(); // 使用私钥签名 String token = Jwts.builder() .setSubject("user123") .signWith(keyPair.getPrivate(), SignatureAlgorithm.RS256) .compact(); // 使用公钥验证 Claims claims = Jwts.parser() .setSigningKey(keyPair.getPublic()) .parseClaimsJws(token) .getBody();
3.3 设置合理的过期时间
// 访问Token:短过期时间 private static final long ACCESS_TOKEN_EXPIRE = 15 * 60 * 1000; // 15分钟 // 刷新Token:长过期时间 private static final long REFRESH_TOKEN_EXPIRE = 7 * 24 * 60 * 60 * 1000; // 7天
四、安全最佳实践
4.1 Token存储策略
| 存储位置 | 优点 | 缺点 | 适用场景 |
|---|
| LocalStorage | 方便访问 | XSS风险 | 单页应用 |
| SessionStorage | 会话级存储 | 页面切换丢失 | 临时数据 |
| HttpOnly Cookie | XSS安全 | CSRF风险 | 传统Web |
| Memory | 最安全 | 页面刷新丢失 | 高安全场景 |
4.2 CSRF防护
// Spring Security配置 http.csrf(csrf -> csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .ignoringRequestMatchers("/api/**") );
4.3 XSS防护
<!-- 设置HttpOnly和Secure标志 --> Set-Cookie: JWT=token; HttpOnly; Secure; SameSite=Strict
五、Token黑名单机制
5.1 基于Redis的黑名单
public class TokenBlacklist { private final StringRedisTemplate redisTemplate; private static final String PREFIX = "blacklist:"; public void invalidateToken(String token, long expireSeconds) { String key = PREFIX + token; redisTemplate.opsForValue().set(key, "true", expireSeconds, TimeUnit.SECONDS); } public boolean isBlacklisted(String token) { String key = PREFIX + token; return Boolean.TRUE.equals(redisTemplate.hasKey(key)); } }
5.2 拦截器验证
public class JwtInterceptor implements HandlerInterceptor { private final TokenBlacklist blacklist; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token = extractToken(request); if (blacklist.isBlacklisted(token)) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return false; } // 继续验证token return true; } }
六、JWT vs Session对比
| 特性 | JWT | Session |
|---|
| 状态 | 无状态 | 有状态 |
| 存储 | 客户端 | 服务端 |
| 扩展性 | 好 | 差 |
| 安全性 | 需额外处理 | 相对安全 |
| 性能 | 网络传输大 | 服务器内存 |
七、安全检查清单
7.1 必做检查
| 检查项 | 说明 |
|---|
| 使用HTTPS | 防止Token被窃取 |
| 设置过期时间 | 限制Token有效期 |
| 避免敏感信息 | Payload是Base64编码,不是加密 |
| 使用强密钥 | 256位以上密钥 |
| 验证签名 | 防止Token被篡改 |
7.2 推荐检查
| 检查项 | 说明 |
|---|
| 实现刷新机制 | 定期轮换Token |
| 实现黑名单 | 支持主动注销 |
| 限制Token大小 | 避免过大Payload |
| 监控异常行为 | 检测暴力破解 |
八、总结
JWT是一种强大的身份认证机制,但需要正确使用才能保证安全:
- 使用HTTPS:始终通过HTTPS传输Token
- 合理设置过期时间:访问Token短,刷新Token长
- 使用非对称加密:避免密钥泄露风险
- 实现黑名单机制:支持主动注销
- 存储在安全位置:根据场景选择存储方式
通过以上措施,可以构建安全可靠的JWT认证系统。