JSON Web Token(JWT)是一种开放标准(RFC 7519),用于在网络应用之间安全地传输信息。JWT 以 JSON 对象的形式封装数据,并通过数字签名保证数据的完整性和不可篡改性。它广泛应用于身份验证、信息交换和单点登录(SSO)等场景。
一、JWT 核心原理
-
基本结构
JWT(JSON Web Token)由三部分组成,以.
分隔:- Header(头部):声明加密算法(如 HS256、RS256)和令牌类型(
typ: JWT
),经 Base64Url 编码。{"alg": "HS256", "typ": "JWT"}
- Payload(负载):存储用户身份信息和声明(如
sub
、exp
、iat
),支持自定义字段,需注意敏感数据加密。{"sub": "123", "name": "Alice", "exp": 1735689600}
- 注册声明(Registered Claims):如 iss(签发者)、exp(过期时间)、sub(主题)等。
- 公共声明(Public Claims):自定义字段,需避免冲突。
- 私有声明(Private Claims):自定义字段,供双方约定使用。
- Signature(签名):对编码后的 Header 和 Payload 通过密钥和算法生成哈希值,确保数据完整性。 签名算法由 Header 中的 alg 指定,如 HS256(HMAC-SHA256)。
示例(HS256 算法):signature = HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret_key)
- Header(头部):声明加密算法(如 HS256、RS256)和令牌类型(
-
工作流程
- 生成:用户登录成功后,服务器使用密钥和算法生成包含用户信息的 JWT,返回给客户端。
- 传输:客户端存储 JWT(如 LocalStorage 或 Cookie),后续请求通过
Authorization: Bearer <token>
携带。 - 验证:服务端解析并验证签名、有效期及数据完整性,通过后授权访问资源。
二、JWT 核心优势
- 无状态性:服务端无需存储会话信息,适合微服务架构和水平扩展。
- 跨域支持:可在不同域之间传递,支持单点登录(SSO)场景。
- 自包含性:所有认证信息包含在 Token 中,减少数据库查询。
- 安全性:通过签名防篡改,结合 HTTPS 可防止中间人攻击。
三、JWT 使用实践
-
生成 Token(以 Java 为例)
// 引入依赖:com.auth0/java-jwt String token = JWT.create() .withClaim("userId", 123) .withClaim("role", "admin") .withExpiresAt(new Date(System.currentTimeMillis() + 3600 * 1000)) // 1小时过期 .sign(Algorithm.HMAC256("your_secret_key")); // 使用 HMAC-SHA256 签名。
-
验证 Token
JWTVerifier verifier = JWT.require(Algorithm.HMAC256("your_secret_key")).build(); DecodedJWT decodedJWT = verifier.verify(token); String userId = decodedJWT.getClaim("userId").asString(); // 解析用户信息。
-
集成框架(如 Spring Security)
- 配置过滤器拦截请求并验证 JWT。
- 自定义权限注解(如
@PreAuthorize("hasRole('admin')")
)实现细粒度控制。
四、安全注意事项
-
敏感信息泄露
- 风险:Payload 仅 Base64 编码,未加密,明文存储敏感数据(如手机号)易被解码。
- 防御:最小化 Payload 内容,对必要字段额外加密(如 AES)。
-
签名绕过攻击
- 风险:攻击者篡改
alg: none
或弱算法(如 HS256 密钥过短)可伪造 Token。 - 防御:强制校验算法类型,使用强密钥(如 RSA 非对称加密)。
- 风险:攻击者篡改
-
续期与吊销
- 短期有效期:设置合理过期时间(如 1 小时),结合 Refresh Token 实现无感续期。
- 黑名单机制:通过 Redis 记录已注销的 Token,拦截非法请求。
五、最佳实践总结
-
设计规范
• 仅存储必要字段(如userId
、role
),避免冗余数据。
• 使用 HTTPS 传输,防止 Token 被截获。 -
性能优化
• 减少 Token 体积(如缩写字段名),避免请求头过大。
• 分布式场景下统一密钥管理,避免验签失败。 -
工具选择
• 使用成熟库(如java-jwt
、jsonwebtoken
),避免自行实现加密逻辑。
六、JWT整合springboot案例
以下是基于多个实践案例总结的整合方案,涵盖基础实现优化和扩展场景,关键代码示例结合了多个最佳实践。
1、基础整合实现
-
添加依赖(Maven)
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>4.0.0</version> <!-- 或使用3.4.0稳定版 --> </dependency>
-
JWT工具类(核心生成/验证)
public class JwtUtils { private static final String SECRET = "your_secret_key"; private static final long EXPIRATION = 7200 * 1000; // 2小时 // 生成Token public static String generateToken(Map<String, String> claims) { return JWT.create() .withPayload(claims) .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION)) .sign(Algorithm.HMAC256(SECRET)); } // 验证Token public static DecodedJWT verifyToken(String token) { return JWT.require(Algorithm.HMAC256(SECRET)) .build() .verify(token); } }
说明:
claims
可存储用户ID、角色等非敏感数据,避免暴露密码等隐私信息。 -
登录接口实现
@RestController public class AuthController { @PostMapping("/login") public ResponseEntity<?> login(@RequestBody User user) { // 1. 验证用户名密码(需数据库查询) User dbUser = userService.findByUsername(user.getUsername()); if (dbUser == null || !passwordEncoder.matches(user.getPassword(), dbUser.getPassword())) { return ResponseEntity.status(401).body("认证失败"); } // 2. 生成Token Map<String, String> claims = new HashMap<>(); claims.put("userId", dbUser.getId()); claims.put("role", dbUser.getRole()); String token = JwtUtils.generateToken(claims); return ResponseEntity.ok().header("Authorization", token).build(); } }
2、拦截器配置(请求鉴权)
-
Token拦截器
@Component public class JwtInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token = request.getHeader("Authorization"); if (token == null) { throw new RuntimeException("未携带Token"); } try { DecodedJWT decodedJWT = JwtUtils.verifyToken(token); // 将用户信息存入请求上下文(如SecurityContextHolder) request.setAttribute("userId", decodedJWT.getClaim("userId").asString()); return true; } catch (TokenExpiredException e) { throw new RuntimeException("Token已过期"); } catch (SignatureVerificationException e) { throw new RuntimeException("签名验证失败"); } } }
-
注册拦截器
@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private JwtInterceptor jwtInterceptor @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor) .addPathPatterns("/api/**") // 拦截API路径 .excludePathPatterns("/login", "/register"); // 放行登录注册 } }
3、高级优化方案
-
结合Spring Security(推荐)
- 配置类:继承
WebSecurityConfigurerAdapter
,设置白名单和JWT过滤器:@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationFilter jwtFilter; @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/login").permitAll() .anyRequest().authenticated() .and() .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); } }
- 自定义过滤器:解析Token并设置
Authentication
对象到上下文。
- 配置类:继承
-
Token续期与吊销(Redis集成)
- 续期机制:在拦截器中判断Token剩余有效期小于阈值时生成新Token。
- 吊销方案:将失效的Token存入Redis黑名单,拦截时校验是否已注销:
// 示例:登出接口 @PostMapping("/logout") public void logout(@RequestHeader String token) { long expireTime = JwtUtils.getExpireTime(token); // 解析剩余时间 redisTemplate.opsForValue().set(token, "invalid", expireTime, TimeUnit.MILLISECONDS); }
4、最佳实践总结
-
安全性增强
- 使用HTTPS传输Token防止中间人攻击。
- 避免在Payload中存储敏感数据(如密码、手机号)。
- 密钥长度建议至少32位,使用非对称加密(RSA)更安全。
-
性能优化
- Token体积控制:缩短字段名(如
uid
代替userId
)。 - 分布式场景使用统一密钥管理(如配置中心)。
- Token体积控制:缩短字段名(如
-
监控与调试
- 集成Swagger UI可视化测试接口。
- 日志记录Token验证失败原因(如过期、篡改)。
参考实现源码: