
1. 为什么我们需要JWT现代Web应用开发中身份认证是个绕不开的话题。还记得十年前我们是怎么处理用户登录的吗服务器创建一个session把session ID存在cookie里每次请求都要查数据库验证。这套方案在单体应用时代还能凑合但到了微服务架构下就变成了性能瓶颈——想象一下每个API调用都要查一次用户表这谁顶得住啊JWTJSON Web Token就是为了解决这个问题而生的。2015年正式成为RFC 7519标准后它迅速成为分布式系统身份认证的事实标准。我在去年重构的电商系统中就全面采用了JWT单是认证环节的数据库查询就减少了80%API响应时间直接砍半。2. JWT的庐山真面目2.1 解剖一个真实的JWT先看个实际案例这是我项目里的一个JWTeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyNDkwMjJ9. SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c这个字符串看着乱其实暗藏玄机。用点号分隔的三部分分别是Header头部eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9Payload载荷eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyNDkwMjJ9Signature签名SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c把它们base64解码后// Header { alg: HS256, typ: JWT } // Payload { sub: 1234567890, name: John Doe, iat: 1516239022, exp: 1516249022 }2.2 核心工作原理JWT的妙处在于它的自包含性。服务器通过签名保证Token的合法性后后续请求只需要验证签名即可完全不需要查数据库。签名算法通常选用HMAC SHA256HS256或RSA前者适合单机环境后者更适合分布式场景。关键点签名不等于加密JWT默认是明文传输的敏感信息应该加密后再放入Payload。3. Spring Boot实战指南3.1 项目搭建先引入必要的依赖dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version0.11.5/version scoperuntime/scope /dependency3.2 JWT工具类封装这是我项目中经过实战检验的JWT工具类public class JwtUtils { private static final String SECRET_KEY your-256-bit-secret; private static final long EXPIRATION 86400000; // 24小时 public static String generateToken(UserDetails user) { return Jwts.builder() .setSubject(user.getUsername()) .claim(roles, user.getAuthorities()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() EXPIRATION)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } public static boolean validateToken(String token) { try { Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token); return true; } catch (Exception e) { // 这里应该根据具体异常类型记录日志 return false; } } }3.3 拦截器实现在Spring Security配置中添加JWT过滤器Override protected void configure(HttpSecurity http) throws Exception { http.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } // 过滤器实现 public class JwtAuthenticationFilter extends OncePerRequestFilter { Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { String token resolveToken(request); if (token ! null JwtUtils.validateToken(token)) { Authentication auth getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(auth); } chain.doFilter(request, response); } private String resolveToken(HttpServletRequest req) { String bearerToken req.getHeader(Authorization); if (bearerToken ! null bearerToken.startsWith(Bearer )) { return bearerToken.substring(7); } return null; } }4. 那些年我踩过的坑4.1 签名密钥安全问题初期我把密钥硬编码在代码里后来用配置中心动态获取。但最坑的是测试环境用了弱密钥比如123456结果被同事用彩虹表轻松破解。现在我们的密钥规范是生产环境256位随机字符串通过KMS管理测试环境同样强度的伪随机数4.2 Token过期处理前端在Token快过期时比如剩余5分钟应该用refresh token获取新token。但浏览器端存储refresh token有安全风险我们的解决方案是设置较短的access token有效期30分钟refresh token通过HttpOnly的cookie传输每次refresh都检查IP和User-Agent4.3 性能优化技巧当用户量突破百万时单纯的JWT验证也会成为瓶颈。我们通过以下优化将认证耗时从15ms降到2ms用Guava Cache缓存已解析的Token设置10秒过期对HS256签名改用线程安全的SecretKeySpec实例关闭JJWT的冗余日志5. 最佳实践清单根据三年来的实战经验我总结了这些黄金法则永远设置exp过期时间我见过没设过期导致的严重安全事件敏感信息加密存储比如用户手机号应该先AES加密再放入Payload启用HTTPSJWT在HTTP下就是裸奔黑名单机制虽然JWT本身无状态但关键操作如修改密码需要使旧Token失效监控异常请求大量401错误可能是密钥泄露的信号最后分享一个排查技巧当遇到诡异的认证失败时先用 jwt.io 调试器解析Token往往能快速定位是签名问题还是Payload格式错误。