背景讨论

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丢失问题的更多相关文章

  1. spring cloud微服务快速教程之(十四)spring cloud feign使用okhttp3--以及feign调用参数丢失的说明

    0-前言 spring cloud feign 默认使用httpclient,需要okhttp3的可以进行切换 当然,其实两者性能目前差别不大,差别较大的是很早之前的版本,所以,喜欢哪个自己选择: 1 ...

  2. SpringCloud使用Feign调用其他客户端带参数的接口,传入参数为null或报错status 405 reading IndexService#del(Integer);

    SpringCloud使用Feign调用其他客户端带参数的接口,传入参数为null或报错status 405 reading IndexService#del(Integer); 第一种方法: 如果你 ...

  3. SpringCloud:Feign调用接口不稳定问题以及如何设置超时

    1. Feign调用接口不稳定报错 Caused by: java.net.SocketException: Software caused connection abort: recv failed ...

  4. SpringCloud 中 Feign 调用使用总结

    最近做微服务架构的项目,在用 feign 来进行服务间的调用.在互调的过程中,难免出现问题,根据错误总结了一下,主要是请求方式的错误和接参数的错误造成的.在此进行一下总结记录. 以下通过分为三种情况说 ...

  5. 关于 Spring Security OAuth2 中 Feign 调用 Token 问题

    微服务体系中,避免不了服务之间链式调用,一般使用 Feign ,由于使用 Spring Security OAuth2 全局做了安全认证,简单的一种实现方式就是在服务提供方获得 Token 再次通过 ...

  6. SpringCloud使用Feign调用服务时,@FeignClient注解无法使用

    关于解决这个问题的理论根源传送门:https://blog.csdn.net/alinyua/article/details/80070890我在这里只提供解决方案 0. 结论和解决方案 Spring ...

  7. 使用springcloud的feign调用服务时出现的错误:关于实体转换成json错误的介绍

    http://blog.csdn.net/java_huashan/article/details/46428971 原因:实体中没有添加无参的构造函数 fastjson的解释: http://www ...

  8. 解决SpringCloud使用Feign跨服调用时header请求头中的信息丢失

    在使用SpringCloud进行Feign跨服调用时header请求头中的信息会丢失,是因为Feign是不会带上当前请求的Cookie信息和头信息的,这个时候就需要重写请求拦截. 1.需要重写Requ ...

  9. SpringCloud系列——Feign 服务调用

    前言 前面我们已经实现了服务的注册与发现(请戳:SpringCloud系列——Eureka 服务注册与发现),并且在注册中心注册了一个服务myspringboot,本文记录多个服务之间使用Feign调 ...

  10. Feign 调用丢失Header的解决方案

    问题 在 Spring Cloud 中 微服务之间的调用会用到Feign,但是在默认情况下,Feign 调用远程服务存在Header请求头丢失问题. 解决方案 首先需要写一个 Feign请求拦截器,通 ...

随机推荐

  1. Aspose.Cells使用总结大全

    引用:https://blog.csdn.net/u011555996/article/details/79000270 使用到 Aspose.Cells 插件,整理一下. 一:新建解决方案,目录如下 ...

  2. Mac 修改版本号

    修改版本号 在安装某些软件(XCode)的过程中, 系统会提示版本低,需要升级到高版本, 而很多人不想升级而需要安装这些软件, 此时只需将版本号修改成软件安装要求的版本号就可以了. 由于SystemV ...

  3. c# 历史版本特性

    版本 .NET Framework版本 Visual Studio版本 发布日期 特性 C# 1.0 .NET Framework 1.0 Visual Studio .NET 2002 2002.1 ...

  4. Python阿里云消息推送调用API

    很多公司测试APP推送时候,应该也是很头疼:推送环境:测试.正式,稍不注意就把测试的push到正式上,导致所有用户都收到 例子很多: 其实阿里.极光都有推送Api,直接调用API就ok,特别是有的公司 ...

  5. Web前端 -- 利用Babel来将ES6转化为ES5代码

    一.简介 Babel用来将ES6代码转为ES5代码. 二.安装 安装命令行转码工具 Babel提供babel-cli工具,用于命令行转码.它的安装命令如下: npm install --global ...

  6. SpringCloud做的微服务项目--外卖订餐系统

    本项目用到的组件技术可以参考我上一篇博客,来学习. 项目需求: 客户端:针对普通用户,用户登录,用户退出,菜品订购,我的订单 后台管理系统:针对管理员,管理员登录,管理员退出,添加菜品,查询菜品,修改 ...

  7. Flink Standalone集群部署

    Flink Standalone模式部署集群是最简单的一种部署方式,不依赖于其他的组件,另外还支持YARN/Mesos/Docker等模式下的部署,这里使用的flink版本为最新的稳定版1.9.1版本 ...

  8. byte[]类型与datetime日期转换

    在C#中,Timestamp通常表示为一个长整型(long)变量.这是因为它表示自1970年1月1日00:00:00 UTC以来的毫秒数.然而,在某些情况下,例如在处理数据库中的Timestamp时, ...

  9. 力扣1132(MySQL)-报告的记录Ⅱ(中等)

    题目: 编写一段 SQL 来查找:在被报告为垃圾广告的帖子中,被移除的帖子的每日平均占比,四舍五入到小数点后 2 位. Actions 表: Removals 表: Result 表: 2019-07 ...

  10. 龙蜥正式开源 SysOM:百万级实战经验打造!一站式运维管理平台 | 龙蜥技术

    ​简介:SysOM集监控.告警.诊断.修复.安全能力于一体的操作系统运维平台. ​ 文/系统运维 SIG 如果你被突如其来的 OOPS 和满屏奇怪的函数弄得满头问号?机器内存明明很大,却申请不出来内存 ...