spring多个过滤器和controller接口的代码执行顺序

多个过滤器和controller接口的代码执行顺序

研究此问题的起因

  • 在使用开源框架芋道时, 启用了api访问日志功能, 但是发现未能生效,
  • 看源码发现是通过过滤器实现的, 并使用断点测试
  • 发现在过滤器中的日志记录代码写在了 filterChain.doFilter(request, response); 后面
  • 日志记录代码调用了fegin接口, 故会进入fegin的请求拦截器
  • 拦截器处理逻辑是: 从 SecurityContextHolder中取出用户信息来设置到requestTemplate的header中,用于远程调用时的认证校验
  • 发现问题: 在fegin拦截器中却取不到SecurityContextHolder内容
  • 故进行排查

当前代码情况

1.过滤器顺序

  • 当前只关注api访问过滤器和认证过滤器,顺序如下:
  • api访问日志过滤器 => 认证过滤器

2.ApiAccessLogFilter中核心代码

在filterChain.doFilter(request, response);后执行日志插入操作

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
    // 获得开始时间
    LocalDateTime beginTime = LocalDateTime.now();
    // 提前获得参数,避免 XssFilter 过滤处理
    Map<String, String> queryString = ServletUtils.getParamMap(request);
    String requestBody = ServletUtils.isJsonRequest(request) ? ServletUtils.getBody(request) : null;

    try {
        log.debug("======{} api访问日志过滤器处理 前线程:{} =====",request.getRequestURI() ,Thread.currentThread().getName());
        // 继续过滤器
        filterChain.doFilter(request, response);
        // 正常执行,记录日志
        
        log.debug("======{} api访问日志过滤器处理 后线程:{} =====",request.getRequestURI() ,Thread.currentThread().getName());
        createApiAccessLog(request, beginTime, queryString, requestBody, null);
        log.debug("======{} api访问日志过滤器处理 后后线程:{} =====",request.getRequestURI() ,Thread.currentThread().getName());
    } catch (Exception ex) {
        // 异常执行,记录日志, 方法中调用了Fegin接口,担心篇幅太长就不帖出
        createApiAccessLog(request, beginTime, queryString, requestBody, ex);
        throw ex;
    }
}

3.认证逻辑

简单写下:
获取token => 校验token有效性 => 获取用户信息并设置到SecurityContextHolder中

4.feign拦截器

简单写下:
通过SecurityContextHolder获取用户信息包括token, 并设置到requestTemplate的header中,便于后续接口认证使用

排查结果

1.拦截器和controller代码执行顺序:

  1. api访问日志过滤器中filterChain.doFilter(request, response)之前代码
  2. => 认证过滤器中filterChain.doFilter(request, response)之前代码
  3. => controller代码执行完成
  4. => 认证过滤器中filterChain.doFilter(request, response)之后代码
  5. => api访问日志过滤器中filterChain.doFilter(request, response)之后代码

2.问题原因:

  • 在api访问日志过滤器的日志记录代码是在controller接口返回完成后执行的, 同时debug是看到SecurityContextHolder被清除了
    在这里插入图片描述
  • 翻看源码发现SecurityContextHolder.clearContext();是在chain.doFilter(holder.getRequest(), holder.getResponse());之后的代码, 故会在我添加日志的代码之前执行, 所以就取不到SecurityContextHolder
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
		throws IOException, ServletException {
	doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
}

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
		throws IOException, ServletException {
	// ensure that filter is only applied once per request
	if (request.getAttribute(FILTER_APPLIED) != null) {
		chain.doFilter(request, response);
		return;
	}
	request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
	if (this.forceEagerSessionCreation) {
		HttpSession session = request.getSession();
		if (this.logger.isDebugEnabled() && session.isNew()) {
			this.logger.debug(LogMessage.format("Created session %s eagerly", session.getId()));
		}
	}
	HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
	SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
	try {
		SecurityContextHolder.setContext(contextBeforeChainExecution);
		if (contextBeforeChainExecution.getAuthentication() == null) {
			logger.debug("Set SecurityContextHolder to empty SecurityContext");
		}
		else {
			if (this.logger.isDebugEnabled()) {
				this.logger
						.debug(LogMessage.format("Set SecurityContextHolder to %s", contextBeforeChainExecution));
			}
		}
		chain.doFilter(holder.getRequest(), holder.getResponse());
	}
	finally {
		SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
		// Crucial removal of SecurityContextHolder contents before anything else.
		// 此处删除了SecurityContextHolder
		SecurityContextHolder.clearContext();
		this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
		request.removeAttribute(FILTER_APPLIED);
		this.logger.debug("Cleared SecurityContextHolder to complete request");
	}
}
  • 同时还发现了日志记录的方法上加了@Async进行异步处理, 从而是的当前请求和日志记录不是同一个线程, 也会导致没法获取到同一个SecurityContextHolder, 故将@Async更换到了Fegin接口的实现方法上, 使Fegin拦截器和当前请求是同一个线程

