SpringBoot3中token攔截器鏈的設(shè)計(jì)與實(shí)現(xiàn)步驟
流程分析
用戶在進(jìn)行登陸后服務(wù)器會發(fā)放token等信息一起返回給前端,前端會進(jìn)行保存,那么token里面是攜帶一些有關(guān)用戶的身份等信息的,用戶端在請求后端時(shí)需要在請求頭攜帶token,請求先被攔截器截獲,只有經(jīng)過多重?cái)r截器校驗(yàn)通過后才可以執(zhí)行對應(yīng)功能接口,否則會拋出異常返回對應(yīng)錯(cuò)誤信息。
需要清楚的
- 每次登錄都要刷新token信息,
- 不能在用戶訪問的過程中token過期,只要用戶訪問,token就要刷新有效期。
- 如果token正確解析token中的用戶id,根據(jù)用戶id查詢用戶信息。
實(shí)現(xiàn)步驟
總的來說大致分為4步:
1定義攔截器--->2創(chuàng)建攔截器鏈配置類--->3配置攔截器鏈順序--->4配置攔截排除項(xiàng)
1.定義攔截器
首先,需要定義第一個(gè)攔截器類,該攔截器類需要實(shí)現(xiàn) Spring 框架提供的 HandlerInterceptor
接口。該攔截器只做一件事就是刷新token。
import cn.hutool.json.JSONUtil; import com.mijiu.commom.util.JwtUtils; import com.mijiu.commom.util.UserHolder; import com.mijiu.entity.User; import io.jsonwebtoken.Claims; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import java.util.Objects; import java.util.concurrent.TimeUnit; /** * @author mijiupro */ @Slf4j @Component public class RefreshTokenInterceptor implements HandlerInterceptor { private final JwtUtils jwtUtils; private final StringRedisTemplate stringRedisTemplate; public RefreshTokenInterceptor(JwtUtils jwtUtils, StringRedisTemplate stringRedisTemplate) { this.jwtUtils = jwtUtils; this.stringRedisTemplate = stringRedisTemplate; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1、從請求頭中獲取token String authorizationHeader = request.getHeader("authorization"); if (StringUtils.isBlank(authorizationHeader)) { return true; } // 2.解析token Claims claims = jwtUtils.parseToken(authorizationHeader); if (Objects.isNull(claims)) { return true; } // 3.獲取用戶信息 Integer userId = claims.get("userId", Integer.class); String userInfoJson = stringRedisTemplate.opsForValue().get("login:user:" + userId); if (StringUtils.isBlank(userInfoJson)) { return true; } // 4.刷新token String refreshToken = jwtUtils.refreshToken(authorizationHeader); response.setHeader("Access-Control-Expose-Headers", "Authorization"); response.addHeader("Authorization", refreshToken); stringRedisTemplate.expire("login:user:" + userId, 30, TimeUnit.MINUTES); // 5.將用戶信息存入本地線程方便獲取 User user = JSONUtil.toBean(userInfoJson, User.class); UserHolder.setInfoByToken(user); return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { // 清理本地線程 UserHolder.clear(); } }
值得注意:因?yàn)橛行┙涌谑遣恍枰J(rèn)證的比如你在商城,瀏覽商品,是不是不登錄也可以瀏覽。不登錄就沒token,沒token就直接放行(認(rèn)證交給后續(xù)的認(rèn)證攔截器),有token就直接刷新(不可能你登錄了瀏覽了30分鐘,突然下單然后告訴你token過期重新登錄吧,所以登錄后調(diào)用的每個(gè)接口都要走一遍token刷新)。最后請求處理完一定要清理一下本地線程,不然用戶多的時(shí)候內(nèi)存占用會很大。
然后,就要實(shí)現(xiàn)一個(gè)認(rèn)證攔截器了,實(shí)現(xiàn)用戶身份認(rèn)證。
import com.mijiu.commom.util.UserHolder; import com.mijiu.entity.User; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import java.util.Objects; /** * @author mijiupro */ @Component public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { User user = UserHolder.getInfoByToken(); if (Objects.isNull(user)) { response.setStatus(401); return false; } return true; } }
值得注意:在上個(gè)攔截器我們是做過解析token了并存在本地線程里面,所以只需要判斷本地線程有沒有即可。
2.創(chuàng)建攔截器鏈配置類
創(chuàng)建一個(gè)配置類,用于配置攔截器鏈。在該配置類中,通過實(shí)現(xiàn) WebMvcConfigurer
接口來添加攔截器,具體包括 addInterceptors
方法。
import com.mijiu.commom.interceptor.LoginInterceptor; import com.mijiu.commom.interceptor.RefreshTokenInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @author mijiupro */ @Configuration public class WebConfig implements WebMvcConfigurer { private final RefreshTokenInterceptor refreshTokenInterceptor; private final LoginInterceptor loginInterceptor; public WebConfig(RefreshTokenInterceptor refreshTokenInterceptor, LoginInterceptor loginInterceptor) { this.refreshTokenInterceptor = refreshTokenInterceptor; this.loginInterceptor = loginInterceptor; } @Override public void addInterceptors( InterceptorRegistry registry) { registry.addInterceptor(refreshTokenInterceptor) .addPathPatterns("/**").order(0);//設(shè)置攔截器對所有路徑生效,執(zhí)行順序?yàn)? registry.addInterceptor(loginInterceptor) .excludePathPatterns("/captcha/graph-captcha")//排除用戶登錄獲取驗(yàn)證碼接口 .excludePathPatterns("/","*/login","*.html","/images/**","/doc.html" ,"/webjars/**","/swagger-resources","/swagger-resources/**","/v3/**")//排除登錄獲取靜態(tài)資源、swagger接口文檔等。 .order(1);//設(shè)置攔截器對所有路徑生效,執(zhí)行順序?yàn)? } @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") // 對所有路徑生效 .allowedOrigins("*") //允許所有源地址 .allowedMethods("GET", "POST", "PUT", "DELETE") // 允許的請求方法 .allowedHeaders("*"); // 允許的請求頭 } }
3.配置攔截器鏈順序
刷新token的攔截器要最先執(zhí)行,接著才是認(rèn)證攔截器
4.配置攔截排除項(xiàng)
像用戶登錄的驗(yàn)證碼接口、登錄接口以及像一些靜態(tài)資源、網(wǎng)頁、圖片等需要進(jìn)行攔截排除,如果整合了swagger接口文檔也是需要排除的。
最后
token攔截器鏈的設(shè)計(jì)與實(shí)現(xiàn)核心就四步:
1定義攔截器--->2創(chuàng)建攔截器鏈配置類--->3配置攔截器鏈順序--->4配置攔截排除項(xiàng)
到此這篇關(guān)于SpringBoot3中token攔截器鏈的設(shè)計(jì)與實(shí)現(xiàn)步驟的文章就介紹到這了,更多相關(guān)SpringBoot3 token攔截器鏈內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringCloud Webflux過濾器增加header傳遞方式
這篇文章主要介紹了SpringCloud Webflux過濾器增加header傳遞方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02Java實(shí)現(xiàn)通過時(shí)間獲取8位驗(yàn)證碼
這篇文章主要為大家詳細(xì)介紹了Java如何通過時(shí)間獲取8位驗(yàn)證碼(每兩個(gè)小時(shí)生成一個(gè)),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-11-11java使用泛型實(shí)現(xiàn)棧結(jié)構(gòu)示例分享
泛型是Java SE5.0的重要特性,使用泛型編程可以使代碼獲得最大的重用。由于在使用泛型時(shí)要指明泛型的具體類型,這樣就避免了類型轉(zhuǎn)換。本實(shí)例將使用泛型來實(shí)現(xiàn)一個(gè)棧結(jié)構(gòu),并對其進(jìn)行測試2014-03-03java中continue和break區(qū)別詳細(xì)解析
break和continue都是跳轉(zhuǎn)語句,它們將程序的控制權(quán)轉(zhuǎn)移到程序的另一部分,下面這篇文章主要給大家介紹了關(guān)于java中continue和break區(qū)別的相關(guān)資料,需要的朋友可以參考下2022-11-11springboot 實(shí)現(xiàn)長鏈接轉(zhuǎn)短鏈接的示例代碼
短鏈接服務(wù)通過將長URL轉(zhuǎn)換成6位短碼,并存儲長短鏈接對應(yīng)關(guān)系到數(shù)據(jù)庫中,用戶訪問短鏈接時(shí),系統(tǒng)通過查詢數(shù)據(jù)庫并重定向到原始URL,實(shí)現(xiàn)快速訪問,本文就來介紹一下如何使用,感興趣的可以了解一下2024-09-09基于spring?@Cacheable?注解的spel表達(dá)式解析執(zhí)行邏輯
這篇文章主要介紹了spring?@Cacheable?注解的spel表達(dá)式解析執(zhí)行邏輯,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01