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)證失??!token 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-11Java對(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ù)等,下面就一起來了解一下2024-07-07SpringBoot統(tǒng)一返回JSON格式實(shí)現(xiàn)方法詳解
這篇文章主要介紹了SpringBoot統(tǒng)一返回JSON格式實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-02-02Quarkus中實(shí)現(xiàn)Resteasy的文件上傳下載操作
這篇文章主要為大家介紹了Quarkus中實(shí)現(xiàn)Resteasy的文件上傳下載的操作過程步驟,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-02-02java中阻塞隊(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