服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - Feign调用服务时丢失Cookie和Header信息的解决方案

Feign调用服务时丢失Cookie和Header信息的解决方案

2022-09-21 11:57迷雾总会解 Java教程

这篇文章主要介绍了Feign调用服务时丢失Cookie和Header信息的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

Feign调用服务丢失Cookie和Header信息

今天在使用Feign调用其他微服务的接口时,发现了一个问题:因为我的项目采用了无状态登录,token信息是存放在cookie中的,所以调用接口时,因为cookie中没有token信息,我的请求被拦截器拦截了。 

参考几篇文章,靠谱的解决方法是:将cookie信息放到请求头中,再进行调用接口时,拦截器中可以对请求头进行解析,获取cookie信息

服务调用方

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package top.codekiller.manager.upload.config;
import feign.RequestInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
/**
 * @author codekiller
 * @date 2020/5/26 14:22
 *
 *   自定义的请求头处理类,处理服务发送时的请求头;
 *   将服务接收到的请求头中的uniqueId和token字段取出来,并设置到新的请求头里面去转发给下游服务
 *   比如A服务收到一个请求,请求头里面包含uniqueId和token字段,A处理时会使用Feign客户端调用B服务
 *   那么uniqueId和token这两个字段就会添加到请求头中一并发给B服务;
 */
@Configuration
@Slf4j
public class FeignHeaderConfiguration {
    @Bean
    public RequestInterceptor requestInterceptor() {
        return requestTemplate -> {
            ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attrs != null) {
                HttpServletRequest request = attrs.getRequest();
                // 如果在Cookie内通过如下方式取
                Cookie[] cookies = request.getCookies();
                if (cookies != null && cookies.length > 0) {
                    for (Cookie cookie : cookies) {
                        requestTemplate.header(cookie.getName(), cookie.getValue());
                        System.out.println("信息"+cookie.getName()+cookie.getValue());
                    }
                } else {
                    log.warn("FeignHeadConfiguration", "获取Cookie失败!");
                }
                
                // 如果放在header内通过如下方式取
                Enumeration<String> headerNames = request.getHeaderNames();
                if (headerNames != null) {
                    while (headerNames.hasMoreElements()) {
                        String name = headerNames.nextElement();
                        String value = request.getHeader(name);
                        /**
                         * 遍历请求头里面的属性字段,将jsessionid添加到新的请求头中转发到下游服务
                         * */
                        if ("jsessionid".equalsIgnoreCase(name)) {
                            log.debug("添加自定义请求头key:" + name + ",value:" + value);
                            requestTemplate.header(name, value);
                        } else {
                            log.debug("FeignHeadConfiguration", "非自定义请求头key:" + name + ",value:" + value + "不需要添加!");
                        }
                    }
                } else {
                    log.warn("FeignHeadConfiguration", "获取请求头失败!");
                }
            }
        };
    }
}

服务接受方

?
1
2
3
4
5
6
7
8
9
10
11
12
//有些请求时从通过feign进行请求的,这一部分请求时不包含cookie信息的,因此我们要从请求头中获取
            Enumeration<String> headerNames = request.getHeaderNames();
            if (headerNames != null) {
                while (headerNames.hasMoreElements()) {
                    String name = headerNames.nextElement();
                    String value = request.getHeader(name);
                    System.out.println("header的信息"+name+"::::"+value);
                    if(name.equalsIgnoreCase("MC_TOKEN")){  //注意这里变成了小写
                        token=value;
                    }
                }
            }

运行的时候,我发现请求还是被拦截了,看了下打印信息,发现我的MC_TOKEN变成了小写,所以在字符串进行比较的时候要忽略大小写。

Feign调用服务时丢失Cookie和Header信息的解决方案

以下为扩展,仅仅记录一下

这样仍然有个问题:

在开启熔断器之后,方法里的attrs是null,因为熔断器默认的隔离策略是thread,也就是线程隔离,实际上接收到的对象和这个在发送给B不是一个线程,怎么办?

有一个办法,修改隔离策略hystrix.command.default.execution.isolation.strategy=SEMAPHORE,改为信号量的隔离模式,但是不推荐,因为thread是默认的,而且要命的是信号量模式,熔断器不生效,比如设置了熔断时间。

另一个办法:重写Feign的隔离策略

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import com.netflix.hystrix.HystrixThreadPoolKey;
import com.netflix.hystrix.HystrixThreadPoolProperties;
import com.netflix.hystrix.strategy.HystrixPlugins;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariable;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle;
import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier;
import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook;
import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher;
import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy;
import com.netflix.hystrix.strategy.properties.HystrixProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
 
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
 
/**
 * 自定义Feign的隔离策略;
 * 在转发Feign的请求头的时候,如果开启了Hystrix,Hystrix的默认隔离策略是Thread(线程隔离策略),因此转发拦截器内是无法获取到请求的请求头信息的,可以修改默认隔离策略为信号量模式:hystrix.command.default.execution.isolation.strategy=SEMAPHORE,这样的话转发线程和请求线程实际上是一个线程,这并不是最好的解决方法,信号量模式也不是官方最为推荐的隔离策略;另一个解决方法就是自定义Hystrix的隔离策略,思路是将现有的并发策略作为新并发策略的成员变量,在新并发策略中,返回现有并发策略的线程池、Queue;将策略加到Spring容器即可;
 *
 */
