基于JWT封装用户Token,并自定义封装了拦截器校验JWT的数据有效性

252 阅读3分钟

1.JWT应用场景:登陆验证

image.png 第一次登录生成令牌,之后每次前端向后端发送请求都要带上JWT令牌,用来校验

若令牌出错或没有,则会被拦截,无法到达后端接口进行处理

image.png

2.JWT组成:

image.png 实际生成如下:

image.png

3.具体代码实现

1.创建JWT工具类JwtUtil

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;
public class JwtUtil {
    /**
     * 生成jwt
     * 使用Hs256算法, 私匙使用固定秘钥
     *
     * @param secretKey jwt秘钥
     * @param ttlMillis jwt过期时间(毫秒)
     * @param claims    设置的信息
     * @return
     */
    public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
        // 指定签名的时候使用的签名算法,也就是header那部分
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        // 生成JWT的时间
        long expMillis = System.currentTimeMillis() + ttlMillis;
        Date exp = new Date(expMillis);

        // 设置jwt的body
        JwtBuilder builder = Jwts.builder()
                // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setClaims(claims)
                // 设置签名使用的签名算法和签名使用的秘钥
                .signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置过期时间
                .setExpiration(exp);
        //调用`compact()`方法将JWT转换为字符串形式并返回
        return builder.compact();
    }

    /**
     * Token解密
     *
     * @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
     * @param token     加密后的token
     * @return
     */
    public static Claims parseJWT(String secretKey, String token) {
        // 得到DefaultJwtParser
        Claims claims = Jwts.parser()
                // 设置签名的秘钥
                .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
                // 解析JWT字符串,获取解析后的声明信息。
                .parseClaimsJws(token).getBody();
        return claims;
    }

}

2.用户登录时生成令牌


    /**
     * 登录
     *
     * @param employeeLoginDTO
     * @return
     */
    @PostMapping("/login")
    @ApiOperation(value = "员工登录")
    public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
        //.........

        //登录成功后,生成jwt令牌
        //首先创建一个Map<String, Object>类型的变量claims,用于存储令牌的声明信息。
        Map<String, Object> claims = new HashMap<>();
        //然后将员工的ID作为声明信息添加到claims中
        claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
        //用JwtUtil.createJWT()方法,传入管理员的密钥、令牌有效期和声明信息,生成JWT令牌。
        String token = JwtUtil.createJWT(
                jwtProperties.getAdminSecretKey(),
                jwtProperties.getAdminTtl(),
                claims);

        EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
                .id(employee.getId())
                .userName(employee.getUsername())
                .name(employee.getName())
                .token(token)//将生成的JWT令牌赋值给employeeLoginVO对象的token属性
                .build();

        return Result.success(employeeLoginVO);
    }

}

3.JWT令牌的校验

package com.sky.interceptor;

/**
 * jwt令牌校验的拦截器
 */
@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 校验jwt
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    //用于处理HTTP请求的预处理。它的作用是验证请求头中的令牌(Token),并根据验证结果决定是否放行请求。
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       
		 //判断传入的handler对象是否为HandlerMethod类型。如果不是,说明当前拦截到的不是Controller的方法,直接放行(返回true)
        if (!(handler instanceof HandlerMethod)) {
            //当前拦截到的不是动态方法,直接放行
            return true;
        }
        
        //1、从请求头中获取令牌
        String token = request.getHeader(jwtProperties.getAdminTokenName());

        //2、校验令牌
        try {
            log.info("jwt校验:{}", token);
            //传入管理员密匙和令牌解析令牌,获取其中的声明信息。
            Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
            //从Claims对象中获取员工ID(empId)
            Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
            log.info("当前员工id:", empId);
            //3、通过,放行
            return true;
        } catch (Exception ex) {
            //4、不通过,响应401状态码
            response.setStatus(401);
            return false;
        }
    }
}