Spring Boot 使用 Hutool-jwt 实现 token 验证功能

 更新时间:2023年07月04日 15:42:30   作者:訾博ZiBo  
JWT 就是一种网络身份认证和信息交换格式,这篇文章主要介绍了Spring Boot使用Hutool-jwt实现token验证,需要的朋友可以参考下

一、JWT 概述

1、简介

简单地说,JWT 就是一种网络身份认证和信息交换格式

2、结构

  • Header 头部信息,主要声明了 JWT 的签名算法等信息;
  • Payload 载荷信息,主要承载了各种声明并传递明文数据
  • Signature 签名,拥有该部分的 JWT 被称为 JWS,也就是签了名的 JWS ,用于校验数据。
# 整体结构
header.payload.signature

3、使用

JWT模块的核心主要是两个类:

  • JWT类用于链式生成、解析或验证JWT信息。
  • JWTUtil类主要是JWT的一些工具封装,提供更加简洁的JWT生成、解析和验证工作。

二、基本使用

逻辑较为简单,下面的代码作为参考。

0、整体思路

  • 写一个工具类封装生成、校验和解析 token 的方法;
  • 在注册和登录时生成 token ,生成的 token 存入 redis ,下次登录去 redis 获取,如果存在则直接返回,反之重新生成,并存入 redis;
  • 在拦截器中校验和解析 token ,拿到 token 中有用的信息存入 private static final ThreadLocal<UserDto> *THREAD_LOCAL* = new ThreadLocal<>(); ,以便后续取用。

1、JWT 工具类

根据需要调整 userDto

package com.zibo.common.util;
import cn.hutool.jwt.JWT;
import com.zibo.modules.user.dto.UserDto;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
/**
 * JWT 工具类
 *
 * @author zibo
 * @date 2023/7/2 14:36
 * @slogan 慢慢学,不要停。
 */
public class JWTUtility {
    /**
     * 密钥
     */
    private static final byte[] KEY = "zibo".getBytes();
    /**
     * 过期时间(秒):7 天
     */
    public static final long EXPIRE = 7 * 24 * 60 * 60;
    private JWTUtility() {
    }
    /**
     * 根据 userDto 生成 token
     *
     * @param dto    用户信息
     * @return token
     */
    public static String generateTokenForUser(UserDto dto) {
        Map<String, Object> map = new HashMap<>();
        map.put("id", dto.getId());
        map.put("nickname", dto.getNickname());
        return generateToken(map);
    }
    /**
     * 根据 map 生成 token 默认:HS265(HmacSHA256)算法
     *
     * @param map    携带数据
     * @return token
     */
    public static String generateToken(Map<String, Object> map) {
        JWT jwt = JWT.create();
        // 设置携带数据
        map.forEach(jwt::setPayload);
        // 设置密钥
        jwt.setKey(KEY);
        // 设置过期时间
        jwt.setExpiresAt(new Date(System.currentTimeMillis() + EXPIRE * 1000));
        return jwt.sign();
    }
    /**
     * token 校验
     * @param token token
     * @return 是否通过校验
     */
    public static boolean verify (String token) {
        if (StringUtils.isBlank(token)) return false;
        return JWT.of(token).setKey(KEY).verify();
    }
    /**
     * token 校验,并获取 userDto
     * @param token token
     * @return userDto
     */
    public static UserDto verifyAndGetUser(String token) {
        if(!verify(token)) return null;
        // 解析数据
        JWT jwt = JWT.of(token);
        Long id = Long.valueOf(jwt.getPayload("id").toString());
        String nickname = jwt.getPayload("nickname").toString();
        // 返回用户信息
        return new UserDto(id, nickname);
    }
}

2、在拦截器中校验和解析 token

package com.zibo.common.config;
import com.zibo.common.enums.AppCode;
import com.zibo.common.pojo.ApiException;
import com.zibo.common.pojo.UserHolder;
import com.zibo.common.util.JWTUtility;
import com.zibo.modules.user.dto.UserDto;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
/**
 * 自定义拦截器
 * @author Administrator
 */
@Component
@Slf4j
public class UserInterceptor implements HandlerInterceptor {
    /**
     * 进入controller方法之前执行。如果返回false,则不会执行 controller 的方法
     *
     * @param request 请求
     * @param response 响应
     * @param handler 处理器
     * @return 是否放行
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 获取 header 中的 Authorization 信息
        String token = request.getHeader("token");
        if (StringUtils.isNotBlank(token)) {
            UserDto dto = JWTUtility.verifyAndGetUser(token);
            if (dto != null) {
                UserHolder.setUserInfo(dto);
            } else {
                log.error("token 验证失败!token is {}, uri is {}", token, request.getRequestURI());
                throw new ApiException(AppCode.TOKEN_ERROR, "token 校验不通过!");
            }
        } else {
            log.error("token 验证失败!token is {}, uri is {}", token, request.getRequestURI());
            throw new ApiException(AppCode.TOKEN_ERROR, "token 为空!");
        }
        return true;
    }
    /**
     * 响应结束之前
     * @param request 请求
     * @param response 响应
     * @param handler 处理器
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        // 清理掉当前线程中的数据,防止内存泄漏
        UserHolder.remove();
    }
}

3、在注册和登录时签发 token

@PostMapping("/loginOrRegister")
public UserDto loginOrRegister(@RequestBody @Validated UserDto dto) {
    // 通过手机号查询
    UserDto byPhone = service.findByPhone(dto.getPhone());
    // 如果操作标记为空,则报错
    if (ObjectUtils.isEmpty(dto.getOperation())) {
        throw new ApiException("操作标记不能为空!");
    }
    // 如果是注册
    if (dto.getOperation() == 0) {
        // 如果用户已经存在,则报错
        if (ObjectUtils.isNotEmpty(byPhone)) {
            throw new ApiException("注册失败!账号已存在!");
        }
        // 创建用户
        Long save = service.save(dto);
        // 返回用户信息
        UserDto userDto = service.findById(save);
        // 根据用户生成 token
        String token = JWTUtility.generateTokenForUser(userDto);
        // 保存到 redis
        service.saveToken(userDto.getId(), token);
        // 设置 token
        userDto.setToken(token);
        LogUtility.baseInfoWith("注册成功!" + userDto);
        return userDto;
    }
    // 登录
    if (ObjectUtils.isEmpty(byPhone)) {
        log.error("登录失败!账号不存在!" + dto);
        // 账号不存在
        throw new ApiException("登录失败!账号不存在!com/zibo/controller/user/UserController.java:62");
    } else {
        // 比较密码是否一致
        if (!byPhone.getPassword().equals(dto.getPassword())) {
            throw new ApiException("登录失败!账号或密码错误!");
        }
        // 更新最后登录时间
        byPhone.setLastLoginTime(LocalDateTime.now());
        service.save(byPhone);
        // 从 redis 获取 token
        String token = service.getToken(byPhone.getId());
        if (StringUtils.isBlank(token)) {
            // 根据用户生成 token
            token = JWTUtility.generateTokenForUser(byPhone);
            log.info("用户登录,并生成token,id 为:{}, 昵称为:{},token 为:{}", byPhone.getId(), byPhone.getNickname(), token);
            // 保存到 redis
            service.saveToken(byPhone.getId(), token);
        }
        // 设置 token
        byPhone.setToken(token);
        LogUtility.baseInfoWith("登录成功!" + byPhone);
        return byPhone;
    }
}

4、用户信息 UserHolder

package com.zibo.common.pojo;
import com.zibo.modules.user.dto.UserDto;
/**
 * 存放用户信息的容器
 * @author Administrator
 */
public class UserHolder {
    private static final ThreadLocal<UserDto> THREAD_LOCAL = new ThreadLocal<>();
    private UserHolder() {
    }
    /**
     * 获取线程中的用户
     * @return 用户信息
     */
    public static UserDto getUserInfo() {
        return THREAD_LOCAL.get();
    }
    /**
     * 设置当前线程中的用户
     * @param info 用户信息
     */
    public static void setUserInfo(UserDto info) {
        THREAD_LOCAL.set(info);
    }
    public static Long getUserId() {
        UserDto dto = THREAD_LOCAL.get();
        if (dto != null) {
            return dto.getId();
        } else {
            // 注册或登录时没有,返回 0
            return 0L;
        }
    }
    public static void remove() {
        THREAD_LOCAL.remove();
    }
}

到此这篇关于Spring Boot 使用 Hutool-jwt 实现 token 验证的文章就介绍到这了,更多相关Spring Boot token 验证内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 如何利用java实现生成PDF文件

    如何利用java实现生成PDF文件

    前段时间因为相关业务需求需要后台生成pdf文件,下面这篇文章主要给大家介绍了关于如何利用java实现生成PDF文件的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-10-10
  • Java中使用Jedis操作Redis的示例代码

    Java中使用Jedis操作Redis的示例代码

    本篇文章主要介绍了Java中使用Jedis操作Redis的示例代码,具有一定的参考价值,有兴趣的可以了解一下。
    2016-12-12
  • Java实现多用户注册登录的幸运抽奖

    Java实现多用户注册登录的幸运抽奖

    这篇文章主要为大家详细介绍了Java实现多用户注册登录的幸运抽奖,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-11-11
  • Java对线程池做监控的实现方法

    Java对线程池做监控的实现方法

    本文主要介绍了Java对线程池做监控的实现方法,监控线程池可以帮助我们了解线程池的状态,如当前活跃线程数、任务队列长度、已完成任务数等,下面就一起来了解一下
    2024-07-07
  • SpringBoot统一返回JSON格式实现方法详解

    SpringBoot统一返回JSON格式实现方法详解

    这篇文章主要介绍了SpringBoot统一返回JSON格式实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-02-02
  • JavaWeb三大组件之一的Filter详解

    JavaWeb三大组件之一的Filter详解

    本篇文章主要介绍了JavaWeb三大组件之中的Filter过滤器详解,实例分析了JavaWeb之Filter过滤器的使用技巧,非常具有实用价值,需要的朋友可以参考下
    2022-06-06
  • mybatis处理枚举类的简单方法

    mybatis处理枚举类的简单方法

    这篇文章主要给大家介绍了关于mybatis处理枚举类的简单方法,文中通过示例代码介绍的非常详细,对大家学习或者使用mybatis具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-05-05
  • Quarkus中实现Resteasy的文件上传下载操作

    Quarkus中实现Resteasy的文件上传下载操作

    这篇文章主要为大家介绍了Quarkus中实现Resteasy的文件上传下载的操作过程步骤,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2022-02-02
  • java动态代理示例分享

    java动态代理示例分享

    这篇文章主要介绍了java动态代理示例,需要的朋友可以参考下
    2014-02-02
  • java中阻塞队列和非阻塞队列的实现

    java中阻塞队列和非阻塞队列的实现

    在Java并发编程中,阻塞队列和非阻塞队列是两种主要的队列类型,分别适用于不同的场景,了解这两种队列的特点和工作机制,可以帮助开发者更好地选择合适的数据结构解决并发问题
    2024-10-10

最新评论