jwt包含三个部分:
1.Header(头部)    header说明 令牌类型 和 签名算法:
2.payload(载荷)   保存数据信息;
3.signature(签名)  确保安全用的,把header,payload,加入指定密钥,通过指定的签名算法计算而来;

header和payload用的是Base64编码;
------------------------------
/**
     * 生成jwt令牌
     */
    @Test
    public void testGenJwt(){

        Map<String, Object> map=new HashMap<>();
        map.put("id",1);
        map.put("name","xiangxiang");

        // 生成jwt令牌
        String jwt = Jwts.builder()
                .signWith(SignatureAlgorithm.HS256, "xiangxiang")//设置签名,第一个参数是header中的签名算法,第二个参数是signature中的密钥
                .setClaims(map)//设置自定义内容
                .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000))//设置过期时间1h
                .compact();
        System.out.println(jwt);
    }
-------------------------------
/**
     * 解析Jwt令牌
     */
    @Test
    public void testParseJwt(){
        Claims claims = Jwts.parser()
                .setSigningKey("xiangxiang")//生成jwt时传入的密钥
                .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoieGlhbmd4aWFuZyIsImlkIjoxLCJleHAiOjE3NjE1NzIyNzl9.KuLBB8oY7YLTWVDzOJw3WNY9whl3NMbG3C3tIWHmADk")//生成的jwt令牌
                .getBody();
        System.out.println(claims);
    }
--------------------------------

注意:
易错点:
parseClaimsJws(String token)	parse Claims JSON Web Signature	解析 签名过的 JWT(JWS)
parseClaimsJwt(String token)	parse Claims JSON Web Token	解析 未签名的 JWT(纯 JWT)
两个方法差一个字母,一个是用于签过名的jwt,一个用于没签过名的jwt;


开发中一般定义一个工具类来操作jwt
public class JwtUtils {

    /**
     * 生成jwt令牌
     */
    public static String createJwt(String secret, Map<String, Object> claims,Long expire){

        String jwt = Jwts.builder()
                .signWith(SignatureAlgorithm.HS256, secret.getBytes(StandardCharsets.UTF_8))
                .setClaims(claims)
                .setExpiration(new Date(System.currentTimeMillis() + expire))
                .compact();
        return jwt;
    }
    /**
     * 解析jwt令牌
     */
    public static Claims parseJwt(String token,String secret){
        Claims claims = Jwts.parser()
                .setSigningKey(secret.getBytes(StandardCharsets.UTF_8))
                .parseClaimsJws(token)
                .getBody();
        return claims;
    }
}
--------------------------------
注意点:
1.secret.getBytes(StandardCharsets.UTF_8)是为了让 JWT 使用 UTF-8 编码后的二进制密钥来签名或验证,确保跨平台一致且安全
2.工具类方法要加static关键字,使得可以类名直接调用
-------------------------------------
为什么要转成字节数组?
因为底层 HMAC-SHA256 算法操作的是二进制数据;
setSigningKey() 方法支持多种参数类型:String、byte[]、Key;
用字符串可能因为平台编码不同(如 Windows vs Linux)而导致签名不一致;
所以推荐使用固定编码(UTF-8)来转换,确保跨平台一致性


------------------------------------
下一步要注册拦截器:

@Component//记得添加
@Slf4j
public class JwtTokenInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //判断当前拦截到的是Controller的方法还是其他资源
        if(!(handler instanceof HandlerMethod)){
                //当前拦截到的不是动态方法,直接放行
                return true;
        }

        //从请求头上获取令牌
        String token = request.getHeader("token");//参数是令牌的名称

        //检查令牌是否为空
        if(token ==null || token.isEmpty()){
            log.warn("未检测到token");
            response.setStatus(401);//设置异常状态码
            return false;
        }

        //校验令牌
        try {
            log.info("校验令牌:{}",token);
            Claims claims = JwtUtils.parseJwt(token, "xiangxiang");
            //如果校验通过,可以把解析出的用户信息存入 request 域中
            request.setAttribute("claims",claims);
            log.info("校验通过,用户信息为:{}",claims);
            return true;//放行
        }catch (Exception e){
            log.error("JWT验证失败: {}", e.getMessage());
            //不通过,响应401状态码
            response.setStatus(401);
            return false;//不放行
        }
    }
}
-----------------------------------------
同时要添加拦截器配置:
@Configuration
@Slf4j
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private JwtTokenInterceptor jwtTokenInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        log.info("开始注册自定义拦截器...");

        registry.addInterceptor(jwtTokenInterceptor)//将拦截器注入进来
                .addPathPatterns("/login/**")//添加拦截路径
                .excludePathPatterns("/login/login");//添加拦截路径下排除的路径
    }
}

------------------------------
细节:
request.setAttribute("claims", claims)
就是在 当前请求对象中临时保存解析出的用户信息,
以便后续的 Controller 能直接通过 request.getAttribute("claims") 获取当前登录用户的身份数据