您的位置:首頁(yè) > 軟件教程 > 教程 > SpringCloud解決feign調(diào)用token丟失問(wèn)題

SpringCloud解決feign調(diào)用token丟失問(wèn)題

來(lái)源:好特整理 | 時(shí)間:2024-05-17 18:46:17 | 閱讀:194 |  標(biāo)簽: T 問(wèn) token Ri K S C in   | 分享到:

背景討論 feign請(qǐng)求 在微服務(wù)環(huán)境中,完成一個(gè)http請(qǐng)求,經(jīng)常需要調(diào)用其他好幾個(gè)服務(wù)才可以完成其功能,這種情況非常普遍,無(wú)法避免。那么就需要服務(wù)之間的通過(guò)feignClient發(fā)起請(qǐng)求,獲取需要的 資源。 認(rèn)證和鑒權(quán) 一般而言,微服務(wù)項(xiàng)目部署環(huán)境中,各個(gè)微服務(wù)都是運(yùn)行在內(nèi)網(wǎng)環(huán)境,網(wǎng)關(guān)服務(wù)負(fù)責(zé)請(qǐng)

背景討論

feign請(qǐng)求

在微服務(wù)環(huán)境中,完成一個(gè)http請(qǐng)求,經(jīng)常需要調(diào)用其他好幾個(gè)服務(wù)才可以完成其功能,這種情況非常普遍,無(wú)法避免。那么就需要服務(wù)之間的通過(guò)feignClient發(fā)起請(qǐng)求,獲取需要的 資源 。

認(rèn)證和鑒權(quán)

一般而言,微服務(wù)項(xiàng)目部署環(huán)境中,各個(gè)微服務(wù)都是運(yùn)行在內(nèi)網(wǎng)環(huán)境,網(wǎng)關(guān)服務(wù)負(fù)責(zé)請(qǐng)求的 路由 ,對(duì)外通過(guò)nginx暴露給請(qǐng)求者。

這種情況下,似乎網(wǎng)關(guān)這里做一個(gè) 認(rèn)證 ,就可以確保請(qǐng)求者是合法的,至于微服務(wù)調(diào)用微服務(wù),反正都是自己人,而且是內(nèi)網(wǎng),無(wú)所謂是否驗(yàn)證身份了。

我有一個(gè)朋友 ,他們公司的項(xiàng)目確實(shí)就是這樣做的,正經(jīng)的商業(yè)項(xiàng)目。

講道理,只要框架提供了這樣的功能,那么就有存在的意義,但是,如果涉及權(quán)限的校驗(yàn),微服務(wù)之間的feign調(diào)用就需要知道 身份 了,即需要做 鑒權(quán) 。

token

無(wú)論是JWT、還是OAUTH2、還是shiro,大家比較公認(rèn)的認(rèn)證、鑒權(quán)方案,就是在請(qǐng)求頭中放一堆東西,然后服務(wù)提供者通過(guò)解析這些東西完成認(rèn)證和鑒權(quán),這些東西俗稱 token

在feign調(diào)用中需要解決的就是token傳遞的問(wèn)題,只有請(qǐng)求發(fā)起者將正確的token傳遞給服務(wù)提供者,服務(wù)提供者才能完成認(rèn)證&鑒權(quán),進(jìn)而返回需要的 資源 。

問(wèn)題描述

在feign調(diào)用中可能會(huì)遇到如下問(wèn)題:

  • 同步調(diào)用中,token丟失,這種可以通過(guò)創(chuàng)建一個(gè)攔截器,將token做透?jìng)鱽?lái)解決
  • 異步調(diào)用中,token丟失,這種就無(wú)法直接透?jìng)髁耍驗(yàn)樽泳程并沒(méi)有 token ,這種需要先將token從父線程傳遞到子線程,再進(jìn)行透?jìng)?

解決方案

token透?jìng)?

編寫(xiě)一個(gè)攔截器,在feign請(qǐng)求前,將http請(qǐng)求攜帶的token傳遞給restTemplate。

具體實(shí)現(xiàn)方式為:

  • 創(chuàng)建一個(gè)Component實(shí)現(xiàn)com.nghsmart.ar.context.RequestAttributeContext中的RequestInterceptor接口

  • 重寫(xiě)apply方法

  • 通過(guò)RequestContextHolder對(duì)象獲取到RequestAttributes

  • 通過(guò)RequestAttributes對(duì)象獲取到HttpServletRequest

  • 通過(guò)HttpServletRequest對(duì)象獲取到請(qǐng)求頭

  • 在請(qǐng)求頭中把token拿出來(lái)

  • 將token塞進(jìn)restTemplate創(chuàng)建的http請(qǐng)求頭中

