SpringCloud解決feign調(diào)用token丟失問題解決辦法
背景討論
feign請求
在微服務(wù)環(huán)境中,完成一個http請求,經(jīng)常需要調(diào)用其他好幾個服務(wù)才可以完成其功能,這種情況非常普遍,無法避免。那么就需要服務(wù)之間的通過feignClient發(fā)起請求,獲取需要的 資源。
認證和鑒權(quán)
一般而言,微服務(wù)項目部署環(huán)境中,各個微服務(wù)都是運行在內(nèi)網(wǎng)環(huán)境,網(wǎng)關(guān)服務(wù)負責(zé)請求的路由,對外通過nginx暴露給請求者。
這種情況下,似乎網(wǎng)關(guān)這里做一個認證,就可以確保請求者是合法的,至于微服務(wù)調(diào)用微服務(wù),反正都是自己人,而且是內(nèi)網(wǎng),無所謂是否驗證身份了。
我有一個朋友,他們公司的項目確實就是這樣做的,正經(jīng)的商業(yè)項目。
講道理,只要框架提供了這樣的功能,那么就有存在的意義,但是,如果涉及權(quán)限的校驗,微服務(wù)之間的feign調(diào)用就需要知道身份了,即需要做鑒權(quán)。
token
無論是JWT、還是OAUTH2、還是shiro,大家比較公認的認證、鑒權(quán)方案,就是在請求頭中放一堆東西,然后服務(wù)提供者通過解析這些東西完成認證和鑒權(quán),這些東西俗稱token。
在feign調(diào)用中需要解決的就是token傳遞的問題,只有請求發(fā)起者將正確的token傳遞給服務(wù)提供者,服務(wù)提供者才能完成認證&鑒權(quán),進而返回需要的資源。
問題描述
在feign調(diào)用中可能會遇到如下問題:
- 同步調(diào)用中,token丟失,這種可以通過創(chuàng)建一個攔截器,將token做透傳來解決
- 異步調(diào)用中,token丟失,這種就無法直接透傳了,因為子線程并沒有token,這種需要先將token從父線程傳遞到子線程,再進行透傳
解決方案
token透傳
編寫一個攔截器,在feign請求前,將http請求攜帶的token傳遞給restTemplate。
具體實現(xiàn)方式為:
創(chuàng)建一個Component實現(xiàn)com.nghsmart.ar.context.RequestAttributeContext中的RequestInterceptor接口
重寫apply方法
通過RequestContextHolder對象獲取到RequestAttributes
通過RequestAttributes對象獲取到HttpServletRequest
通過HttpServletRequest對象獲取到請求頭
在請求頭中把token拿出來
將token塞進restTemplate創(chuàng)建的http請求頭中
示例代碼:
BizFeignRequestInterceptor
import com.nghsmart.ar.context.RequestAttributeContext; import com.nghsmart.common.core.utils.ServletUtils; import com.nghsmart.common.core.utils.StringUtils; import com.nghsmart.common.core.utils.ip.IpUtils; import com.nghsmart.common.security.constant.FeignRequestHeader; import feign.RequestInterceptor; import feign.RequestTemplate; import lombok.extern.slf4j.Slf4j; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.web.context.request.AbstractRequestAttributes; import org.springframework.web.context.request.FacesRequestAttributes; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import javax.servlet.http.HttpServletRequest; import java.util.Map; @Slf4j @Order(1) @Component public class BizFeignRequestInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate requestTemplate) { RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); if (null! = attributes) { ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) attributes; String token = servletRequestAttributes.getRequest().getHeader("token"); requestTemplate.header("token",token); } } }
token異步線程傳遞
上述添加BizFeignRequestInterceptor只能解決同步調(diào)用環(huán)境下的token傳遞問題,當(dāng)是異步線程環(huán)境下就GG了。
通過在主線程中主動將RequestAttribute傳遞到子線程中可以解決一部分異步線程中token傳遞的問題,示例代碼如下:
RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true);
但是這種方式有弊端,當(dāng)主線程先于子線程結(jié)束的時候,子線程將獲取不到RequestAttribute,原因是Tomcat會在http請求結(jié)束的時候清空數(shù)據(jù)。
我們可以創(chuàng)建一個InheritableThreadLocal用來保存RequestAttribute,這樣就可以完美解決問題了。
實現(xiàn)思路為:
創(chuàng)建一個 RequestAttributeContext,其中維護一個InheritableThreadLocal對象,用來存RequestAttributes
創(chuàng)建一個RequestAttributeInterceptor,實現(xiàn)HandlerInterceptor, WebMvcConfigurer接口,用來在請求開始前把 RequestAttributes 存放到 RequestAttributeContext 中
修改 BizFeignRequestInterceptor ,當(dāng)無法獲取到 RequestAttributes 的時候,就從 RequestAttributeContext 中獲取
透傳邏輯不變
相關(guān)示例代碼如下:
RequestAttributeContext
import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.web.context.request.RequestAttributes; @Slf4j public class RequestAttributeContext { private static final ThreadLocal<RequestAttributes> context = new InheritableThreadLocal<>(); public static void setAttribute(RequestAttributes attributes) { if (null == attributes) { log.debug("RequestAttributes is null"); } context.set(attributes); } public static RequestAttributes getAttribute() { return context.get(); } public static void removeAttribute() { context.remove(); } }
RequestAttributeInterceptor
import com.alibaba.fastjson.JSON; import com.nghsmart.ar.context.RequestAttributeContext; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Configuration; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Slf4j @Configuration public class RequestAttributeInterceptor implements HandlerInterceptor, WebMvcConfigurer { /** * 重寫 WebMvcConfigurer 的 addInterceptors,將 RequestAttributeInterceptor 添加到攔截器列表 * * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(this).addPathPatterns("/**").excludePathPatterns("/swagger-resources/**", "/v2/api-docs/**"); } /** * 重寫 HandlerInterceptor 的 preHandle,在請求開始處理前,將 RequestAttribute 存入 RequestAttributeContext * * @param request current HTTP request * @param response current HTTP response * @param handler chosen handler to execute, for type and/or instance evaluation * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); RequestAttributeContext.setAttribute(requestAttributes); return true; } }
BizFeignRequestInterceptor
import com.nghsmart.ar.context.RequestAttributeContext; import com.nghsmart.common.core.utils.ServletUtils; import com.nghsmart.common.core.utils.StringUtils; import com.nghsmart.common.core.utils.ip.IpUtils; import com.nghsmart.common.security.constant.FeignRequestHeader; import feign.RequestInterceptor; import feign.RequestTemplate; import lombok.extern.slf4j.Slf4j; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.web.context.request.AbstractRequestAttributes; import org.springframework.web.context.request.FacesRequestAttributes; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import javax.servlet.http.HttpServletRequest; import java.util.Map; @Slf4j @Order(1) @Component public class BizFeignRequestInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate requestTemplate) { RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); if (null! = attributes) { ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) attributes; String token = servletRequestAttributes.getRequest().getHeader("token"); requestTemplate.header("token",token); }else { RequestAttributes requestAttributes = RequestAttributeContext.getAttribute(); if (null != requestAttributes) { RequestContextHolder.setRequestAttributes(requestAttributes); } else { log.debug("requestAttributes is null"); } ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes; String token = servletRequestAttributes.getRequest().getHeader("token"); requestTemplate.header("token",token); } } }
到此這篇關(guān)于SpringCloud解決feign調(diào)用token丟失問題解決辦法的文章就介紹到這了,更多相關(guān)SpringCloud中feign調(diào)用token丟失內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mybatis整合springboot報BindingException:Invalid?bound?stateme
這篇文章主要給大家介紹了關(guān)于mybatis整合springboot報BindingException:Invalid?bound?statement?(not?found)異常的解決辦法,這個錯誤通常是由于Mapper文件中的statement?id與Java代碼中的方法名不一致導(dǎo)致的,需要的朋友可以參考下2024-01-01解讀靜態(tài)資源訪問static-locations和static-path-pattern
本文主要介紹了Spring Boot中靜態(tài)資源的配置和訪問方式,包括靜態(tài)資源的默認前綴、默認地址、目錄結(jié)構(gòu)、訪問路徑以及靜態(tài)資源處理器的工作原理,通過配置文件和實現(xiàn)`WebMvcConfigurer`接口,可以自定義靜態(tài)資源目錄和訪問前綴2025-01-01Spring MVC如何設(shè)置請求頭和響應(yīng)頭的Header
這篇文章主要介紹了Spring MVC如何設(shè)置請求頭和響應(yīng)頭的Header問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-03-03