SpringBoot 自定义Filter 提前返回 CORS 错误 处理前后端分离跨域配置无效问题解析

前言

浏览器有跨域限制,非同源策略 (协议、主机名或端口不同) 被视为跨域请求,解决跨域有跨域资源共享(CORS)、反向代理和 JSONP的方式。本篇通过 SpringBoot 的资源共享配置 (CORS) 来解决前后端分离项目的跨域,以及从原理上去解决跨域配置不生效的问题。

准备工作

https://gitee.com/youlaiorg/vue3-element-admin) 默认通过 vite + proxy 前端反向代理解决跨域,如果想关闭方向代理只需修改 baseURL 即可:

// request.ts
const service = axios.create({//baseURL: import.meta.env.VITE_APP_BASE_API,  // 前端反向代理解决跨域的配置baseURL: "http://localhost:8989", // 后端通过配置CORS解决跨域的配置, http://localhost:8989 是后端接口地址timeout: 50000,headers: { 'Content-Type': 'application/json;charset=utf-8' }
});

配置 CORS 允许跨域

一般情况在项目添加以下配置即可解决浏览器跨域限制。

/*** CORS 资源共享配置** @author haoxr* @date 2022/10/24*/
@Configuration
public class CorsConfig {@Beanpublic CorsFilter corsFilter() {CorsConfiguration corsConfiguration = new CorsConfiguration();//1.允许任何来源corsConfiguration.setAllowedOriginPatterns(Collections.singletonList("*"));//2.允许任何请求头corsConfiguration.addAllowedHeader(CorsConfiguration.ALL);//3.允许任何方法corsConfiguration.addAllowedMethod(CorsConfiguration.ALL);//4.允许凭证corsConfiguration.setAllowCredentials(true);UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", corsConfiguration);return new CorsFilter(source);}
}

CORS 允许跨域原理

CorsFilter 读取 CorsConfig 配置通过 DefaultCorsProcessor 给 response 响应头添加 Access-Control-Allow-* 以允许跨域请求能够被成功处理。

响应头参数作用
Access-Control-Allow-Origin允许访问的源地址
Access-Control-Allow-Methods允许访问的请求方法
Access-Control-Allow-Headers允许访问的请求头
Access-Control-Allow-Credentials是否允许发送 Cookie 等身份凭证
Access-Control-Max-Age缓存预检请求的时间

核心是 DefaultCorsProcessor# handleInternal 方法

CORS 配置失效原理分析

但。。。有的项目按照如上配置允许跨域请求成功了,但有些项目却不生效?

其实就是一个结论:有中断响应的过滤器在 CorsFilter 之前执行了,也就无法执行到 CorsFilter,自然 CorsConfiguration 中的配置形同虚设。

常见的场景:项目中使用了 Spring Security 安全框架导致 CORS 跨域配置失效。

接下来就 Spring Security 导致 CORS 配置失效展开分析。

在 ApplicationFilterChain#internalDoFilter 添加断点,然后通过改造后 (移除反向代理) 的 发出跨域请求。

在这里插入图片描述

可以看出 SpringSecurityFilterChain 是先于 CorsFilter 执行的(重点), 如果是跨域请求浏览器会在正式请求前发出一次预检请求 (OPTIONS),判断服务器是否允许跨域。

跨域请求没到达 CorsFilter 过滤器就先被 Spring Security 的过滤器给拦截了,要知道预检 OPTIONS 请求是不带 token 的,所以响应 401 未认证的错误。预检请求失败导致后面的请求响应会被浏览器拦截。

在这里插入图片描述

CORS 配置失效解决方案

根据配置失效原理分析,有两个解决方案:

  • 解决方案一: 配置 CorsFilter 优先于 SpringSecurityFilter 执行;

  • 解决方案二: 放行预检 OPTIONS 请求 + 基础 CORS 配置。

解决方案一 (推荐)

配置 CorsFilter 优先于 SpringSecurityFilter 执行

Spring Security 过滤器是通过 SecurityFilterAutoConfiguration 的 DelegatingFilterProxyRegistrationBean 注册到 servletContext 上下文,其中过滤器的顺序属性 Order 读取的 是 SecurityProperties 的默认配置也就是 -100;


在这里插入图片描述

SpringBoot 可以通过 FilterRegistrationBean 来对 Filter 自定义注册(排序), 设置 Order 小于 SpringSecurity 的 -100 即可。完整配置如下:

/*** CORS资源共享配置** @author haoxr* @date 2023/4/17*/
@Configuration
public class CorsConfig {@Beanpublic FilterRegistrationBean filterRegistrationBean() {CorsConfiguration corsConfiguration = new CorsConfiguration();//1.允许任何来源corsConfiguration.setAllowedOriginPatterns(Collections.singletonList("*"));//2.允许任何请求头corsConfiguration.addAllowedHeader(CorsConfiguration.ALL);//3.允许任何方法corsConfiguration.addAllowedMethod(CorsConfiguration.ALL);//4.允许凭证corsConfiguration.setAllowCredentials(true);UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", corsConfiguration);CorsFilter corsFilter = new CorsFilter(source);FilterRegistrationBean<CorsFilter> filterRegistrationBean=new FilterRegistrationBean<>(corsFilter);filterRegistrationBean.setOrder(-101);  // 小于 SpringSecurity Filter的 Order(-100) 即可return filterRegistrationBean;}
}

可以看到不同源的跨域请求能够成功响应。

在这里插入图片描述

解决方案二

放行预检 OPTIONS 请求 + 基础 CORS 配置

SecurityConfig 放行 OPTIONS 预检请求配置 SecurityConfig 配置源码

@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http ...// 走 Spring Security 过滤器链的放行配置.requestMatchers(HttpMethod.OPTIONS,"/**").permitAll() // 放行预检请求.anyRequest().authenticated();return http.build();}@Beanpublic WebSecurityCustomizer webSecurityCustomizer() {// 不走过滤器链的放行配置return (web) -> web.ignoring().requestMatchers(HttpMethod.OPTIONS,"/**") // 放行预检请求}

或者

