SpringCloud办理feign调用token丢失问题

瑞星  金牌会员 | 2024-5-20 11:09:44 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 864|帖子 864|积分 2592

背景讨论

feign请求

在微服务环境中,完成一个http请求,经常必要调用其他好几个服务才可以完成其功能,这种情况非常广泛,无法避免。那么就必要服务之间的通过feignClient发起请求,获取必要的 资源
认证和鉴权

一般而言,微服务项目摆设环境中,各个微服务都是运行在内网环境,网关服务负责请求的路由,对外通过nginx暴露给请求者。
这种情况下,似乎网关这里做一个认证,就可以确保请求者是正当的,至于微服务调用微服务,反正都是本身人,而且是内网,无所谓是否验证身份了。
我有一个朋友,他们公司的项目确实就是这样做的,正经的商业项目。
讲道理,只要框架提供了这样的功能,那么就有存在的意义,但是,假如涉及权限的校验,微服务之间的feign调用就必要知道身份了,即必要做鉴权
token

无论是JWT、还是OAUTH2、还是shiro,各人比较公认的认证、鉴权方案,就是在请求头中放一堆东西,然后服务提供者通过解析这些东西完成认证和鉴权,这些东西俗称token
在feign调用中必要办理的就是token传递的问题,只有请求发起者将正确的token传递给服务提供者,服务提供者才能完成认证&鉴权,进而返回必要的资源
问题描述

在feign调用中可能会遇到如下问题:

  • 同步调用中,token丢失,这种可以通过创建一个拦截器,将token做透传来办理
  • 异步调用中,token丢失,这种就无法直接透传了,由于子线程并没有token,这种必要先将token从父线程传递到子线程,再进行透传
办理方案

token透传

编写一个拦截器,在feign请求前,将http请求携带的token传递给restTemplate。
具体实现方式为:

  • 创建一个Component实现com.nghsmart.ar.context.RequestAttributeContext中的RequestInterceptor接口
  • 重写apply方法
  • 通过RequestContextHolder对象获取到RequestAttributes
  • 通过RequestAttributes对象获取到HttpServletRequest
  • 通过HttpServletRequest对象获取到请求头
  • 在请求头中把token拿出来
  • 将token塞进restTemplate创建的http请求头中
示例代码:
BizFeignRequestInterceptor
  1. import com.nghsmart.ar.context.RequestAttributeContext;
  2. import com.nghsmart.common.core.utils.ServletUtils;
  3. import com.nghsmart.common.core.utils.StringUtils;
  4. import com.nghsmart.common.core.utils.ip.IpUtils;
  5. import com.nghsmart.common.security.constant.FeignRequestHeader;
  6. import feign.RequestInterceptor;
  7. import feign.RequestTemplate;
  8. import lombok.extern.slf4j.Slf4j;
  9. import org.springframework.core.annotation.Order;
  10. import org.springframework.stereotype.Component;
  11. import org.springframework.web.context.request.AbstractRequestAttributes;
  12. import org.springframework.web.context.request.FacesRequestAttributes;
  13. import org.springframework.web.context.request.RequestAttributes;
  14. import org.springframework.web.context.request.RequestContextHolder;
  15. import javax.servlet.http.HttpServletRequest;
  16. import java.util.Map;
  17. @Slf4j
  18. @Order(1)
  19. @Component
  20. public class BizFeignRequestInterceptor implements RequestInterceptor {
  21.     @Override
  22.     public void apply(RequestTemplate requestTemplate) {
  23.         RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
  24.         if (null! = attributes) {
  25.             ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) attributes;
  26.             String token = servletRequestAttributes.getRequest().getHeader("token");
  27.             requestTemplate.header("token",token);
  28.         }
  29.     }
  30. }
复制代码
token异步线程传递

上述添加BizFeignRequestInterceptor只能办理同步调用环境下的token传递问题,当是异步线程环境下就GG了。
通过在主线程中自动将RequestAttribute传递到子线程中可以办理一部分异步线程中token传递的问题,示例代码如下:
  1. RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true);
复制代码
但是这种方式有弊端,当主线程先于子线程结束的时候,子线程将获取不到RequestAttribute,原因是Tomcat会在http请求结束的时候清空数据。
我们可以创建一个InheritableThreadLocal用来保存RequestAttribute,这样就可以完美办理问题了。
实现思路为:

  • 创建一个 RequestAttributeContext,其中维护一个InheritableThreadLocal对象,用来存RequestAttributes
  • 创建一个RequestAttributeInterceptor,实现HandlerInterceptor, WebMvcConfigurer接口,用来在请求开始前把 RequestAttributes 存放到 RequestAttributeContext 中
  • 修改 BizFeignRequestInterceptor ,当无法获取到  RequestAttributes  的时候,就从 RequestAttributeContext 中获取
  • 透传逻辑不变
相关示例代码如下:
RequestAttributeContext
  1. import lombok.Data;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.springframework.web.context.request.RequestAttributes;
  4. @Slf4j
  5. public class RequestAttributeContext {
  6.     private static final ThreadLocal<RequestAttributes> context = new InheritableThreadLocal<>();
  7.     public static void setAttribute(RequestAttributes attributes) {
  8.         if (null == attributes) {
  9.             log.debug("RequestAttributes is null");
  10.         }
  11.         context.set(attributes);
  12.     }
  13.     public static RequestAttributes getAttribute() {
  14.         return context.get();
  15.     }
  16.     public static void removeAttribute() {
  17.         context.remove();
  18.     }
  19. }
复制代码
RequestAttributeInterceptor
  1. import com.alibaba.fastjson.JSON;
  2. import com.nghsmart.ar.context.RequestAttributeContext;
  3. import lombok.extern.slf4j.Slf4j;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.web.context.request.RequestAttributes;
  6. import org.springframework.web.context.request.RequestContextHolder;
  7. import org.springframework.web.servlet.HandlerInterceptor;
  8. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  9. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  10. import javax.servlet.http.HttpServletRequest;
  11. import javax.servlet.http.HttpServletResponse;
  12. @Slf4j
  13. @Configuration
  14. public class RequestAttributeInterceptor implements HandlerInterceptor, WebMvcConfigurer {
  15.     /**
  16.      * 重写 WebMvcConfigurer 的 addInterceptors,将 RequestAttributeInterceptor 添加到拦截器列表
  17.      *
  18.      * @param registry
  19.      */
  20.     @Override
  21.     public void addInterceptors(InterceptorRegistry registry) {
  22.         registry.addInterceptor(this).addPathPatterns("/**").excludePathPatterns("/swagger-resources/**", "/v2/api-docs/**");
  23.     }
  24.     /**
  25.      * 重写 HandlerInterceptor 的 preHandle,在请求开始处理前,将 RequestAttribute 存入 RequestAttributeContext
  26.      *
  27.      * @param request  current HTTP request
  28.      * @param response current HTTP response
  29.      * @param handler  chosen handler to execute, for type and/or instance evaluation
  30.      * @return
  31.      * @throws Exception
  32.      */
  33.     @Override
  34.     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  35.         RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
  36.         RequestAttributeContext.setAttribute(requestAttributes);
  37.         return true;
  38.     }
  39.  
  40. }
复制代码
BizFeignRequestInterceptor
  1. import com.nghsmart.ar.context.RequestAttributeContext;
  2. import com.nghsmart.common.core.utils.ServletUtils;
  3. import com.nghsmart.common.core.utils.StringUtils;
  4. import com.nghsmart.common.core.utils.ip.IpUtils;
  5. import com.nghsmart.common.security.constant.FeignRequestHeader;
  6. import feign.RequestInterceptor;
  7. import feign.RequestTemplate;
  8. import lombok.extern.slf4j.Slf4j;
  9. import org.springframework.core.annotation.Order;
  10. import org.springframework.stereotype.Component;
  11. import org.springframework.web.context.request.AbstractRequestAttributes;
  12. import org.springframework.web.context.request.FacesRequestAttributes;
  13. import org.springframework.web.context.request.RequestAttributes;
  14. import org.springframework.web.context.request.RequestContextHolder;
  15. import javax.servlet.http.HttpServletRequest;
  16. import java.util.Map;
  17. @Slf4j
  18. @Order(1)
  19. @Component
  20. public class BizFeignRequestInterceptor implements RequestInterceptor {
  21.     @Override
  22.     public void apply(RequestTemplate requestTemplate) {
  23.         RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
  24.         if (null! = attributes) {
  25.             ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) attributes;
  26.             String token = servletRequestAttributes.getRequest().getHeader("token");
  27.             requestTemplate.header("token",token);
  28.         }else {
  29.             RequestAttributes requestAttributes = RequestAttributeContext.getAttribute();
  30.             if (null != requestAttributes) {
  31.                 RequestContextHolder.setRequestAttributes(requestAttributes);
  32.             } else {
  33.                 log.debug("requestAttributes is null");
  34.             }
  35.             ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
  36.             String token = servletRequestAttributes.getRequest().getHeader("token");
  37.             requestTemplate.header("token",token);
  38.         }
  39.     }
  40. }
复制代码
引用

https://zhuanlan.zhihu.com/p/545508501
技能交流QQ群:1158377441 接待关注我的微信公众号【TechnologyRamble】,后续博文将在公众号首发:

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

瑞星

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表