Spring Boot uses Hutool-jwt to implement token verification

Spring Boot uses Hutool-jwt to implement token verification

1. Overview of JWT

1 Introduction

Simply put, JWT is a network identity authentication and information exchange format .

2. Structure

  • Header header information, which mainly declares the signature algorithm and other information of JWT;
  • Payload load information, which mainly carries various statements and transmits plaintext data ;
  • Signature signature, the JWT with this part is called JWS, that is, the signed JWS, which is used to verify the data.
# 整体结构
header.payload.signature

3. Use

The core of the JWT module is mainly two classes:

  1. JWTClasses are used to chain generate, parse or verify JWT information.
  2. JWTUtilThe class is mainly some tool encapsulation of JWT, providing more concise JWT generation, parsing and verification work.

2. Basic use

The logic is relatively simple, and the following code is used as a reference.

0. Overall thinking

  1. Write a tool class to encapsulate the methods of generating, verifying and parsing tokens;
  2. Generate a token when registering and logging in, store the generated token in redis, and get it from redis next time you log in, if it exists, it will return directly, otherwise it will be regenerated and stored in redis;
  3. Verify and parse the token in the interceptor, and store useful information in the token private static final ThreadLocal<UserDto> *THREAD_LOCAL* = new ThreadLocal<>();for subsequent access.

1. JWT tools

Adjust userDto as needed

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. Check and parse the token in the interceptor

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. Issue tokens when registering and logging in

@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. User information 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();
    }

}

Guess you like

Origin blog.csdn.net/qq_29689343/article/details/131522359