最近在写一个业务,结合feign远程调用和spring security,再发送请求的时候需在请求头中加入身份信息。而另一个服务,需要这份身份信息,来获取id,不然就会报错,这里我用jwt存储身份信息。
首先写一个拦截器:
写一个拦截器,拦截下来feign的请求并添加所需要的信息。
由于这个拦截器是配置类,可以把他放置在公有的包里。
我这里的操作是:
拦截下来请求,并获取请求头,将请求头信息传入到feign的template中。
java
@Slf4j
@Configuration
public class FeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
//获取request对象
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if(requestAttributes != null){
//获取request
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
//获取请求头
String authorization = request.getHeader("Authorization");
log.info("FeignInterceptor.apply authorization:{}",authorization);
System.out.println("FeignInterceptor.apply authorization:{}"+authorization);
//将请求头信息传递到feign中
template.header("Authorization", authorization);
}
}
}
然后在对应的client里配置该类
报错:
正常来说,如果没有配置隔离策略,那么就可以正常对请求进行拦截了,但是如果feign结合了hystrix或者sentinel的线程隔离的话,那么就会获取不到。
源码剖析
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
这个方法,是从上下文中获取内容
getRequestAttributes的方法是基于线程上下文实现的
ini
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}
ThreadLocal:
arduino
private static final boolean jsfPresent =
ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<>("Request context");
可以看出,他需要从本地线程中来获取。
对比一下hystrix和sentinel
Sentinel | Hystrix | |
---|---|---|
隔离策略 | 信号量隔离(限制每个服务线程请求数) | 线程池隔离/信号量隔离(cpu性能降低) |
熔断降级策略 | 基于慢调用比例(耗时比较久的熔断)或异常比例 | 基于失败比率 |
实时指标实现 | 滑动窗口 | 滑动窗口(基于 RxJava) |
规则配置 | 支持多种数据源 | 支持多种数据源 |
扩展性 | 多个扩展点 | 插件的形式 |
基于注解的支持 | 支持 | 支持 |
限流 | 基于 QPS,支持基于调用关系的限流 | 有限的支持 |
流量整形 | 支持慢启动、匀速排队模式 | 不支持 |
系统自适应保护 | 支持 | 不支持 |
控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 不完善 |
常见框架的适配 | Servlet、Spring Cloud、Dubbo、gRPC 等 | Servlet、Spring Cloud Netflix |
yaml
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 30000 #熔断超时时间
整合spring security的时候,远程调用接口想发送请求头信息,线程隔离的策略配置的是线程隔离。
由于在进行远程调用的时候,服务收到hystrix保护,在去调用的时候会去分配一些线程让该服务去执行,然后决定是否进行降级。而分配的这些线程是隔离的,也就是说无法访问本地线程的东西,因此在获取本地线程的请求头内容时返回为空,所以我们这里改成信号量隔离。采用更好的sentinel,它默认采用的是信号量隔离
在远程调用的时候获取不到请求头内容
解决
由于综合来看sentinel更为优秀,所以这里不去更改hystrix了,直接改用sentinel
更改feign配置
yaml
feign:
sentinel:
enabled: true
# hystrix:
# enabled: true
circuitbreaker:
enabled: true
# hystrix:
# command:
# default:
# execution:
# isolation:
# thread:
# timeoutInMilliseconds: 1000000 #熔断超时时间
就可以成功获取了。