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
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丢失问题的更多相关文章
- spring cloud微服务快速教程之(十四)spring cloud feign使用okhttp3--以及feign调用参数丢失的说明
0-前言 spring cloud feign 默认使用httpclient,需要okhttp3的可以进行切换 当然,其实两者性能目前差别不大,差别较大的是很早之前的版本,所以,喜欢哪个自己选择: 1 ...
- SpringCloud使用Feign调用其他客户端带参数的接口,传入参数为null或报错status 405 reading IndexService#del(Integer);
SpringCloud使用Feign调用其他客户端带参数的接口,传入参数为null或报错status 405 reading IndexService#del(Integer); 第一种方法: 如果你 ...
- SpringCloud:Feign调用接口不稳定问题以及如何设置超时
1. Feign调用接口不稳定报错 Caused by: java.net.SocketException: Software caused connection abort: recv failed ...
- SpringCloud 中 Feign 调用使用总结
最近做微服务架构的项目,在用 feign 来进行服务间的调用.在互调的过程中,难免出现问题,根据错误总结了一下,主要是请求方式的错误和接参数的错误造成的.在此进行一下总结记录. 以下通过分为三种情况说 ...
- 关于 Spring Security OAuth2 中 Feign 调用 Token 问题
微服务体系中,避免不了服务之间链式调用,一般使用 Feign ,由于使用 Spring Security OAuth2 全局做了安全认证,简单的一种实现方式就是在服务提供方获得 Token 再次通过 ...
- SpringCloud使用Feign调用服务时,@FeignClient注解无法使用
关于解决这个问题的理论根源传送门:https://blog.csdn.net/alinyua/article/details/80070890我在这里只提供解决方案 0. 结论和解决方案 Spring ...
- 使用springcloud的feign调用服务时出现的错误:关于实体转换成json错误的介绍
http://blog.csdn.net/java_huashan/article/details/46428971 原因:实体中没有添加无参的构造函数 fastjson的解释: http://www ...
- 解决SpringCloud使用Feign跨服调用时header请求头中的信息丢失
在使用SpringCloud进行Feign跨服调用时header请求头中的信息会丢失,是因为Feign是不会带上当前请求的Cookie信息和头信息的,这个时候就需要重写请求拦截. 1.需要重写Requ ...
- SpringCloud系列——Feign 服务调用
前言 前面我们已经实现了服务的注册与发现(请戳:SpringCloud系列——Eureka 服务注册与发现),并且在注册中心注册了一个服务myspringboot,本文记录多个服务之间使用Feign调 ...
- Feign 调用丢失Header的解决方案
问题 在 Spring Cloud 中 微服务之间的调用会用到Feign,但是在默认情况下,Feign 调用远程服务存在Header请求头丢失问题. 解决方案 首先需要写一个 Feign请求拦截器,通 ...
随机推荐
- C++ 异常和错误处理机制:如何使您的程序更加稳定和可靠
在C++编程中,异常处理和错误处理机制是非常重要的.它们可以帮助程序员有效地处理运行时错误和异常情况.本文将介绍C++中的异常处理和错误处理机制. 什么是异常处理? 异常处理是指在程序执行过程中发生异 ...
- Blazor OIDC 单点登录授权实例7 - Blazor hybird app 端授权
目录: OpenID 与 OAuth2 基础知识 Blazor wasm Google 登录 Blazor wasm Gitee 码云登录 Blazor OIDC 单点登录授权实例1-建立和配置IDS ...
- Mogdb - 安装报错Failed to encrypt the password for databaseError
Mogdb - 安装报错 Failed to encrypt the password for databaseError 本文出处:https://www.modb.pro/db/418363 版本 ...
- 如何采用VuePress构建文档网站
作者:倾城 博客: https://www.codingbrick.com 寄语:当你意识到面子不重要时,你才算个真正的成年人. 在建设博客的初期,我采用GitBook构建了编码专家的专栏系统.Git ...
- redis 简单整理——redis 的列表基本结构和命令[四]
前言 简单整理一下redis的列表. 正文 列表(list)类型是用来存储多个有序的字符串,如图2-18所示,a. b.c.d.e五个元素从左到右组成了一个有序的列表,列表中的每个字符串 称为元素(e ...
- 批处理 if的知识点
正文 批处理的if不同于我们在一些常规语言中的if,比如说c或者c# 或者 java等. 如果我们判断两个字符是否相等,我们使用 'a'=='a' 来判断. 但是如果是不相等却不能这样. gtr 大于 ...
- 实训篇-Html-表单练习
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- JavaWeb技术JSP连接数据库操作
"感谢您阅读本篇博客!如果您觉得本文对您有所帮助或启发,请不吝点赞和分享给更多的朋友.您的支持是我持续创作的动力,也欢迎留言交流,让我们一起探讨技术,共同成长!谢谢!" 0X01 ...
- 力扣618(MySQL)-学生地理信息报告(困难)
题目: 一所美国大学有来自亚洲.欧洲和美洲的学生,他们的地理信息存放在如下 student 表中 该表没有主键.它可能包含重复的行.该表的每一行表示学生的名字和他们来自的大陆. 一所学校有来自亚洲.欧 ...
- KubeDL HostNetwork:加速分布式训练通信效率
简介:ubeDL 为分布式训练作业带来了 HostNetwork 网络模式,支持计算节点之间通过宿主机网络相互通信以提升网络性能,同时适应 RDMA/SCC 等新型高性能数据中心架构的网络环境,此外 ...