springcloud Hystrix微服务接口之间的oauth2统一认证token传递

    技术2023-08-31  104

    在springcloud框架使用Feign搭建服务之间的接口时,启用熔断器Hystrix时,发现接口的auth统一认证生效,而导致接口调用401报错。

    为了跑通接口,尝试了很多种不同的方法

     

    一、接口校验忽略

    客户端忽略认证需要调用的接口,接口不需要通过认证就不会报错了(可方法就失去认证的功能,或获取token用户的信息)

    security.ignored=/service/deploy/**

     

    二、不想忽略接口,继续尝试

    需要传递在服务端对请求的header中补充Authorization属性值

    springcloud Fegin框架提供了一个RequestInterceptor拦截器,对Feign发起的请求进行拦截

    如代码所示,给Fegin发起的请求加上当前上下文RequestContextHolder的header信息,这样请求就有Authorization认证参数了。

    (Feign也是一个HTTP协议发送请求,相当于一个封装好的HttpClient服务,会启动一个新的request请求接口,而并非类似网关那样把原request请求转发到接口,所以header的参数就丢失了。)

    import feign.RequestInterceptor; import feign.RequestTemplate; import org.springframework.context.annotation.Configuration; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.Map; @Configuration public class FeignConfig implements RequestInterceptor { @Override public void apply(RequestTemplate template) { HttpServletRequest httpServletRequest = getHttpServletRequest(); if(httpServletRequest!=null){ Map<String, String> headers = getHeaders(httpServletRequest); // 传递所有请求头,防止部分丢失 //此处也可以只传递认证的header //requestTemplate.header("Authorization", request.getHeader("Authorization")); for (Map.Entry<String, String> entry : headers.entrySet()) { template.header(entry.getKey(), entry.getValue()); } } } private HttpServletRequest getHttpServletRequest() { try { return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); } catch (Exception e) { return null; } } /** * 获取原请求头 */ private Map<String, String> getHeaders(HttpServletRequest request) { Map<String, String> map = new LinkedHashMap<>(); Enumeration<String> enumeration = request.getHeaderNames(); if(enumeration!=null){ while (enumeration.hasMoreElements()) { String key = enumeration.nextElement(); String value = request.getHeader(key); map.put(key, value); } } return map; } }

    但是使用了RequestInterceptor 后可能还是发现 401报错哦。

    经过断点跟踪发现RequestContextHolder.getRequestAttributes()).getRequest();  get出来的Request是null的,直接跳过赋值。

    查询资料发现,当我们项目启动了熔断器时,并且默认熔断策略时,RequestContextHolder就不会生效。

     

    1、如果项目不需要熔断器,那么在配置文件添加配置。token即可传递成功

    feign.hystrix.enabled=false

     

    2、要使用熔断器,需要更改策略,不能使用默认的策略(默认为THREAD策略,同样会导致RequestContextHolder不生效)

    hystrix提供了两种策略,THREAD和SEMAPHORE。将策略改成SEMAPHORE,token即可传递成功

    hystrix.command.default.execution.isolation.strategy=SEMAPHORE

     

    但问题又双叒叕来了,官方推荐使用的是默认策略,即是THREAD策略。

    因为SEMAPHORE是信号量隔离,采用一个全局变量来控制并发量,一个请求过来全局变量+1,当加到跟配置值相等时就不再接受用户请求了。

    而THREAD线程池隔离策略,独立线程接收请求(默认的),线程池连接数超过配置值就不再接受请求。

    在推荐使用THREAD而又要token传递生效,那就需要第三种方法

    而且通过了解,是THREAD中的策略,生成一个新的ThreadLocal传递

    3、自定义策略

    模仿THREAD继承HystrixConcurrencyStrategy,也重写里面的策略内容,将旧的RequestContextHolder的RequestAttributes赋值到新的RequestContextHolder中,这样,在请求之前的拦截器RequestInterceptor就能在RequestContextHolder中get到请求的所有header值

    @Component public class RequestAttributeHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy { private static final Log log = LogFactory.getLog(RequestAttributeHystrixConcurrencyStrategy.class); private HystrixConcurrencyStrategy delegate; public RequestAttributeHystrixConcurrencyStrategy() { try { this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy(); if (this.delegate instanceof RequestAttributeHystrixConcurrencyStrategy) { // Welcome to singleton hell... return; } HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins .getInstance().getCommandExecutionHook(); HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance() .getEventNotifier(); HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance() .getMetricsPublisher(); HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance() .getPropertiesStrategy(); this.logCurrentStateOfHystrixPlugins(eventNotifier, metricsPublisher, propertiesStrategy); HystrixPlugins.reset(); HystrixPlugins.getInstance().registerConcurrencyStrategy(this); HystrixPlugins.getInstance() .registerCommandExecutionHook(commandExecutionHook); HystrixPlugins.getInstance().registerEventNotifier(eventNotifier); HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher); HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy); } catch (Exception e) { log.error("Failed to register Sleuth Hystrix Concurrency Strategy", e); } } private void logCurrentStateOfHystrixPlugins(HystrixEventNotifier eventNotifier, HystrixMetricsPublisher metricsPublisher, HystrixPropertiesStrategy propertiesStrategy) { if (log.isDebugEnabled()) { log.debug("Current Hystrix plugins configuration is [" + "concurrencyStrategy [" + this.delegate + "]," + "eventNotifier [" + eventNotifier + "]," + "metricPublisher [" + metricsPublisher + "]," + "propertiesStrategy [" + propertiesStrategy + "]," + "]"); log.debug("Registering Sleuth Hystrix Concurrency Strategy."); } } @Override public <T> Callable<T> wrapCallable(Callable<T> callable) { RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); return new WrappedCallable<>(callable, requestAttributes); } @Override public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, HystrixProperty<Integer> corePoolSize, HystrixProperty<Integer> maximumPoolSize, HystrixProperty<Integer> keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { return this.delegate.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); } @Override public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) { return this.delegate.getBlockingQueue(maxQueueSize); } @Override public <T> HystrixRequestVariable<T> getRequestVariable( HystrixRequestVariableLifecycle<T> rv) { return this.delegate.getRequestVariable(rv); } static class WrappedCallable<T> implements Callable<T> { private final Callable<T> target; private final RequestAttributes requestAttributes; public WrappedCallable(Callable<T> target, RequestAttributes requestAttributes) { this.target = target; this.requestAttributes = requestAttributes; } @Override public T call() throws Exception { try { RequestContextHolder.setRequestAttributes(requestAttributes); return target.call(); } finally { RequestContextHolder.resetRequestAttributes(); } } } }

    没有仔细查看后面的springCloud版本是否完善了该问题,毕竟我觉得springCloud与oauth2统一认证、Eureka是同一套框架的,在使用Hystrix请求在Eureka注册的接口时,应该提供token的直接传递。后面继续研究学习

    自定义策略参考学习了该文章的内容,感谢分享 http://www.itmuch.com/spring-cloud-sum/hystrix-threadlocal/

    Processed: 0.009, SQL: 12