SpringCloud解决feign调用token丢失问题

news/2024/11/19 13:22:41/文章来源:https://www.cnblogs.com/Naylor/p/18198025

背景讨论

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/707812.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

揭秘Linux find命令:高效查找文件的终极指南,让你秒变文件搜索达人!

Linux中find命令的详细使用指南:高效查找文件的秘诀Linux中find命令的详细使用指南:高效查找文件的秘诀 在Linux系统中,find命令是一个强大的工具,它允许你在文件系统中搜索特定的文件或目录。无论你是系统管理员还是普通用户,掌握find命令都能极大地提升你在Linux环境下的…

PVE导入VMware虚拟机

1、在VMware中导出需要迁移的虚拟机,导出为 OVF 2、将导出的虚拟机上传到PVE中,共三个文件 3、命令导入到PVE虚拟机中,等待导入完成qm importovf 103 Ubuntu Server 20.04.ovf disk1 --format qcow2 #103为新建的虚拟机id,不要和现有的重复,disk1为PVE中的磁盘路…

我开源了一款高颜值云端一体的项目。欢迎体验!!!

项目介绍 旅拍路书:旅行爱好者的专属记录伙伴,基于uniCloud + vue3的全栈项目,包括用户登录,更新个人信息,富文本编辑,分类管理以及AI助手等功能的高颜值项目。 预览 h5端扫码预览: 小程序扫码预览: 微信小程序提交审核未通过(你的小程序涉及用户自行生成内容(文字、…

如何快速找出文件夹里的全部带有符号纯符号的文件

参考之前发的文章:《如何快速找出文件夹里的全部带有中文&纯中文的文件》 只需要根据自己的需求,把下面相关的设置调整好即可

PVE学生自用记录

PVE记录 这篇博客主要记录自己大二阶段配置和使用PVE的过程。 什么是PVE 说到PVE,大家可能会想到Playsers Vs Environment,但是这里肯定不是指的游戏中的模式了,而是一个操作系统。它的全称为:Proxmox VE,是一个运行虚拟机和容器的平台。基于 Debian Linux 完全开源。最大…

提升团队生产力:2024年必知的一体化协同办公平台

本文将介绍11款主流一体化协同办公平台,包括Worktile、PingCode、Microsoft Teams、钉钉、Google Workspace等。本文介绍的主流一体化协同办公平台有:Worktile、PingCode、Microsoft Teams、钉钉、Google Workspace、Jive、Avaya、Bitrix24、Asana、ClickUp、飞书。在现代工作…

vue将页面生成图片 vue生成海报

Hello,大家好,我是小编鹏仔,近几年开发项目中,经常用到将网页生成海报图片功能,每次使用都要去查找复制一下的,比较麻烦还费时间,还是自己整理到自己的博客方便,那么本次鹏仔就给大家整理一下vue使用html2canvas插件将网页生成图片吧!如上图所示功能是开发了一个电子签…

jmeter每5分钟发送一次请求

在线程组下设置一个定时器,时间设置为300000MS 这样 这个线程组下的请求就是每隔5分钟执行一次 这样每隔5分钟发送一次每天进步一点点 分享快乐

CSS动画-数字轮盘滚动效果实现(组件封装,快速使用)

效果图:原理分析:这玩意就和垂直方向的轮播图差不多,只是把轮播的图换成数字 主要实现:父组件:父组件接收一个curNum属性,这个属性代表当前需要显示的数字。它将这个数字传递给子组件AnimateNum,以便子组件可以正确地显示和滚动数字。子组件一 (AnimateNum):这个组件接…

【rust】《Rust整合OpenCV ( Ubuntu.22.04系统 ) 》

前言 Ubuntu22.04的环境搭建、rust安装配置、VSCode安装等参照另一篇博文:https://www.cnblogs.com/-CO-/p/18197715 环境搭建对应项 ## 虚拟机版本 VMware 17 ## 系统版本 Ubuntu 22.04.4 ## Rust版本 Rust 1.78.0 ## Rust依赖Opencv版本 Opencv-rust …

吐槽visdom

必须大吐特吐!!!槽点一:使用前,必须得通过python3 -m visdom.server启动visdom前端服务器 槽点二:服务器不联网好像没法启动(待证实) 槽点三:访问,必须通过启动后的指定端口访问(http://localhost:8097/),要先启动后访问 ⭐见vscode中的PORTS槽点四:程序一旦发生意…

Metabase 安装和使用教程

Metabase 是一款开源的数据分析和商业智能工具,允许企业用户在几分钟内搭建起一个功能完善的数据探索和数据分析平台,不需要编写复杂的 SQL 查询语句或者使用专业的数据可视化工具,就可以轻松地探索数据、创建图表、构建仪表盘,从而洞察业务趋势,回答关键问题。Metabase 还…