SpringBoot中JWT详解,底层原理及生成验证实例。
目录
JWT
什么是 JWT?
在 Spring Boot 中使用 JWT 的场景
JWT 底层原理
1. 令牌生成
2. 令牌验证
3. 签名算法
Spring Boot 中 JWT 的实现示例
JWT 的优缺点
安全注意事项
实例演示:
导入Maven坐标
JwtProperties配置类
application.yml配置文件
JwtTokenUserInterceptor拦截器
WebMvcConfiguration注册自定义拦截器
Controller层登录生成token
JWT
什么是 JWT?
JWT (JSON Web Token) 是一种开放标准 (RFC 7519),用于在各方之间安全地传输信息作为 JSON 对象。它通常用于身份验证和信息交换。
JWT 由三部分组成,用点(.)分隔:
-
Header - 包含令牌类型和使用的哈希算法
-
Payload - 包含声明(claims),即有关实体(通常是用户)和其他数据的声明
-
Signature - 用于验证消息在传输过程中没有被更改
格式:xxxxx.yyyyy.zzzzz
在 Spring Boot 中使用 JWT 的场景
-
身份验证:用户登录后,服务器生成 JWT 返回给客户端,客户端在后续请求中携带该令牌
-
信息交换:安全地在各方之间传输信息
-
无状态认证:特别适合 RESTful API,服务端不需要保存会话信息
-
跨域认证:适用于单点登录(SSO)场景
-
移动应用认证:比传统的 cookie-session 更适合移动端
JWT 底层原理
1. 令牌生成
-
用户提供凭据(如用户名密码)登录
-
服务器验证凭据
-
服务器创建 JWT:
-
Header: {"alg": "HS256", "typ": "JWT"}
-
Payload: {"sub": "1234567890", "name": "John Doe", "iat": 1516239022}
-
Signature: 使用密钥对编码后的 header 和 payload 进行签名
-
服务器返回 JWT 给客户端
-
2. 令牌验证
-
客户端在请求头中发送 JWT (通常在 Authorization 头中)
-
服务器提取 JWT
-
服务器验证签名是否有效
-
如果有效,服务器使用 payload 中的信息处理请求
3. 签名算法
JWT 通常使用以下算法之一进行签名:
-
HMAC + SHA256 (HS256)
-
RSA + SHA256 (RS256)
-
ECDSA + SHA256 (ES256)、
Spring Boot 中 JWT 的实现示例
// 添加依赖 // implementation 'io.jsonwebtoken:jjwt:0.9.1' import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.Claims; public class JwtUtil { private static final String SECRET_KEY = "your-secret-key"; private static final long EXPIRATION_TIME = 864_000_000; // 10天 // 生成JWT public static String generateToken(String username) { return Jwts.builder() .setSubject(username) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } // 验证JWT public static Boolean validateToken(String token, String username) { final String usernameInToken = extractUsername(token); return (username.equals(usernameInToken) && !isTokenExpired(token)); } // 从JWT中提取用户名 public static String extractUsername(String token) { return getClaims(token).getSubject(); } // 检查JWT是否过期 public static Boolean isTokenExpired(String token) { return getClaims(token).getExpiration().before(new Date()); } // 从JWT中获取claims private static Claims getClaims(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody(); } }
JWT 的优缺点
优点:
-
无状态,服务器不需要存储会话信息
-
适合分布式系统和微服务架构
-
可以包含自定义的 claims
-
跨语言支持
缺点:
-
令牌一旦签发,在有效期内无法撤销(除非使用黑名单机制)
-
如果被盗用,攻击者可以在有效期内滥用
-
负载大小比 session ID 大
安全注意事项
-
始终使用 HTTPS
-
不要将敏感信息存储在 JWT payload 中
-
设置合理的过期时间
-
考虑使用刷新令牌机制
-
对于高安全性应用,可以考虑使用短期令牌+黑名单机制
实例演示:
导入Maven坐标
io.jsonwebtoken jjwt ${jjwt}
JwtProperties配置类
import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "sky.jwt") @Data public class JwtProperties { /** * 用户端微信用户生成jwt令牌相关配置 */ private String userSecretKey; private long userTtl; private String userTokenName; }
application.yml配置文件
sky: jwt: #设置jwt签名加密时使用的密钥 user-secret-key: itcast # 设置jwt过期时间 user-ttl: 7200000 # 设置前端传递过来的令牌名称 user-token-name: authentication
JwtTokenUserInterceptor拦截器
/** * jwt令牌校验的拦截器 */ @Component @Slf4j public class JwtTokenUserInterceptor implements HandlerInterceptor { @Autowired private JwtProperties jwtProperties; /** * 校验用户端的jwt * * @param request * @param response * @param handler * @return * @throws Exception */ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //System.out.println("当前线程的id:"+Thread.currentThread().getId()); //判断当前拦截到的是Controller的方法还是其他资源 if (!(handler instanceof HandlerMethod)) { //当前拦截到的不是动态方法,直接放行 return true; } //1、从请求头中获取令牌 String token = request.getHeader(jwtProperties.getUserTokenName()); //2、校验令牌 try { log.info("用户端jwt校验:{}", token); Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token); Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString()); log.info("当前用户id:", userId); BaseContext.setCurrentId(userId); //3、通过,放行 return true; } catch (Exception ex) { //4、不通过,响应401状态码 response.setStatus(401); return false; } } }
WebMvcConfiguration注册自定义拦截器
/** * 配置类,注册web层相关组件 */ @Configuration @Slf4j public class WebMvcConfiguration extends WebMvcConfigurationSupport { @Autowired private JwtTokenUserInterceptor jwtTokenUserInterceptor; /** * 注册自定义拦截器 * * @param registry */ protected void addInterceptors(InterceptorRegistry registry) { log.info("开始注册自定义拦截器..."); registry.addInterceptor(jwtTokenUserInterceptor) .addPathPatterns("/user/**") .excludePathPatterns("/user/user/login") .excludePathPatterns("user/shop/status"); } }
Controller层登录生成token
@RestController @RequestMapping("/user/user") @Slf4j @RequiredArgsConstructor // Lombok 自动生成构造函数 @Api(tags = "C端用户相关接口") public class UserController { private final UserService userService; private final JwtProperties jwtProperties; /** * 微信登录 * @param userLoginDTO * @return */ @PostMapping("/login") @ApiOperation("微信登录") public Result login(@RequestBody UserLoginDTO userLoginDTO){ log.info("微信用户登录:{}",userLoginDTO.getCode()); //微信登录 User user = userService.wxLogin(userLoginDTO); //为微信用户生成jwt令牌 Map claims = new HashMap(); claims.put(JwtClaimsConstant.USER_ID,user.getId()); String token = JwtUtil.createJWT(jwtProperties.getUserSecretKey(),jwtProperties.getUserTtl(),claims); UserLoginVO userLoginVO = UserLoginVO.builder() .id(user.getId()) .openid(user.getOpenid()) .token(token) .build(); return Result.success(userLoginVO); } }
-
-
-