示例代碼:

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傳遞問(wèn)題,當(dāng)是異步線程環(huán)境下就GG了。

通過(guò)在主線程中主動(dòng)將RequestAttribute傳遞到子線程中可以解決一部分異步線程中token傳遞的問(wèn)題,示例代碼如下:


RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true);


但是這種方式有弊端,當(dāng)主線程先于子線程結(jié)束的時(shí)候,子線程將獲取不到RequestAttribute,原因是Tomcat會(huì)在http請(qǐng)求結(jié)束的時(shí)候清空數(shù)據(jù)。

我們可以創(chuàng)建一個(gè)InheritableThreadLocal用來(lái)保存RequestAttribute,這樣就可以完美解決問(wèn)題了。

實(shí)現(xiàn)思路為:

  • 創(chuàng)建一個(gè) RequestAttributeContext,其中維護(hù)一個(gè)InheritableThreadLocal對(duì)象,用來(lái)存RequestAttributes

  • 創(chuàng)建一個(gè)RequestAttributeInterceptor,實(shí)現(xiàn)HandlerInterceptor, WebMvcConfigurer接口,用來(lái)在請(qǐng)求開(kāi)始前把 RequestAttributes 存放到 RequestAttributeContext 中

  • 修改 BizFeignRequestInterceptor ,當(dāng)無(wú)法獲取到 RequestAttributes? 的時(shí)候,就從 RequestAttributeContext 中獲取

  • 透?jìng)鬟壿嫴蛔?

相關(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 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 {

? ? /**
? ? ?* 重寫(xiě) WebMvcConfigurer 的 addInterceptors,將 RequestAttributeInterceptor 添加到攔截器列表
? ? ?*
? ? ?* @param registry
? ? ?*/
? ? @Override
? ? public void addInterceptors(InterceptorRegistry registry) {
? ? ? ? registry.addInterceptor(this).addPathPatterns("/**").excludePathPatterns("/swagger-resources/**", "/v2/api-docs/**");
? ? }
? ? /**
? ? ?* 重寫(xiě) HandlerInterceptor 的 preHandle,在請(qǐng)求開(kāi)始處理前,將 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);
? ? ? ? }
? ? }
}


引用

https://zhuanlan.zhihu.com/p/545508501

小編推薦閱讀

好特網(wǎng)發(fā)布此文僅為傳遞信息,不代表好特網(wǎng)認(rèn)同期限觀點(diǎn)或證實(shí)其描述。

RPG Ri序章 0.2.1
RPG Ri序章 0.2.1
類(lèi)型:角色扮演  運(yùn)營(yíng)狀態(tài):正式運(yùn)營(yíng)  語(yǔ)言: 日文  

游戲攻略

游戲禮包

游戲視頻

游戲下載

游戲活動(dòng)

《RPG_Ri序章》是GameMaker'Child-Dream'制作的一款幻想廢土風(fēng)RPG手游,完全免費(fèi)的幻想廢土風(fēng)RPG登場(chǎng)!元
K
K
類(lèi)型:角色扮演  運(yùn)營(yíng)狀態(tài):封測(cè)  語(yǔ)言:中文   

游戲攻略

游戲禮包

游戲視頻

游戲下載

游戲活動(dòng)

《K》是由樂(lè)次元開(kāi)發(fā)的一款日系動(dòng)漫RPG游戲,游戲根據(jù)同名動(dòng)漫改編而來(lái),高水準(zhǔn)的漫畫(huà)和音樂(lè)是這款游戲的

相關(guān)視頻攻略

更多

掃二維碼進(jìn)入好特網(wǎng)手機(jī)版本!

掃二維碼進(jìn)入好特網(wǎng)微信公眾號(hào)!

本站所有軟件,都由網(wǎng)友上傳,如有侵犯你的版權(quán),請(qǐng)發(fā)郵件[email protected]

湘ICP備2022002427號(hào)-10 湘公網(wǎng)安備:43070202000427號(hào)© 2013~2025 haote.com 好特網(wǎng)