SpringCloud解决feign调用token丢失问题

springcloud,feign,token · 浏览次数 : 13

小编点评

**BizFeignRequestInterceptor** 类在 Spring Boot 中用于处理 Feign 请求时存储和获取请求属性。 **主要功能:** 1. 在 **preHandle** 方法中,从 **RequestAttributeContext** 中获取请求属性。 2. 如果属性为空,则从请求属性中获取透传逻辑不变相关示例代码。 3. 将获取到的属性存储在 **RequestAttributeContext** 中。 4. 在 **apply** 方法中,从 **RequestAttributeContext** 中获取属性,并将其设置到 `RequestAttributes` 中。 5. 如果属性为空,则从 `RequestAttributeContext` 中获取属性,并打印调试信息。 **关键代码:** ```java @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); if (null != attributes) { ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) attributes; String token = servletRequestAttributes.getRequest().getHeader("token"); requestTemplate.header("token", token); } else { RequestAttributes requestAttributes = RequestAttributeContext.getAttribute(); if (null != requestAttributes) { RequestContextHolder.setRequestAttributes(requestAttributes); } else { log.debug("requestAttributes is null"); } } return true; } ``` **注意:** 1. `RequestAttributeContext` 是一个线程本地变量,需要在线程安全的情况下访问。 2. `RequestAttributes` 是一个抽象类,需要在具体场景中实现。

正文

背景讨论

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



import com.nghsmart.ar.context.RequestAttributeContext;
import com.nghsmart.common.core.utils.ServletUtils;
import com.nghsmart.common.core.utils.StringUtils;
import com.nghsmart.common.core.utils.ip.IpUtils;
import com.nghsmart.common.security.constant.FeignRequestHeader;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.AbstractRequestAttributes;
import org.springframework.web.context.request.FacesRequestAttributes;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;


 
@Slf4j
@Order(1)
@Component
public class BizFeignRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        if (null! = attributes) {
            ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) attributes;
            String token = servletRequestAttributes.getRequest().getHeader("token");
            requestTemplate.header("token",token);
        }
    }
}



token异步线程传递

上述添加BizFeignRequestInterceptor只能解决同步调用环境下的token传递问题,当是异步线程环境下就GG了。

通过在主线程中主动将RequestAttribute传递到子线程中可以解决一部分异步线程中token传递的问题,示例代码如下:


RequestContextHolder.setRequestAttributes(RequestContextHolder.getRequestAttributes(), true);


但是这种方式有弊端,当主线程先于子线程结束的时候,子线程将获取不到RequestAttribute,原因是Tomcat会在http请求结束的时候清空数据。

我们可以创建一个InheritableThreadLocal用来保存RequestAttribute,这样就可以完美解决问题了。

实现思路为:

  • 创建一个 RequestAttributeContext,其中维护一个InheritableThreadLocal对象,用来存RequestAttributes

  • 创建一个RequestAttributeInterceptor,实现HandlerInterceptor, WebMvcConfigurer接口,用来在请求开始前把 RequestAttributes 存放到 RequestAttributeContext 中

  • 修改 BizFeignRequestInterceptor ,当无法获取到 RequestAttributes  的时候,就从 RequestAttributeContext 中获取

  • 透传逻辑不变

相关示例代码如下:

RequestAttributeContext



import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.context.request.RequestAttributes;



@Slf4j
public class RequestAttributeContext {

    private static final ThreadLocal<RequestAttributes> context = new InheritableThreadLocal<>();

    public static void setAttribute(RequestAttributes attributes) {
        if (null == attributes) {
            log.debug("RequestAttributes is null");
        }
        context.set(attributes);
    }

    public static RequestAttributes getAttribute() {
        return context.get();
    }

    public static void removeAttribute() {
        context.remove();
    }

}

RequestAttributeInterceptor


import com.alibaba.fastjson.JSON;
import com.nghsmart.ar.context.RequestAttributeContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


 
@Slf4j
@Configuration
public class RequestAttributeInterceptor implements HandlerInterceptor, WebMvcConfigurer {