@Component
public class FeignHystrixConcurrencyStrategyIntellif extends HystrixConcurrencyStrategy {
 
    private static final Logger log = LoggerFactory.getLogger(FeignHystrixConcurrencyStrategyIntellif.class);
    private HystrixConcurrencyStrategy delegate;
 
    public FeignHystrixConcurrencyStrategyIntellif() {
        try {
            this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy();
            if (this.delegate instanceof FeignHystrixConcurrencyStrategyIntellif) {
                // 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 ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
                                            HystrixThreadPoolProperties threadPoolProperties) {
        return this.delegate.getThreadPool(threadPoolKey, threadPoolProperties);
    }
 
    @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();
            }
        }
    }
}

然后使用默认的熔断器隔离策略,也可以在拦截器内获取到上游服务的请求头信息了;

Feign调用存在的问题

① feign远程调用丢失请求头

问题描述:

当远程调用其他服务时,设置了拦截器判断用户是否登录,但是结果是即使用户登录了,也会显示用户没登录,原因在于远程调用时,发送的请求是一个新的情求,请求中并不存在cookie,而原始请求中是携带cookie的。

Feign调用服务时丢失Cookie和Header信息的解决方案

解决方案如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Configuration
public class MallFeignConfig {
    @Bean("requestInterceptor")
    public RequestInterceptor requestInterceptor() {
        RequestInterceptor requestInterceptor = template -> {
            //1、使用RequestContextHolder拿到刚进来的请求数据
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (requestAttributes != null) {
                //老请求
                HttpServletRequest request = requestAttributes.getRequest();
                if (request != null) {
                    //2、同步请求头的数据(主要是cookie)
                    //把老请求的cookie值放到新请求上来,进行一个同步
                    String cookie = request.getHeader("Cookie");
                    template.header("Cookie", cookie);
                }
            }
        };
        return requestInterceptor;
    }
}

② 异步调用Feign丢失上下文问题

问题描述:

由于feign请求拦截器为新的request设置请求头底层是使用ThreadLocal保存刚进来的请求,所以在异步情况下,其他线程并不能获取到主线程的ThreadLocal,所以也拿不到请求。

解决:

先获取主线程的requestAttributes,再分别向其他线程中设置

?
1
2
3
4
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
CompletableFuture.runAsync(() ->{
   RequestContextHolder.setRequestAttributes(requestAttributes);
});

以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。

原文链接:https://blog.csdn.net/qq_44766883/article/details/106358839

延伸 · 阅读

精彩推荐
  • Java教程Ubuntu16.04安装部署solr7的图文详细教程

    Ubuntu16.04安装部署solr7的图文详细教程

    这篇文章主要为大家详细介绍了Ubuntu16.04安装部署solr7的图文详细教程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    稻草人的信仰4062021-05-21
  • Java教程mybatis批量新增、删除、查询和修改方式

    mybatis批量新增、删除、查询和修改方式

    这篇文章主要介绍了mybatis批量新增、删除、查询和修改方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...

    xuforeverlove6492022-01-24
  • Java教程自定义feignClient的常见坑及解决

    自定义feignClient的常见坑及解决

    这篇文章主要介绍了自定义feignClient的常见坑及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...

    君莫笑_080811052022-02-24
  • Java教程java组件SmartUpload和FileUpload实现文件上传功能

    java组件SmartUpload和FileUpload实现文件上传功能

    这篇文章主要为大家详细介绍了java组件SmartUpload和FileUpload实现文件上传功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    bug诗10532021-02-05
  • Java教程Java Mybatis架构设计深入了解

    Java Mybatis架构设计深入了解

    在本篇文章里小编给大家整理的是一篇关于Java Mybatis架构设计详解内容,对此有兴趣的朋友们可以参考下,希望能够给你带来帮助...

    女友在高考10402022-03-10
  • Java教程Mybatis-Plus 通用CRUD的详细操作

    Mybatis-Plus 通用CRUD的详细操作

    这篇文章主要介绍了Mybatis-Plus 通用CRUD的详细操作,包括插入操作,更新操作及删除操作等,针对每种操作通过实例代码给大家介绍的非常详细,需要的朋友...

    律二萌萌哒11192022-01-05
  • Java教程有关tomcat内存溢出的完美解决方法

    有关tomcat内存溢出的完美解决方法

    下面小编就为大家带来一篇有关tomcat内存溢出的完美解决方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧 ...

    jingxian3902020-05-06
  • Java教程java必学必会之GUI编程

    java必学必会之GUI编程

    这篇文章主要为大家详细介绍了java GUI编程,对于GUI编程小编也不是很了解,通过这篇文章和大家一起学习GUI编程,感兴趣的小伙伴们可以参考一下 ...

    孤傲苍狼4992020-03-07