spring cloud 问题记录(十六) 使用Feign跨服调用时header请求头中的信息丢失

        写这篇帖子呢其实是因为昨天(2019年7月4号)我同事(大牛)遇到的一个BUG,我也就跟着学习了下。

        情况呢很简单,我们项目的权限验证使用的keycloak,而验证体系继承在了gateway,获取用户信息的时候。而用户的部分信息是直接通过gateway获取之后,使用header的方式传输给服务的。而同事在使用A服务调用B服务的时候,发现从request里面获取不到我们自己指定的请求头信息了,其实也就是说在服务调用的时候request的请求头丢失了。而使用的就是Feignclient的调用方式。

       好了,问题发现了,当然就得解决咯,其实这个问题很多人都遇到过。往上的解决方案也一大把,我也就梳理总结下:

第一步,做一个请求拦截,把header信息都放到RequestTemplate 

/**
 *  功能描述:
 * @author 朱维
 * @date 2019年7月4日
 * @time 下午9:14:52
 * @Path: com.xuebaclass.sato.conf.FeignConfiguration.java
 */
@Component
public class FeignConfiguration implements RequestInterceptor{
    private final Logger logger = LoggerFactory.getLogger(getClass());
    
    @Override
    public void apply(RequestTemplate template) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        Enumeration<String> headerNames = request.getHeaderNames();
        if (headerNames != null) {
            while (headerNames.hasMoreElements()) {
                String name = headerNames.nextElement();
                String values = request.getHeader(name);
                template.header(name, values);

            }
            logger.info("feign interceptor header:{}",template);
        }
    }
}

第二步:Feignclient调用的时候指定configuration


@FeignClient(name = "crm-course", fallback = AutoRenewHystrix.class,configuration=FeignConfiguration.class)
public interface AutoRenewService {
    /**
     * 续排课时
     *
     * @param studentId
     * @param totalNum
     * @return
     */
    @PostMapping("/autoRnnewForOrder")
    public String autoRenewForOrder(@RequestBody Map<String, String> params);
}

第三步:指定Feign的隔离策略为:SEMAPHORE

hystrix: 
  command: 
    default: 
      execution: 
        timeout: 
          enabled: true 
        isolation: 
          thread: 
            timeoutInMilliseconds: 600000
          strategy: SEMAPHORE

注意当Feign的隔离策略为THREAD时,由于当使用该隔离策略时,是没办法拿到 ThreadLocal 中的值的,但是RequestContextHolder 源码中,使用了两个ThreadLocal,因此当使用该隔离策略时是没有办法通过RequestContextHolder获取到request对象的,这时如果你还坚持使用THREAD这个隔离策略就需要自定义策略(重写THREAD隔离策略),其他的配置内容一样,代码如下:

/**
 * 自定义并发策略
 * 将现有的并发策略作为新并发策略的成员变量
 * 在新并发策略中,返回现有并发策略的线程池、Queue
 *
 * hystrix.command.default.execution.isolation.strategy=THREAD   Hystrix的默认隔离策略(官方推荐,当使用该隔离策略时,是没办法拿到 ThreadLocal 中的值的,但是
 *                                                               RequestContextHolder 源码中,使用了两个ThreadLocal)
 * hystrix.command.default.execution.isolation.strategy=SEMAPHORE (将隔离策略改为SEMAPHORE 也可以解决这个问题,但是官方并不推荐这个策略,因为这个策略对网络资源消耗比较大)
 *
 * 主要是解决当 Hystrix的默认隔离策略是THREAD时,不能通过RequestContextHolder获取到request对象的问题
 *
 * Create By yxl on 2018/5/22
 */
@Component
public class FeignConfig extends HystrixConcurrencyStrategy {
    private static final Logger log = LoggerFactory.getLogger(FeignConfig.class);
    private HystrixConcurrencyStrategy delegate;

    public FeignConfig() {
        try {
            this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy();
            if (this.delegate instanceof FeignConfig) {
                // 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();
            }
        }
    }
}

 

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
如果您想让Spring Security拦截除`FeignClientInterceptor`接口拦截器添加Spring Security认证信息以外的Feign调用,可以考虑使用`RequestInterceptor`接口实现类来实现这个需求。`RequestInterceptor`是Feign提供的一个请求拦截器,它可以在每次Feign请求发送前做一些处理,例如添加Header、添加参数等等。 具体实现步骤如下: 1. 创建一个`RequestInterceptor`接口的实现类,例如`FeignAuthInterceptor`。 2. 在`FeignAuthInterceptor`注入`AuthenticationManager`和`JwtTokenUtil`,用于获取Token和进行Token校验。 3. 在`FeignAuthInterceptor`的`apply`方法获取当前请求的URL,并根据URL判断是否需要进行Spring Security认证。 4. 如果需要进行Spring Security认证,则在`FeignAuthInterceptor`调用`JwtTokenUtil`获取Token,并在请求Header添加Token信息。 5. 在Feign客户端,通过`@RequestLine`注解或者`@RequestMapping`注解的`value`属性指定请求URL,然后在Feign客户端的`configuration`属性指定`FeignAuthInterceptor`。 下面是一个简单的示例: ```java @Component public class FeignAuthInterceptor implements RequestInterceptor { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtTokenUtil jwtTokenUtil; @Override public void apply(RequestTemplate requestTemplate) { String requestUrl = requestTemplate.url(); // 判断是否需要进行Spring Security认证 if (needAuth(requestUrl)) { // 获取Token并添加到请求Header String token = getToken(); requestTemplate.header("Authorization", "Bearer " + token); } } private boolean needAuth(String requestUrl) { // 根据请求URL判断是否需要进行Spring Security认证 // ... } private String getToken() { // 获取Token // ... } } ``` 在Feign客户端配置`FeignAuthInterceptor`: ```java @FeignClient(name = "my-service", configuration = FeignConfig.class) public interface MyFeignClient { @RequestMapping(value = "/api/xxx", method = RequestMethod.GET) String xxx(); } @Configuration public class FeignConfig { @Autowired private FeignAuthInterceptor feignAuthInterceptor; @Bean public RequestInterceptor requestInterceptor() { return feignAuthInterceptor; } } ``` 这样,当Feign客户端调用`/api/xxx`接口,`FeignAuthInterceptor`会拦截请求并添加Token信息请求Header,从而实现了Spring Security的拦截。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值