什么是令牌中继?
令牌中继通俗的讲则是让令牌在微服务链路调用中传递下去,保证各个微服务能够获取令牌中的用户信息。
以下订单的例子来说,如下图:
客户端携带令牌请求网关,网关鉴权成功后会将令牌中的用户信息解析出来放在请求头中下发给订单服务,同样的,订单服务需要将用户信息传递给账户服务获取该用户的账户信息。
那么问题来了?如何保证网关服务->订单服务->账户服务这条链路中的用户信息传递下去是个痛点
解决方案
令牌在openFeign调用过程中是不能自动中继的,因此必须手动的将令牌信息传递下去。
注意:openFeign在开启熔断降级后内部调用开启了子线程,因此传统的方案直接在RequestInterceptor
中设置是不可行的。
那么如何保证子线程也能获取请求头中的用户信息呢?
答案是:RequestContextHolder
这个神器。RequestContextHolder
内部通过InheritableThreadLocal
实现子线程共享信息。
在FeignCircuitBreakerInvocationHandler
这个类中也是有如下一行代码:
RequestContextHolder.setRequestAttributes(requestAttributes);
正是使用RequestContextHolder
将request的信息保存在其中,因此实现令牌中继只需要读取RequestContextHolder
的信息即可。
详细代码如下:
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
//从RequestContextHolder中获取HttpServletRequest
HttpServletRequest httpServletRequest = RequestContextUtils.getRequest();
//获取RequestContextHolder中的信息
Map<String, String> headers = getHeaders(httpServletRequest);
//放入feign的RequestTemplate中
for (Map.Entry<String, String> entry : headers.entrySet()) {
template.header(entry.getKey(), entry.getValue());
}
}
/**
* 获取原请求头
*/
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;
}
}