# 企业级JWT使用示例 ## 📖 目录 - [基本使用](#基本使用) - [高级功能](#高级功能) - [最佳实践](#最佳实践) --- ## 基本使用 ### 1. 生成Token(登录时) 当前项目已在 `UserServiceImpl.login()` 中集成: ```java // src/main/java/com/caiji/uls/service/impl/UserServiceImpl.java @Override public LoginRespond login(String username, String password) { // ... 验证用户和密码 ... LoginRespond respond = new LoginRespond(); respond.setUserId(Long.valueOf(user.getId())); respond.setUsername(user.getUsername()); // ✅ 已集成企业级RSA JWT respond.setToken(JwtUtil.generateToken(user.getId().toString(), user.getUsername())); return respond; } ``` **返回示例:** ```json { "code": 200, "message": "登录成功", "data": { "userId": 1, "username": "admin", "token": "eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiI1ZjNlMmE4Yi0xYjRkLTRl..." } } ``` --- ### 2. 验证Token(拦截器/过滤器中) 创建认证拦截器示例: ```java package com.caiji.uls.config; import com.caiji.uls.utils.exception.jwt.*; import com.caiji.uls.utils.jwt.JwtUtil; import com.caiji.uls.utils.jwt.TokenBlacklistService; import io.jsonwebtoken.JwtException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; @Component public class JwtAuthenticationInterceptor implements HandlerInterceptor { @Autowired private TokenBlacklistService blacklistService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 从Header获取Token String authHeader = request.getHeader("Authorization"); if (authHeader == null || !authHeader.startsWith("Bearer ")) { throw new JwtMalformedException("缺少认证令牌"); } String token = authHeader.substring(7); // 去掉"Bearer "前缀 try { // ✅ 完整验证(签名 + 格式 + 黑名单) if (!JwtUtil.validateToken(token, blacklistService)) { throw new JwtSignatureInvalidException("令牌验证失败"); } // 提取用户信息并存储到请求属性 String userId = JwtUtil.getUserIdFromToken(token); String username = JwtUtil.getUsernameFromToken(token); request.setAttribute("userId", userId); request.setAttribute("username", username); return true; // 通过验证 } catch (JwtTokenExpiredException e) { throw new JwtTokenExpiredException("令牌已过期"); } catch (JwtTokenBlacklistedException e) { throw new JwtTokenBlacklistedException("令牌已被注销"); } catch (JwtException e) { throw new JwtSignatureInvalidException("令牌无效"); } } } ``` 注册拦截器: ```java package com.caiji.uls.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private JwtAuthenticationInterceptor jwtAuthenticationInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtAuthenticationInterceptor) .addPathPatterns("/api/v1/**") // 保护所有API .excludePathPatterns( "/api/v1/login", // 排除登录接口 "/api/v1/register" // 排除注册接口 ); } } ``` --- ### 3. 在Controller中使用 ```java package com.caiji.uls.controller; import com.caiji.uls.dto.Uni_Respond; import jakarta.servlet.http.HttpServletRequest; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1") public class UserController { @GetMapping("/profile") public Uni_Respond getUserProfile(HttpServletRequest request) { // ✅ 从请求属性获取已验证的用户信息 String userId = (String) request.getAttribute("userId"); String username = (String) request.getAttribute("username"); // 查询用户详细信息... Uni_Respond response = new Uni_Respond(); response.setCode(200); response.setMessage("获取成功"); response.setData(userInfo); return response; } } ``` --- ## 高级功能 ### 4. Token注销(登出) ```java @PostMapping("/logout") public Uni_Respond logout(HttpServletRequest request) { String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { String token = authHeader.substring(7); try { // 获取Token的JTI String jti = JwtUtil.getJtiFromToken(token); // 计算剩余有效期 long remainingTime = JwtUtil.getTokenRemainingTime(token); // ✅ 加入黑名单 blacklistService.addToBlacklist(jti, remainingTime); } catch (Exception e) { // Token可能已过期或无效,忽略 } } Uni_Respond response = new Uni_Respond(); response.setCode(200); response.setMessage("登出成功"); return response; } ``` --- ### 5. 带额外声明的Token ```java // 登录时添加角色信息 Map claims = new HashMap<>(); claims.put("role", "admin"); claims.put("permissions", Arrays.asList("read", "write", "delete")); claims.put("department", "IT"); String token = JwtUtil.generateToken(userId.toString(), username, claims); ``` 在拦截器中提取: ```java Claims claims = JwtUtil.getClaimsFromToken(token); String role = claims.get("role", String.class); List permissions = claims.get("permissions", List.class); // 基于角色的访问控制 if ("admin".equals(role)) { // 管理员权限 } ``` --- ### 6. Token刷新 ```java @PostMapping("/refresh-token") public Uni_Respond refreshToken(HttpServletRequest request) { String authHeader = request.getHeader("Authorization"); if (authHeader == null || !authHeader.startsWith("Bearer ")) { throw new JwtMalformedException("缺少令牌"); } String oldToken = authHeader.substring(7); // 验证旧Token if (!JwtUtil.validateToken(oldToken, blacklistService)) { throw new JwtSignatureInvalidException("令牌无效"); } // ✅ 生成新Token(新的JTI和过期时间) String newToken = JwtUtil.refreshToken(oldToken); // 将旧Token加入黑名单 String jti = JwtUtil.getJtiFromToken(oldToken); long remainingTime = JwtUtil.getTokenRemainingTime(oldToken); blacklistService.addToBlacklist(jti, remainingTime); Map data = new HashMap<>(); data.put("token", newToken); Uni_Respond response = new Uni_Respond(); response.setCode(200); response.setMessage("刷新成功"); response.setData(data); return response; } ``` --- ### 7. 修改密码后使所有Token失效 ```java @PostMapping("/change-password") public Uni_Respond changePassword(@RequestBody ChangePasswordRequest req) { // 1. 验证旧密码 // 2. 更新新密码 // 3. ✅ 将所有该用户的Token加入黑名单 // (实际项目中需要维护用户ID -> JTI列表的映射) Set userTokens = redisTemplate.opsForSet() .members("user:tokens:" + userId); for (String token : userTokens) { try { String jti = JwtUtil.getJtiFromToken(token); blacklistService.addToBlacklist(jti, 86400); // 24小时 } catch (Exception e) { // Token可能已过期 } } // 4. 清除缓存 redisTemplate.delete("user:tokens:" + userId); Uni_Respond response = new Uni_Respond(); response.setCode(200); response.setMessage("密码修改成功,请重新登录"); return response; } ``` --- ## 最佳实践 ### ✅ 推荐做法 1. **始终使用HTTPS传输Token** ```nginx # Nginx配置强制HTTPS server { listen 443 ssl; ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; } ``` 2. **Token存储在HttpOnly Cookie中(前端)** ```javascript // 前端设置Cookie document.cookie = `token=${token}; HttpOnly; Secure; SameSite=Strict; Path=/`; ``` 3. **设置合理的过期时间** ```properties # 短期Token(推荐) jwt.expiration=3600000 # 1小时 # 或使用Refresh Token机制 # Access Token: 15分钟 # Refresh Token: 7天 ``` 4. **监控黑名单大小** ```java @Scheduled(fixedRate = 3600000) // 每小时 public void monitorBlacklist() { long size = blacklistService.getBlacklistSize(); if (size > 10000) { log.warn("JWT黑名单数量异常: {}", size); } } ``` 5. **定期轮换密钥** ```java @Scheduled(cron = "0 0 0 1 * ?") // 每月1号凌晨 public void rotateKeys() { // 1. 生成新密钥对 // 2. 执行轮换 JwtKeyManager.rotateKeys(newPublicKey, newPrivateKey); // 3. 通知所有服务更新公钥 // 4. 30天后清除旧密钥 } ``` --- ### ❌ 避免的做法 1. **❌ 不要在日志中打印完整Token** ```java // 错误 log.info("Token: {}", token); // 正确 log.info("Token JTI: {}", JwtUtil.getJtiFromToken(token)); ``` 2. **❌ 不要在前端LocalStorage存储敏感Token** ```javascript // 不安全 localStorage.setItem('token', token); // 推荐 // 使用HttpOnly Cookie ``` 3. **❌ 不要硬编码密钥** ```properties # 错误 - 不要提交到Git jwt.private-key=MIIEvAIBADANBgkqh... # 推荐 - 使用环境变量 jwt.private-key=${JWT_PRIVATE_KEY} ``` 4. **❌ 不要忽略Token验证异常** ```java // 错误 try { JwtUtil.validateToken(token); } catch (Exception e) { // 静默忽略 - 危险! } // 正确 if (!JwtUtil.validateToken(token, blacklistService)) { throw new UnauthorizedException("认证失败"); } ``` --- ## 🔍 调试技巧 ### 查看Token内容(不验证签名) ```java import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; // 解析Token(仅用于调试,不验证签名) Claims claims = Jwts.parser() .build() .parseSignedClaims(token) .getPayload(); System.out.println("JTI: " + claims.getId()); System.out.println("Subject: " + claims.getSubject()); System.out.println("Issuer: " + claims.getIssuer()); System.out.println("Expiration: " + claims.getExpiration()); ``` ### 在线JWT解码工具 - [jwt.io](https://jwt.io/) - 粘贴Token即可查看内容(注意:不要在生产环境使用) --- ## 📊 性能优化 1. **Redis连接池配置** ```properties spring.data.redis.lettuce.pool.max-active=20 spring.data.redis.lettuce.pool.max-idle=10 spring.data.redis.lettuce.pool.min-idle=5 ``` 2. **本地缓存公钥** ```java // JwtKeyManager已使用AtomicReference,线程安全且高性能 RSAPublicKey publicKey = JwtKeyManager.getPublicKey(); ``` 3. **批量黑名单检查** ```java // 对于高频API,可以考虑本地LRU缓存 @Cacheable(value = "blacklist", key = "#jti") public boolean isBlacklisted(String jti) { return blacklistService.isBlacklisted(jti); } ``` --- ## 🎯 总结 你现在拥有: - ✅ RSA-256非对称加密 - ✅ 完整的Claims验证(ISS/AUD/NBF/JTI) - ✅ Redis黑名单防重放 - ✅ 密钥轮换机制 - ✅ 分类异常处理 - ✅ 全局异常处理器 这是**符合OWASP标准的企业级JWT实现**!🚀