springboot在filter中如何用threadlocal存放用戶身份信息
本文章主要描述通過springboot的filter類,在過濾器中設置jwt信息進行身份信息保存的方法
流程:請求->過濾器->解析請求的body信息->放入threadlocal中
定義filter:一個使用 Servlet 規(guī)范的過濾器(Filter),它通過 @WebFilter 注解注冊為攔截所有匹配 /api 路徑的 HTTP 請求。
@WebFilter(“/api”) 注解指定了過濾器將應用于所有訪問 /api路徑的請求。
@Component 注解:
@Component 是 Spring 框架的注解,表明 JwtFilter 是一個 Spring 組件,可以被 Spring 容器管理,并支持依賴注入。
doFilter方法:
doFilter 方法定義了過濾器如何攔截和處理進入 Servlet 或 Servlet 容器的請求和響應。
方法簽名:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException;這個方法接受三個參數(shù):ServletRequest request、ServletResponse response 和 FilterChain chain。
它可能拋出 IOException 或 ServletException。
請求和響應:
doFilter 方法的前兩個參數(shù)代表當前的請求和響應對象,你可以在這個方法中讀取請求數(shù)據(jù)、修改請求和響應。
通常,在 doFilter 方法的最后,你需要調用 chain.doFilter(request, response) 來繼續(xù)執(zhí)行過濾器鏈中的下一個過濾器或目標資源。
如果要重新修改請求內容,可以用HttpServletRequestWrapper,HttpServletRequestWrapper 是一個包裝器類,它擴展了 HttpServletRequest 接口,允許你修改或擴展請求的處理。使用 HttpServletRequestUriWrapper(這可能是一個自定義的包裝器類,繼承自 HttpServletRequestWrapper)的目的通常包括:
修改請求 URI:
你可能想要修改請求的 URI,但不想改變原始的 HttpServletRequest 對象。通過使用 HttpServletRequestUriWrapper,你可以包裝原始請求并提供一個修改后的 URI。
保持原始請求不變:
使用包裝器可以保持原始請求對象不變,同時允許你在過濾鏈中的某個點修改請求的某些方面。
過濾和預處理:
在調用 filterChain.doFilter 之前,你可以在 doFilter 方法中添加任何預處理邏輯,例如修改請求參數(shù)、更改請求路徑、添加或修改請求頭等。
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
@WebFilter("/api")
@Component
@Slf4j
public class JwtFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) {
// noting to do
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
var httpRequest = (HttpServletRequest) servletRequest;
var requestBodyPayload = StreamUtils.copyToString(servletRequest.getInputStream(), StandardCharsets.UTF_8);
// 解析Body參數(shù),并存入threadLocal管理
var jwtInfo = JwtUtil.getJwtInfoFromReq(requestBodyPayload);
JwtUtil.setJwtInfo(jwtInfo);
// 讀取過body,需要重新設置body
var wrapper = new HttpServletRequestUriWrapper(httpRequest, httpRequest.getRequestURI(), requestBodyPayload);
// 將請求傳遞到下一個過濾器(或者最終到達控制器方法)
filterChain.doFilter(wrapper, servletResponse);
}
@Override
public void destroy() {
JwtUtil.removeJwtInfo();
MDC.clear();
}
}jwt信息:
@Slf4j
@Component
public class JwtUtil {
/** 線程jwt信息維護 */
private static final ThreadLocal<JwtInfo> REQUEST_BASE_INFO_THREAD_LOCAL = new ThreadLocal<>();
/** 解析jwt信息 */
public static JwtInfo getJwtInfoFromReq(String requestBodyPayload) {
var jwtInfo = new JwtInfo();
try {
var requestBody = JsonUtil.getJsonNode(requestBodyPayload);
log.info("[JwtUtil] RequestBody -> {}", requestBody);
// 解析requestBody,轉為jwtInfo對象
jwtInfo.setRequestId(requestBody.get("RequestId") != null ? requestBody.get("RequestId").asText() : "");
jwtInfo.setRegion(requestBody.get("Region") != null ? requestBody.get("Region").asText() : "");
log.info("[JwtUtil] JwtInfo -> {}", jwtInfo);
} catch (Exception e) {
log.error("[JwtUtil] Parse RequestBodyInfo Error, Error Message -> {}", e.getMessage(), e);
}
return jwtInfo;
}
/** 獲取jwt信息 */
public static JwtInfo getJwtInfo() {
var jwtInfo = REQUEST_BASE_INFO_THREAD_LOCAL.get();
if (Objects.isNull(jwtInfo)) {
final var requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (Objects.nonNull(requestAttributes)) {
var requestBodyPayload = "";
try {
requestBodyPayload = StreamUtils.copyToString(requestAttributes.getRequest().getInputStream(),
StandardCharsets.UTF_8);
} catch (Exception e) {
log.error("[JwtUtil] Parse RequestBodyInfo Error, Error Message -> {}", e.getMessage());
}
jwtInfo = getJwtInfoFromReq(requestBodyPayload);
setJwtInfo(jwtInfo);
}
}
return jwtInfo;
}
/** 將jwt信息存入threadLocal中 */
public static void setJwtInfo(JwtInfo jwtInfo) {
REQUEST_BASE_INFO_THREAD_LOCAL.set(jwtInfo);
// 將traceId寫入日志變量
MDC.put("traceId", jwtInfo.getRequestId());
}
public static void setJwtInfo(String appId, String ownerUin) {
var jwtInfo = new JwtUtil.JwtInfo();
jwtInfo.setRequestId(UUID.randomUUID().toString());
setJwtInfo(jwtInfo);
}
/** 從threadLocal中刪除jwt信息 */
public static void removeJwtInfo() {
REQUEST_BASE_INFO_THREAD_LOCAL.remove();
}
@Data
public static class JwtInfo {
@JsonPropertyDescription("請求requestId")
private String requestId;
@JsonPropertyDescription("請求的Region")
private String region;
}
}獲得jwt中的內容,去發(fā)送其他http請求:
public static JsonNode sendHttpRequest(String method, String action, String url, Map<String, Object> body)
throws IOException, InterruptedException {
// 設置通用參數(shù)
var jwtInfo = JwtUtil.getJwtInfo();
if (jwtInfo != null) {
body.put("RequestId", jwtInfo.getRequestId());
body.put("AppId", Integer.valueOf(jwtInfo.getAppId()));
body.put("Uin", jwtInfo.getUin());
body.put("Region", jwtInfo.getRegion());
}
// 設置action
body.put("Action", action);
// 發(fā)送http請求,拿到請求結果
HttpConnectUtil.ResponseInfo responseInfo = switch (method) {
case "GET" -> HttpConnectUtil.sendGetByJson(url, JsonUtil.toJson(body));
case "POST" -> HttpConnectUtil.sendPost(url, JsonUtil.toJson(body), new HashMap<>(2));
default -> new HttpConnectUtil.ResponseInfo();
};
// 檢查Api3格式返回結果,并解析
var jsonResponse = JsonUtil.getJsonNode(responseInfo.getContent()).get("Response");
var jsonError = jsonResponse.get("Error");
if (jsonError != null) {
var errorCode = jsonError.get("Code").asText();
var errorMessage = jsonError.get("Message").asText();
throw new ApiException(ErrorCode.INTERNAL_ERROR,
String.format("錯誤碼:[%s],錯誤信息:[%s]", errorCode, errorMessage));
}
return jsonResponse;
}到此這篇關于springboot中在filter中用threadlocal存放用戶身份信息的文章就介紹到這了,更多相關springboot threadlocal存放用戶身份信息內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
- SpringBoot中的ThreadLocal保存請求用戶信息的實例demo
- springboot登錄攔截器+ThreadLocal實現(xiàn)用戶信息存儲的實例代碼
- SpringBoot ThreadLocal 簡單介紹及使用詳解
- SpringBoot+ThreadLocal+AbstractRoutingDataSource實現(xiàn)動態(tài)切換數(shù)據(jù)源
- Springboot公共字段填充及ThreadLocal模塊改進方案
- SpringBoot ThreadLocal實現(xiàn)公共字段自動填充案例講解
- SpringBoot通過ThreadLocal實現(xiàn)登錄攔截詳解流程
- springboot 使用ThreadLocal的實例代碼
- SpringBoot中使用?ThreadLocal?進行多線程上下文管理及注意事項小結
相關文章
SpringMVC中的DispatcherServlet初始化流程詳解
這篇文章主要介紹了SpringMVC中的DispatcherServlet初始化流程詳解,DispatcherServlet這個前端控制器是一個Servlet,所以生命周期和普通的Servlet是差不多的,在一個Servlet初始化的時候都會調用該Servlet的init()方法,需要的朋友可以參考下2023-12-12
java wait()/notify() 實現(xiàn)生產者消費者模式詳解
這篇文章主要介紹了java wait()/notify() 實現(xiàn)生產者消費者模式詳解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07
Spring AI對接大模型開發(fā)易錯點總結與實戰(zhàn)解決辦法
本文介紹了SpringAI接入大模型時常見的問題及解決方案,主要從地址配置、密鑰鑒權、模型匹配、版本依賴四大維度入手,詳細分析了各種問題的成因,并提供了實用的配置與代碼解決方案,幫助開發(fā)者快速避坑,提高開發(fā)效率,需要的朋友可以參考下2026-05-05
Java代碼混淆工具ProGuard使用指南(附有1.8以上和以下使用工具)
ProGuard是一個開源的Java?class文件縮小器、優(yōu)化器、混淆器和預驗證器,它通過刪除未使用的類、字段、方法和屬性,優(yōu)化字節(jié)碼指令,并重命名類、字段和方法,使反編譯后的代碼難以理解,從而提高應用的安全性,本文給大家詳細介紹了ProGuard使用指南,需要的朋友可以參考下2025-05-05

