Files
Uni_Login_system/JWT_QUICK_REFERENCE.md
T
2026-05-31 04:36:59 +08:00

5.9 KiB

JWT企业级加密 - 快速参考卡

🔑 密钥管理

生成新密钥对

.\generate-jwt-keys.ps1

配置密钥

jwt.public-key=YOUR_PUBLIC_KEY_BASE64
jwt.private-key=YOUR_PRIVATE_KEY_BASE64
jwt.expiration=86400000

💻 代码速查

生成Token

// 基础用法
String token = JwtUtil.generateToken(userId, username);

// 带额外声明
Map<String, Object> claims = new HashMap<>();
claims.put("role", "admin");
String token = JwtUtil.generateToken(userId, username, claims);

验证Token

// 基础验证
boolean isValid = JwtUtil.validateToken(token);

// 完整验证(含黑名单)
boolean isValid = JwtUtil.validateToken(token, blacklistService);

提取信息

String userId = JwtUtil.getUserIdFromToken(token);
String username = JwtUtil.getUsernameFromToken(token);
String jti = JwtUtil.getJtiFromToken(token);
long remainingTime = JwtUtil.getTokenRemainingTime(token); // 秒

Token注销

String jti = JwtUtil.getJtiFromToken(token);
long remainingTime = JwtUtil.getTokenRemainingTime(token);
blacklistService.addToBlacklist(jti, remainingTime);

刷新Token

String newToken = JwtUtil.refreshToken(oldToken);

🛡️ Claims字段

字段 说明 示例
jti JWT唯一ID "5f3e2a8b-1b4d-4e..."
iss 签发者 "uni-login-system"
aud 受众 "uni-login-client"
sub 主题(用户名) "admin"
iat 签发时间 1716624000
nbf 生效时间 1716623995
exp 过期时间 1716710400
userId 用户ID(自定义) "123"

⚠️ 异常类型

异常类 HTTP码 触发条件
JwtTokenExpiredException 401 Token已过期
JwtSignatureInvalidException 401 签名验证失败
JwtMalformedException 400 Token格式错误
JwtTokenBlacklistedException 401 Token已被注销

🔧 密钥轮换

// 1. 生成新密钥对
KeyPair newKeyPair = RsaKeyGenerator.generateKeyPair();
String newPublicKey = RsaKeyGenerator.encodePublicKey((RSAPublicKey) newKeyPair.getPublic());
String newPrivateKey = RsaKeyGenerator.encodePrivateKey((RSAPrivateKey) newKeyPair.getPrivate());

// 2. 执行轮换(旧密钥仍可用于验证)
JwtKeyManager.rotateKeys(newPublicKey, newPrivateKey);

// 3. 过渡期后清除旧密钥(建议7-30天后)
JwtKeyManager.clearPreviousKey();

📊 Redis黑名单

// 加入黑名单
blacklistService.addToBlacklist(jti, expirationSeconds);

// 检查是否在黑名单中
boolean isBlacklisted = blacklistService.isBlacklisted(jti);

// 获取黑名单大小
long size = blacklistService.getBlacklistSize();

🎯 拦截器模板

@Component
public class JwtAuthenticationInterceptor implements HandlerInterceptor {
    
    @Autowired
    private TokenBlacklistService blacklistService;

    @Override
    public boolean preHandle(HttpServletRequest request, 
                            HttpServletResponse response, 
                            Object handler) {
        String authHeader = request.getHeader("Authorization");
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            throw new JwtMalformedException("缺少认证令牌");
        }

        String token = authHeader.substring(7);
        
        if (!JwtUtil.validateToken(token, blacklistService)) {
            throw new JwtSignatureInvalidException("令牌无效");
        }

        request.setAttribute("userId", JwtUtil.getUserIdFromToken(token));
        request.setAttribute("username", JwtUtil.getUsernameFromToken(token));
        
        return true;
    }
}

📝 前端使用

存储Token

// 推荐:HttpOnly Cookie(后端设置)
// Set-Cookie: token=xxx; HttpOnly; Secure; SameSite=Strict

// 或:LocalStorage(简单场景)
localStorage.setItem('token', token);

发送请求

// Axios
axios.get('/api/v1/profile', {
  headers: {
    'Authorization': `Bearer ${token}`
  }
});

// Fetch
fetch('/api/v1/profile', {
  headers: {
    'Authorization': `Bearer ${token}`
  }
});

处理401错误

axios.interceptors.response.use(
  response => response,
  error => {
    if (error.response?.status === 401) {
      // Token过期或无效,跳转登录页
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

🔍 调试技巧

查看Token内容(不验证)

Claims claims = Jwts.parser()
    .build()
    .parseSignedClaims(token)
    .getPayload();
    
System.out.println("JTI: " + claims.getId());
System.out.println("Subject: " + claims.getSubject());
System.out.println("Expires: " + claims.getExpiration());

在线解码

访问 jwt.io 粘贴Token即可查看


性能优化

# Redis连接池
spring.data.redis.lettuce.pool.max-active=20
spring.data.redis.lettuce.pool.max-idle=10
spring.data.redis.lettuce.pool.min-idle=5

🚫 安全禁忌

不要在日志中打印完整Token
不要在前端LocalStorage存储敏感Token
不要硬编码私钥到代码中
不要忽略Token验证异常
不要在HTTP下传输Token

始终使用HTTPS
使用HttpOnly Cookie存储
定期轮换密钥
设置合理的过期时间
监控黑名单增长


📞 常见问题

Q: Token验证失败?
A: 检查公钥/私钥是否匹配,确认Token未损坏

Q: 如何使所有Token失效?
A: 修改密码后将该用户的所有JTI加入黑名单

Q: 密钥多久轮换一次?
A: 建议30-90天,高安全要求可缩短至7天

Q: Redis宕机怎么办?
A: 降级为基础验证(仅验证签名),记录告警


详细文档: JWT_ENTERPRISE_GUIDE.md
代码示例: JWT_USAGE_EXAMPLES.md