总结

例如有两个过滤器, 过滤器a 和 过滤器b ,顺序为: a => b
spring多个过滤器和controller接口的代码执行顺序如下:

  1. 过滤器a 中 filterChain.doFilter 之前代码
  2. 过滤器b中 filterChain.doFilter 之前代码
  3. controller 代码执行完成
  4. 过滤器b 中 filterChain.doFilter 之后代码
  5. 过滤器a 中 filterChain.doFilter 之后代码

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

png转换jpg怎么操作?这一种方法很方便

很多平台、软件在上传使用图片的时候会对图片格式有限制。而jpg格式的图片相较于其他格式的图片兼容性更高&#xff0c;那么怎么将png格式的图片转换成jpg格式呢&#xff1f;使用在线图片格式转换器。支持上传jpg、webp、gif、png、bmp等格式的图片一键转换。具体操作步骤如下&…

基于JAVA卓越导师双选系统设计与实现

摘 要 如今的信息时代&#xff0c;对信息的共享性&#xff0c;信息的流通性有着较高要求&#xff0c;因此传统管理方式就不适合。为了让导师选择信息的管理模式进行升级&#xff0c;也为了更好的维护导师选择信息&#xff0c;卓越导师双选系统的开发运用就显得很有必要。并且通…

不同的Git仓库单独设置用户名和邮件地址

最近使用公司电脑将自己的一个私人项目推送到远程仓库&#xff0c;仓库显示的公司邮箱地址。因为设置了全局的username和usermail&#xff0c;这样就比较尴尬了。但是又不能频繁来回改用户信息&#xff0c;那么请看下面如何单独设置仓库的用户信息&#xff0c;让不同的仓库展示…

QT增加线程函数步骤流程

在使用线程的时候&#xff0c;不仅要关注线程开启的时机&#xff0c;同时还要关注线程安全退出&#xff0c;这样才能保证程序的健壮性&#xff0c;如果线程开启的较多&#xff0c;且开启关闭比较频繁&#xff0c;建议使用线程池来处理。开启线程有三种方式&#xff1a;第一种C的…

thinkphp 使用phpmailer发送邮件以及使用消息队列异步解耦发送邮件

邮箱注册配置&#xff1a; 注册163或qq邮箱&#xff0c;开启smtp服务 25端口 ssl则465端口 下载phpmailer composer 安装phpmailer composer require phpmailer/phpmailer设置配置文件 配置文件 书写代码 代码 <?php namespace app\job; use think\facade\Log; us…

白话transformer(四):整体架构介绍

transformer现在是最主流的深度学习框架&#xff0c;尤其是大模型的流程让transformer的作用更加凸显&#xff0c;他可以对话、分类、生成文本等功能&#xff0c;那么他到底是如何工作的呢。 B站视频 1、背景知识铺垫 1.1、生成式模型 相信大家在使用手机聊天的输入法时&am…

闪电网络协议设计思想剖析

1. 引言 闪电网络可能是比特币之上部署的最受期待的技术创新。闪电网络&#xff0c;为由 Joseph Poon 和 Tadge Dryja 于2015年首次提出的支付层&#xff0c;承诺支持&#xff1a; 用户之间几乎无限数量的链下交易&#xff0c;几乎免费&#xff0c;同时利用比特币提供的安全性…

基于python+vue云上水果超市的设计与实现flask-django-php-nodejs

本论文的主要内容包括&#xff1a; 第一&#xff0c;研究分析当下主流的web技术&#xff0c;结合超市日常管理方式&#xff0c;进行云上水果超市的数据库设计&#xff0c;设计云上水果超市功能&#xff0c;并对每个模块进行说明。 第二&#xff0c;陈列说明该系统实现所采用的架…

Redis数据类型 Hash Set Zset Bitmap HyperLogLog GEO

Hash 说起Hash大家其实很容易想到java中的集合类HashMap,这里其实就是一个套娃,键值对套了一层键值对他的指令也很简单 首先是设置键值对 这里就是设置两个键值对 我们可以进行获取 使用hget获取值 或者我们使用hgetall来查询所有值 hmset/hmget是批量查找查询,和上面的操作类似…

【Unity】UI九宫格

