• SpringCloud解决feign调用token丢失问题


    背景讨论

    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 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. }

  • 相关阅读:
    生成树(STP)
    mysql集群使用nginx配置负载均衡
    Nacos 2.0 集群使用 Nginx 做代理
    按键灯待机2秒后灭掉
    VMware 安装macos的方法
    关于罗克韦尔跟西门子PLC之间通讯的解决方案
    使用JQ获取并渲染三级联动分类数据
    ROS Node
    qt 信号与槽机制,登录界面跳转
    QT和网络调试助手之间的UDP通信
  • 原文地址:https://blog.csdn.net/qq_38140936/article/details/139340489