		  //放行 options 的请求if ("OPTIONS".equals(request.getMethod())) {filterChain.doFilter(request, servletResponse);}

基础的跨域共享配置

@Configuration
public class CorsConfig {@Beanpublic CorsFilter corsFilter() {CorsConfiguration corsConfiguration = new CorsConfiguration();//1.允许任何来源corsConfiguration.setAllowedOriginPatterns(Collections.singletonList("*"));//2.允许任何请求头corsConfiguration.addAllowedHeader(CorsConfiguration.ALL);//3.允许任何方法corsConfiguration.addAllowedMethod(CorsConfiguration.ALL);//4.允许凭证corsConfiguration.setAllowCredentials(true);UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", corsConfiguration);return new CorsFilter(source);}}

另外有自定义过滤器 (例如:VerifyCodeFilter)通过 response.getWriter().print() 响应给浏览器也是不走后面的 CorsFilter 过滤器,所以需要设置响应头

// ResponseUtils# writeErrMsg
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setHeader("Access-Control-Allow-Origin","*");
response.getWriter().print(JSONUtil.toJsonStr(Result.failed(resultCode)));

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

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

相关文章

负载均衡流程

1、负载均衡流程图 2、触发负载均衡函数trigger_load_balance void trigger_load_balance(struct rq *rq) { /* Dont need to rebalance while attached to NULL domain */ if (unlikely(on_null_domain(rq)))//当前调度队列中的调度域是空的则返回 return; i…

【嵌入式学习】C++QT-Day1-C++基础

思维导图&&笔记 见我的博客&#xff1a;https://lingjun.life/wiki/EmbeddedNote/19Cpp 作业&#xff1a; 提示并输入一个字符串&#xff0c;统计该字符中大写、小写字母个数、数字个数、空格个数以及其他字符个数 要求使用C风格字符串完成 #include <iostream&…

[MRCTF2020]Ez_bypass1

代码审计&#xff0c;要求gg和id的MD5值相等而gg和id的值不等或类型不等 相同MD5值的不同字符串_md5相同的不同字符串-CSDN博客 不过这道题好像只能用数组 下一步是passwd不能是纯数字&#xff0c;但是下一个判断又要passwd等于1234567 这里通过passwd1234567a实现绕过 原…

3D点云数据的标定,从搭建环境到点云标定方法及过程,只要有一台Windows笔记本,让你学会点云标定

ptscloudpre: 点云标定准备&#xff1a; 说明&#xff1a; 如下介绍适用windows系统的电脑。apple笔记本同理&#xff0c;但是需要安装MAC版本的anaconda。网址&#xff1a;Free Download | Anaconda可下载对应MAC版本的Anaconda的安装包建议下载2022年或2021年的安装包安装。…

qml中访问控件内部的子项

如何访问Repeater类型内部的子项、Row等布局类型内部的子项以及ListView内部的子项等。。。 1、测试代码 import QtQuick 2.0 import QtQuick.Controls 2.12 import QtQuick.Window 2.12 import QtQuick.Layouts 1.3 import QtQml 2.12Window {id: windowobjectName: "m…

基于Python Django的大数据招聘数据分析系统,包括数据大屏和后台管理

基于Python Django的大数据招聘数据分析系统是一个综合利用大数据技术和数据可视化技术的招聘行业解决方案。该系统旨在帮助企业和招聘机构更好地理解和分析招聘市场的趋势和变化&#xff0c;从而提高招聘效率和质量。 首先&#xff0c;该系统提供了一个强大的后台管理功能&am…

Docker容器引擎(3)

目录 一.Docker 镜像的创建 1&#xff0e;基于现有镜像创建 2&#xff0e;基于本地模板创建 3.基于Dockerfile创建&#xff1a; Dockerfile 操作常用的指令&#xff1a; ADD 和 COPY 的区别&#xff1f; CMD 和 ENTRYPOINT 的区别&#xff1f; 容器启动命令的优先级 如…

前端vue集成echarts图形报表样例

文章目录 &#x1f412;个人主页&#x1f3c5;Vue项目常用组件模板仓库&#x1f4d6;前言&#xff1a;&#x1f415;1.在项目终端下载echarts依赖包&#x1f3e8;2.在main.js中导入echarts资源包并使用&#x1f380;3.在.vue文件中直接使用echarts&#xff0c;下面是一个样例&a…

[设计模式Java实现附plantuml源码~创建型] 产品族的创建——抽象工厂模式

前言&#xff1a; 为什么之前写过Golang 版的设计模式&#xff0c;还在重新写Java 版&#xff1f; 答&#xff1a;因为对于我而言&#xff0c;当然也希望对正在学习的大伙有帮助。Java作为一门纯面向对象的语言&#xff0c;更适合用于学习设计模式。 为什么类图要附上uml 因为很…

C语言-算法-线性dp

[USACO1.5] [IOI1994]数字三角形 Number Triangles 题目描述 观察下面的数字金字塔。 写一个程序来查找从最高点到底部任意处结束的路径&#xff0c;使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。 在上面的样例中&#xff0c;从 7 → 3 → 8 →…

yolov8上使用gpu教程

yolov8上使用gpu教程 安装Cuda和Cudnnyolov8上使用gpu 安装Cuda和Cudnn 1.查看支持的cuda版本&#xff0c;并去官网下载。 nvidia-smi2.网址&#xff1a;https://developer.nvidia.com/cuda-toolkit-archive 3.安装细节 安装的前提基础是&#xff0c;有vs的C环境。我电脑有…

开始学习Vue2(组件的生命周期和数据共享)

一、组件的生命周期 1. 生命周期 & 生命周期函数 生命周期&#xff08;Life Cycle&#xff09;是指一个组件从创建 -> 运行 -> 销毁的整个阶段&#xff0c;强调的是一个时间段。 生命周期函数&#xff1a;是由 vue 框架提供的内置函数&#xff0c;会伴随着 组件…

必知的量化交易基础

量化交易核心概念 量化思想 量化交易 量化交易是指以先进的数学模型代替人为的主观判断&#xff0c;利用计算机技术从庞大的历史数据中海选能带来超额收益的多种“大概率”事件以制定策略&#xff0c;极大地减少了投资者情绪波动的影响&#xff0c;避免在市场极度狂热或悲观的…

对Git更深入了解与学习

对Git更深入了解与学习 0. 前言0.1 工作区与暂存区 1. git remote update origin2. git push origin --delete 分支名 删除远端分支3. git remote4. git fetch5. git status5.1 git status 直观理解5.2 暂存与暂存取消 &#xff08;git restore&#xff09;5.3 push之后 6. git…

SpringBootAdmin邮件通知

在上一篇中我们学习到了 Admin Service Clustering 分布式缓存配置 &#xff0c;这一篇我们来学习&#xff0c;客户端离线&#xff0c;出现故障的时候&#xff0c;我们这么能及时知道呢&#xff0c;发现邮件通知也许是最简单的方式了&#xff01; 邮件通知 邮件通知将作为使用…

Linux文本编辑器-vi/vim

一.vi/vim编辑器介绍 vi\vim是visual interface的简称, 是Linux中最经典的文本编辑器 同图形化界面中的 文本编辑器一样&#xff0c;vi是命令行下对文本文件进行编辑的绝佳选择。 vim 是 vi 的加强版本&#xff0c;兼容 vi 的所有指令&#xff0c;不仅能编辑文本&#xff0c;而…

哈希的基本概念(开散列和闭散列)(附代码)

哈希 哈希概念哈希冲突哈希函数常见的哈希函数 哈希冲突的解决闭散列开散列 哈希概念 传统的查找函数&#xff0c;搜索的效率取决于比较的次数。而hash算法&#xff1a;在理想情况下&#xff0c;可以不经过任何比较&#xff0c;一次就能得到要搜索的结果。 存储结构&#xff1…

面向社交网络语言隐写分析

论文&#xff1a;Linguistic Steganalysis Toward Social Network 发表在&#xff1a;IEEE Transactions on Information Forensics & Security是网络与信息安全领域的国际两大顶级期刊之一&#xff0c;中国计算机学会&#xff08;CCF&#xff09;推荐的A类期刊&#xff0c…

​用技术的视角,去看一台家用 MPV 该有的水准

「MPV」一个在 2023 年之前都属于「小众」车型的品类。 但从 2023 年初开始&#xff0c;MPV 却变成了新能源&#xff0c;特别是高端新能源品牌必争的细分产品。 从岚图推梦想家开始&#xff0c;到腾势 D9&#xff0c;再到极氪 009&#xff0c;最后到魏牌高山&#xff0c;标志…

【开源】基于JAVA语言的智慧社区业务综合平台

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 业务类型模块2.2 基础业务模块2.3 预约业务模块2.4 反馈管理模块2.5 社区新闻模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 业务类型表3.2.2 基础业务表3.2.3 预约业务表3.2.4 反馈表3.2.5 社区新闻表 四、系统展…