什么是九宫格&#xff1f; 顾名思义&#xff0c;九宫格就是指UI切成9个格子&#xff0c;9个格子可以任意拉伸。 1、3、7、9不拉伸。 2、8水平拉伸。 4、6垂直拉伸。 5既可以水平也可以垂直拉伸。 怎么切九宫格&#xff1f; 选中图片&#xff0c;改成Sprite模式&#xff0c;点…

本地化语音识别、视频翻译和配音工具:赋能音频和视频内容处理

随着人工智能技术的飞速发展&#xff0c;语音识别、视频翻译和配音等任务已经变得更加容易和高效。然而&#xff0c;许多现有的工具和服务仍然依赖于互联网连接&#xff0c;这可能会导致延迟、隐私问题和成本问题。为了克服这些限制&#xff0c;我们介绍了一种本地化、离线运行…

使用 Dify 和 AWS Bedrock 玩转 Anthropic Claude 3

本篇文章&#xff0c;聊聊怎么比较稳定的使用 Anthropic Claude 3&#xff0c;以及基于目前表现非常好的模型&#xff0c;来做一些有趣的 AI Native 小工具。 写在前面 在实际体验了半个多月&#xff0c;月初上线的 Anthropic Claude Pro 后&#xff0c;发现 Claude 3 系列模…

LeetCode-60题:排列序列解法二(原创)

【题目描述】 给出集合 [1,2,3,...,n]&#xff0c;其所有元素共有 n! 种排列。按大小顺序列出所有排列情况&#xff0c;并一一标记&#xff0c;当 n 3 时, 所有排列如下&#xff1a;"123" 、"132" 、"213" 、"231"、"312"、…

第八篇【传奇开心果系列】Python自动化办公库技术点案例示例:深度解读使用Python库清洗处理从PDF文件提取的文本

传奇开心果博文系列 系列博文目录Python自动化办公库技术点案例示例系列 博文目录前言一、Python清洗处理文本的常见步骤二、使用Python库去除非文本元素示例代码三、使用Python库去除格式化元素的示例代码四、使用Python库去除空白字符示例代码五、使用Python库合并段落和行示…

在任何 Mac 上恢复永久删除照片的 5 种简单方法

Mac 为业余和专业摄影师提供了很多东西&#xff0c;从令人印象深刻的硬件到广泛的照片管理和编辑应用程序。它还提供了多种恢复丢失照片的方法&#xff0c;我们在本文中介绍了其中的五种方法&#xff0c;以帮助您避免潜在的灾难性情况。 Mac 上删除的照片去了哪里&#xff1f;…

高能脉冲电阻-高能陶瓷电阻

EAK无感实芯电阻器&#xff0c;高能电阻&#xff0c;高能脉冲电阻&#xff0c;高能陶瓷电阻 产品特性&#xff1a; Ⅰ100%陶瓷实芯压铸结构,由粘土、二氧华硅、瓷粉等无机材料经高温烧结而成。 Ⅱ承受高脉冲能量 ,适应高压,超高压环境,能用于1000KV以上电路瞬间功率达到3KKW以…

【阅读笔记】Kinematic On‐the‐Fly GPS Positioning Relative to a Moving Reference

Hermann B R, Evans A G, Law C S, et al. Kinematic On‐the‐Fly GPS Positioning Relative to a Moving Reference[J]. Navigation, 1995, 42(3): 487-501. 单词解释 Antenna swap&#xff1a;天线交换 pseudokinematic&#xff1a;伪运动学 ambiguity&#xff1a;双关、歧…

Web框架开发-django模型层(多表操作)

一、创建模型 实例: 作者模型:一个作者有姓名和年龄 作者详细模型:把作者的详情放到详情表,包含生日,手机号,家庭住址等信息。作者详情模型和作者模型之间是一对一的关系(one-to-one) 出版商模型:出版商有名称,所在城市以及email。 书籍模型: 书籍有书名和出版…

Python面向对象三大特征(封装、继承、多态)

面向对象编程的三大特征&#xff1a;封装、继承和多态。 注意&#xff1a;在python面向对象编程中&#xff0c;子类对象可以传递给父类类型 一、封装 在Python中&#xff0c;封装是面向对象编程中的一种重要概念&#xff0c;它可以帮助我们实现数据隐藏、信息保护和代码复用。…

使用jscpd对比重复代码

背景 检查项目中重复的代码&#xff0c;或者代码片段 jscpd 两个文件对比 Jscpd 是一个用于检测代码复制和粘贴的工具&#xff0c;它可以比较两个文件并报告相似性的百分比。 以下是如何使用 Jscpd 来比较两个文件的示例&#xff1a; 首先&#xff0c;确保你已经安装了 Nod…