今天这篇文章介绍一下JWT令牌如何在微服务链路中保证信息不丢失?官方称为令牌中继。
什么是令牌中继?
令牌中继通俗的讲则是让令牌在微服务链路调用中传递下去,保证各个微服务能够获取令牌中的用户信息。
以下订单的例子来说,如下图:
下单流程
客户端携带令牌请求网关,网关鉴权成功后会将令牌中的用户信息解析出来放在请求头中下发给订单服务,同样的,订单服务需要将用户信息传递给账户服务获取该用户的账户信息。
那么问题来了?如何保证网关服务->订单服务->账户服务这条链路中的用户信息传递下去是个痛点
解决方案
令牌在openFeign调用过程中是不能自动中继的,因此必须手动的将令牌信息传递下去。
注意:openFeign在开启熔断降级后内部调用开启了子线程,因此传统的方案直接在RequestInterceptor中设置是不可行的。
那么如何保证子线程也能获取请求头中的用户信息呢?
答案是:RequestContextHolder这个神器。
RequestContextHolder内部通过InheritableThreadLocal实现子线程共享信息。
在FeignCircuitBreakerInvocationHandler这个类中也是有如下一行代码:
RequestContextHolder.setRequestAttributes(requestAttributes);
正是使用RequestContextHolder将request的信息保存在其中,因此实现令牌中继只需要读取RequestContextHolder的信息即可。
详细代码如下:
/** * @author 公众号:码猿技术专栏 * 用于实现令牌信息中继 */ @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; } }
源码目录如下图: