Spring?Boot?使用?Hutool-jwt?實(shí)現(xiàn)?token?驗(yàn)證功能
一、JWT 概述
1、簡(jiǎn)介
簡(jiǎn)單地說,JWT 就是一種網(wǎng)絡(luò)身份認(rèn)證和信息交換格式。
2、結(jié)構(gòu)
- Header 頭部信息,主要聲明了 JWT 的簽名算法等信息;
- Payload 載荷信息,主要承載了各種聲明并傳遞明文數(shù)據(jù);
- Signature 簽名,擁有該部分的 JWT 被稱為 JWS,也就是簽了名的 JWS ,用于校驗(yàn)數(shù)據(jù)。
# 整體結(jié)構(gòu) header.payload.signature
3、使用
JWT模塊的核心主要是兩個(gè)類:
JWT類用于鏈?zhǔn)缴?、解析或?yàn)證JWT信息。JWTUtil類主要是JWT的一些工具封裝,提供更加簡(jiǎn)潔的JWT生成、解析和驗(yàn)證工作。
二、基本使用
邏輯較為簡(jiǎn)單,下面的代碼作為參考。
0、整體思路
- 寫一個(gè)工具類封裝生成、校驗(yàn)和解析 token 的方法;
- 在注冊(cè)和登錄時(shí)生成 token ,生成的 token 存入 redis ,下次登錄去 redis 獲取,如果存在則直接返回,反之重新生成,并存入 redis;
- 在攔截器中校驗(yàn)和解析 token ,拿到 token 中有用的信息存入
private static final ThreadLocal<UserDto> *THREAD_LOCAL* = new ThreadLocal<>();,以便后續(xù)取用。
1、JWT 工具類
根據(jù)需要調(diào)整 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 慢慢學(xué),不要停。
*/
public class JWTUtility {
/**
* 密鑰
*/
private static final byte[] KEY = "zibo".getBytes();
/**
* 過期時(shí)間(秒):7 天
*/
public static final long EXPIRE = 7 * 24 * 60 * 60;
private JWTUtility() {
}
/**
* 根據(jù) 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);
}
/**
* 根據(jù) map 生成 token 默認(rèn):HS265(HmacSHA256)算法
*
* @param map 攜帶數(shù)據(jù)
* @return token
*/
public static String generateToken(Map<String, Object> map) {
JWT jwt = JWT.create();
// 設(shè)置攜帶數(shù)據(jù)
map.forEach(jwt::setPayload);
// 設(shè)置密鑰
jwt.setKey(KEY);
// 設(shè)置過期時(shí)間
jwt.setExpiresAt(new Date(System.currentTimeMillis() + EXPIRE * 1000));
return jwt.sign();
}
/**
* token 校驗(yàn)
* @param token token
* @return 是否通過校驗(yàn)
*/
public static boolean verify (String token) {
if (StringUtils.isBlank(token)) return false;
return JWT.of(token).setKey(KEY).verify();
}
/**
* token 校驗(yàn),并獲取 userDto
* @param token token
* @return userDto
*/
public static UserDto verifyAndGetUser(String token) {
if(!verify(token)) return null;
// 解析數(shù)據(jù)
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、在攔截器中校驗(yàn)和解析 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 {
/**
* 進(jìn)入controller方法之前執(zhí)行。如果返回false,則不會(huì)執(zhí)行 controller 的方法
*
* @param request 請(qǐng)求
* @param response 響應(yīng)
* @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 驗(yàn)證失??!token is {}, uri is {}", token, request.getRequestURI());
throw new ApiException(AppCode.TOKEN_ERROR, "token 校驗(yàn)不通過!");
}
} else {
log.error("token 驗(yàn)證失?。oken is {}, uri is {}", token, request.getRequestURI());
throw new ApiException(AppCode.TOKEN_ERROR, "token 為空!");
}
return true;
}
/**
* 響應(yīng)結(jié)束之前
* @param request 請(qǐng)求
* @param response 響應(yīng)
* @param handler 處理器
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
// 清理掉當(dāng)前線程中的數(shù)據(jù),防止內(nèi)存泄漏
UserHolder.remove();
}
}3、在注冊(cè)和登錄時(shí)簽發(fā) token
@PostMapping("/loginOrRegister")
public UserDto loginOrRegister(@RequestBody @Validated UserDto dto) {
// 通過手機(jī)號(hào)查詢
UserDto byPhone = service.findByPhone(dto.getPhone());
// 如果操作標(biāo)記為空,則報(bào)錯(cuò)
if (ObjectUtils.isEmpty(dto.getOperation())) {
throw new ApiException("操作標(biāo)記不能為空!");
}
// 如果是注冊(cè)
if (dto.getOperation() == 0) {
// 如果用戶已經(jīng)存在,則報(bào)錯(cuò)
if (ObjectUtils.isNotEmpty(byPhone)) {
throw new ApiException("注冊(cè)失敗!賬號(hào)已存在!");
}
// 創(chuàng)建用戶
Long save = service.save(dto);
// 返回用戶信息
UserDto userDto = service.findById(save);
// 根據(jù)用戶生成 token
String token = JWTUtility.generateTokenForUser(userDto);
// 保存到 redis
service.saveToken(userDto.getId(), token);
// 設(shè)置 token
userDto.setToken(token);
LogUtility.baseInfoWith("注冊(cè)成功!" + userDto);
return userDto;
}
// 登錄
if (ObjectUtils.isEmpty(byPhone)) {
log.error("登錄失敗!賬號(hào)不存在!" + dto);
// 賬號(hào)不存在
throw new ApiException("登錄失??!賬號(hào)不存在!com/zibo/controller/user/UserController.java:62");
} else {
// 比較密碼是否一致
if (!byPhone.getPassword().equals(dto.getPassword())) {
throw new ApiException("登錄失?。≠~號(hào)或密碼錯(cuò)誤!");
}
// 更新最后登錄時(shí)間
byPhone.setLastLoginTime(LocalDateTime.now());
service.save(byPhone);
// 從 redis 獲取 token
String token = service.getToken(byPhone.getId());
if (StringUtils.isBlank(token)) {
// 根據(jù)用戶生成 token
token = JWTUtility.generateTokenForUser(byPhone);
log.info("用戶登錄,并生成token,id 為:{}, 昵稱為:{},token 為:{}", byPhone.getId(), byPhone.getNickname(), token);
// 保存到 redis
service.saveToken(byPhone.getId(), token);
}
// 設(shè)置 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();
}
/**
* 設(shè)置當(dāng)前線程中的用戶
* @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 {
// 注冊(cè)或登錄時(shí)沒有,返回 0
return 0L;
}
}
public static void remove() {
THREAD_LOCAL.remove();
}
}到此這篇關(guān)于Spring Boot 使用 Hutool-jwt 實(shí)現(xiàn) token 驗(yàn)證的文章就介紹到這了,更多相關(guān)Spring Boot token 驗(yàn)證內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)多用戶注冊(cè)登錄的幸運(yùn)抽獎(jiǎng)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)多用戶注冊(cè)登錄的幸運(yùn)抽獎(jiǎng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-11-11
Java對(duì)線程池做監(jiān)控的實(shí)現(xiàn)方法
本文主要介紹了Java對(duì)線程池做監(jiān)控的實(shí)現(xiàn)方法,監(jiān)控線程池可以幫助我們了解線程池的狀態(tài),如當(dāng)前活躍線程數(shù)、任務(wù)隊(duì)列長(zhǎng)度、已完成任務(wù)數(shù)等,下面就一起來(lái)了解一下2024-07-07
SpringBoot統(tǒng)一返回JSON格式實(shí)現(xiàn)方法詳解
這篇文章主要介紹了SpringBoot統(tǒng)一返回JSON格式實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-02-02
Quarkus中實(shí)現(xiàn)Resteasy的文件上傳下載操作
這篇文章主要為大家介紹了Quarkus中實(shí)現(xiàn)Resteasy的文件上傳下載的操作過程步驟,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-02-02
java中阻塞隊(duì)列和非阻塞隊(duì)列的實(shí)現(xiàn)
在Java并發(fā)編程中,阻塞隊(duì)列和非阻塞隊(duì)列是兩種主要的隊(duì)列類型,分別適用于不同的場(chǎng)景,了解這兩種隊(duì)列的特點(diǎn)和工作機(jī)制,可以幫助開發(fā)者更好地選擇合適的數(shù)據(jù)結(jié)構(gòu)解決并發(fā)問題2024-10-10

