JWT的神奇用法

目录

一、什么是JWT

二、JWT的结构

三、JWT在Java中的实现

3.1、添加依赖

3.2、创建一个JwtProperties类

3.3、创建一个JwtUtil类在类里创建生成jwt和解析jwt的方法,方便后来直接调用方法

3.4、在登录接口调用createJWT方法来生成JWT,将jwt返回给前端,后续的每次请求都会携带JWT

3.5、定义JwtTokenAdminInterceptor拦截器,对登录接口不拦截,其他接口要进行JWT校验,校验通过即可访问

3.6、将自定义的拦截器进行注册

四、JWT的优点和缺点


一、什么是JWT

JSON Web Tokens(JWT)是一种用于在网络应用环境间传递声明的一种紧凑的、URL安全的方式。JWT可以被用来在身份验证和信息交换中安全地在服务之间传递信息

二、JWT的结构

JWT由三部分组成,分别是Header(头部)、Payload(负载)和Signature(签名):

  • Header(头部):通常包含两部分:token的类型(这里是JWT)和所使用的签名算法(如HS256或RS256)。
  • Payload(负载):包含所谓的Claims(声明),它们是关于实体(通常是用户)和其他数据的声明。例如,可以包含用户的ID、用户名、角色等。
  • Signature(签名):用于验证消息在传输过程中未被篡改,并且,对于使用私钥签名的token,还可以验证发送者的身份。

三、JWT在Java中的实现

3.1、添加依赖

首先,需要在项目的pom.xml文件中添加JWT库的依赖。常用的库有jjwt

<dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>0.9.1</version>
            </dependency>

3.2、创建一个JwtProperties类

用来映射yml文件关于jwt的参数,进行全局配置

@Compoent注解,是把该类交给ioc容器管理

@ConfigurationProperties是完成yml文件的映射,prefix="sky.jwt"是标明参数前缀、

@Data是lombok为该类生成get和set方法用来获取参数的值

@Component
@ConfigurationProperties(prefix = "sky.jwt")
@Data
public class JwtProperties {

    /**
     * 管理端员工生成jwt令牌相关配置
     */
    private String adminSecretKey;
    private long adminTtl;
    private String adminTokenName;

 在yml文件对参数进行赋值

#自定义的,于java类相绑定的
sky:
  jwt:
    # 设置jwt签名加密时使用的秘钥
    admin-secret-key: itcast
    # 设置jwt过期时间,二十个小时
    admin-ttl: 72000000
    # 设置前端传递过来的令牌名称
    admin-token-name: token

3.3、创建一个JwtUtil类在类里创建生成jwt和解析jwt的方法,方便后来直接调用方法

/**
     * 生成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);

        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;
    }

3.4、在登录接口调用createJWT方法来生成JWT,将jwt返回给前端,后续的每次请求都会携带JWT

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

        Employee employee = employeeService.login(employeeLoginDTO);

        //登录成功后,生成jwt令牌
        Map<String, Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
        //token就是由createJWT方法生产的jwt令牌w
        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封装到VO返回给前端
                .build();
        //进行返回
        return Result.success(employeeLoginVO);
    }

3.5、定义JwtTokenAdminInterceptor拦截器,对登录接口不拦截,其他接口要进行JWT校验,校验通过即可访问

//注入JwtProperties对象
    @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 {
        //判断当前拦截到的是Controller的方法还是其他资源
        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);
            Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
            //调用线程空间的方法 将解析出来jwt令牌的empID存入这个单独空间(每个用户的请求都是一个单独的线程)
            BaseContext.setCurrentId(empId);
            //log.info("当前线程为:"+BaseContext.getCurrentId());
            log.info("当前员工id:{}", empId);
            //3、通过,放行
            return true;
        } catch (Exception ex) {
            //4、不通过,响应401状态码
            response.setStatus(401);
            return false;
        }
    }

3.6、将自定义的拦截器进行注册

创建一个WebMvcConfiguration实现WebMvcConfigurationSupport接口

@Autowired
    private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;
    /**
     * 注册自定义拦截器
     *
     * @param registry
     */
    protected void addInterceptors(InterceptorRegistry registry) {
        log.info("开始注册自定义拦截器...");
        registry.addInterceptor(jwtTokenAdminInterceptor)
                .addPathPatterns("/admin/**")
                .excludePathPatterns("/admin/employee/login");
}

四、JWT的优点和缺点

优点:

  • 安全性高:基于Token的验证机制,可以避免敏感信息在Cookie中的存储,降低被窃取的风险
  • 无状态和可扩展性:服务器不需要存储Session信息,每个请求都带有Token,减少了服务器的会话管理开销
  • 性能:在网络传输过程中,性能表现更佳

缺点:

  • Token续签问题:需要设计合理的过期时间和刷新机制,以避免用户频繁重新登录
  • 存储安全:Token存储在客户端,存在被XSS攻击窃取的风险,建议使用HTTP Only Cookie或专门的安全存储机制
  • 无法在服务端注销:一旦JWT发出,服务端无法主动使它失效,除非增加额外逻辑,如黑名单机制
  • 28
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值