    /**
     * 重写 WebMvcConfigurer 的 addInterceptors,将 RequestAttributeInterceptor 添加到拦截器列表
     *
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(this).addPathPatterns("/**").excludePathPatterns("/swagger-resources/**", "/v2/api-docs/**");
    }
    /**
     * 重写 HandlerInterceptor 的 preHandle,在请求开始处理前,将 RequestAttribute 存入 RequestAttributeContext
     *
     * @param request  current HTTP request
     * @param response current HTTP response
     * @param handler  chosen handler to execute, for type and/or instance evaluation
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        RequestAttributeContext.setAttribute(requestAttributes);
        return true;
    }
  
}


BizFeignRequestInterceptor


import com.nghsmart.ar.context.RequestAttributeContext;
import com.nghsmart.common.core.utils.ServletUtils;
import com.nghsmart.common.core.utils.StringUtils;
import com.nghsmart.common.core.utils.ip.IpUtils;
import com.nghsmart.common.security.constant.FeignRequestHeader;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.AbstractRequestAttributes;
import org.springframework.web.context.request.FacesRequestAttributes;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;


@Slf4j
@Order(1)
@Component
public class BizFeignRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        if (null! = attributes) {
            ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) attributes;
            String token = servletRequestAttributes.getRequest().getHeader("token");
            requestTemplate.header("token",token);
        }else {
            RequestAttributes requestAttributes = RequestAttributeContext.getAttribute();
            if (null != requestAttributes) {
                RequestContextHolder.setRequestAttributes(requestAttributes);
            } else {
                log.debug("requestAttributes is null");
            }
            ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
            String token = servletRequestAttributes.getRequest().getHeader("token");
            requestTemplate.header("token",token);
        }
    }
}


引用

https://zhuanlan.zhihu.com/p/545508501

与SpringCloud解决feign调用token丢失问题相似的内容:

SpringCloud解决feign调用token丢失问题

背景讨论 feign请求 在微服务环境中,完成一个http请求,经常需要调用其他好几个服务才可以完成其功能,这种情况非常普遍,无法避免。那么就需要服务之间的通过feignClient发起请求,获取需要的 资源。 认证和鉴权 一般而言,微服务项目部署环境中,各个微服务都是运行在内网环境,网关服务负责请

[转帖]鹅厂微服务发现与治理巨作PolarisMesh实践-上

文章目录 概述定义核心功能组件和生态特色亮点解决哪些问题官方性能数据 架构原理资源模型服务治理基本原理服务注册服务发现 安装部署架构集群安装 SpringCloud应用接入版本兼容创建演示父项目提供者微服务示例消费者微服务示例动态配置示例 概述 定义 PolarisMesh 官网地址 https:/

聊聊Spring Cloud Gateway

Spring Cloud Gateway是基于Spring Boot 2.0、Spring WebFlux和Project Reactor等技术开发的网关,它不仅提供了统一的路由请求的方式,还基于过滤链的方式提供了网关最基本的功能;解决了Spring Cloud Zuul的性能问题。

SaaS化开源项目之HouseKeeper云上部署实践

摘要:华为云DTSE技术专家从源码构建、应用部署到系统调测,详细解读云原生SaaS应用构建的全过程。 本文分享自华为云社区《HouseKeeper云上部署实践》,作者:华为云DTSE。 HouseKeeper是华为云开发者团队基于SaaS项目技术支持实践,采用微服务架构(SpringCloud),结

[转帖]springcloud nacos配置

配置文件中的nacos配置,discovery和config配置项 版本: 2.3.2.RELEASE Hoxton.SR9

SpringCloud-ZipKin搭建保姆级教程

服务链路追踪 一、服务追踪说明 微服务架构是通过业务来划分服务的,使⽤REST调⽤。对外暴露的⼀个接⼝,可能需要 很多个服务协同才能完成这个接⼝功能,如果链路上任何⼀个服务出现问题或者⽹络超 时,都会形成导致接⼝调⽤失败。 随着业务的不断扩张,服务之间互相调⽤会越来越复杂,它们之间的调⽤关系也许如下

SpringCloud-Config配置中心搭建保姆级教程

一、分布式配置中⼼ 在使⽤微服务架构开发的项⽬中,每个服务都有⾃⼰的配置⽂件(application.yml),如果将每个服务的配置⽂件直接写在对应的服务中,存在以下问题: 1. 服务开发完成之后,需要打包部署,配置⽂件也会打包在jar⽂件中,不便于项⽬部署之后的配置修改(在源码中修改——重新打包—

SpringCloud-Gateway搭建保姆级教程

一、网关介绍 1、什么是网关? 使⽤服务⽹关作为接⼝服务的统⼀代理,前端通过⽹关完成服务的统⼀调⽤ 2、⽹关可以⼲什么? 路由:接⼝服务的统⼀代理,实现前端对接⼝服务的统⼀访问 过滤:对⽤户请求进⾏拦截、过滤(⽤户鉴权)、监控 限流:限制⽤户的访问流量 3、常⽤的⽹关 Nginx Spring Cl

SpringCloud-Hystrix服务熔断与降级工作原理&源码

在生活中,如果电路的负载过高,保险箱会自动跳闸,以保护家里的各种电器,这就是熔断器的一个活生生例子。在Hystrix中也存在这样一个熔断器,当所依赖的服务不稳定时,能够自动熔断,并提供有损服务,保护服务的稳定性。在运行过程中,Hystrix会根据接口的执行状态(成功、失败、超时和拒绝),收集并统计这些数据,根据这些信息来实时决策是否进行熔断。

SpringCloud搭建保姆级教程

一、搭建服务注册与发现中⼼ 使⽤Spring Cloud Netflix 中的 Eureka 搭建服务注册与发现中⼼ 1、创建SpringBoot应用添加依赖 1、spring web 2、eureka server 2、配置服务注册与发现中⼼ ## 设置服务注册与发现中⼼的端⼝ server: p