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实现绕过 原…

二叉树自顶向下递归和自底向上递归

二叉树自顶向下递归 自顶向下&#xff08;top-down&#xff09; 和前序遍历紧密关联&#xff08;根->左->右&#xff09;当前节点的情况依赖于其父节点的情况考虑完父节点&#xff0c;再考虑当前节点 LeetCode 104. 二叉树的最大深度 class Solution {int ans;public…

数据库学习命令总结(持续更新)

单行注释&#xff1a;以#或--进行单行注释 多行注释&#xff1a;使/* 注释内容 */进行多行注释 使用--时须在最后一个-后添加至少一个控制字符&#xff08;如空格、制表符、换行符等&#xff09;防止注释--与减法运算的混淆 1、DDL数据定义语言 1.1数据库操作 1、创建数据库…

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

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

ModuleNotFoundError No module named ‘bs4‘ 问题处理

ModuleNotFoundError: No module named ‘bs4’ 问题处理 在使用Postgres数据库时&#xff0c;因为SQL脚本中使用到了xml_killer函数&#xff0c;导致直接报错&#xff1a; org.postgresql.util.PSQLException: ERROR: ModuleNotFoundError: No module named ‘bs4’ 后来在…

Java中的Service

七. Service 1. 数据与逻辑分离 之前我们讲面向对象设计&#xff0c;都是把数据和逻辑放在一起&#xff0c;这是理想情况。 现实情况是&#xff0c;把对象分为两类&#xff0c;一类专门存数据&#xff0c;一类专门执行逻辑 存数据的就是一个 Java Bean 存逻辑的叫做 XxxSe…

CentOS 7安装Mysql+Mycat

安装MySQL yum源 yum localinstall http://repo.mysql.com//mysql57-community-release-el7-7.noarch.rpm修改源 vi /etc/yum.repos.d/mysql-community.repo [mysql-connectors-community] nameMySQL Connectors Community baseurlhttp://repo.mysql.com/yum/mysql-connectors…

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…

vue-drag-resize-rotate 拖拉拽旋转

中文在线演示地址 1.安装 npm install gausszhou/vue-drag-resize-rotate 2.使用 <template><div class"container"><vue-drag-resize-rotate:w"200":h"200":x"0":y"0":parent"true":draggab…

数据库四种隔离级别

未提交读&#xff08;脏读&#xff09;ru&#xff0c;在事务b中执行了某些操作&#xff0c;比如添加或者修改&#xff0c;这时候事务a可以读取到事务b这个事务还没提交之前的这些操作的结果&#xff0c;其结果被称为脏读。提交读 rc&#xff0c;事务b执行了一些操作并提交&…

基于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; 容器启动命令的优先级 如…

CF1362C Johnny and Another Rating Drop(二进制、复杂度考虑)

看完数据范围 n ∈ [ 1 , 1 e 18 ] n\in[1,1e18] n∈[1,1e18]就可以先猜一下要不是可以直接推公式&#xff0c;不能暴力去做&#xff0c;更不能遍历一遍&#xff0c;又看到这种2进制的题目&#xff0c;要猜是不是 l o g log log级别的复杂度。 可以依次考虑每一位 所有 i % 2 …

前端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 因为很…

Nginx反向代理配置模块详解

Nginx反向代理配置模块详解 一、前言 随着互联网的发展&#xff0c;Web 应用越来越广泛&#xff0c;随之而来的是对 Web 服务器的高并发、高可用、高性能等需求的日益增长。Nginx 作为一个高性能的 HTTP 和反向代理服务器&#xff0c;由于其出色的性能和稳定性&#xff0c;越…

C语言-算法-线性dp

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

配置ansible自动化工具

自动化运维工具 Puppet : 用ruby语言写的 Saltstack : 用python写的,是一个模块化shell(就是命令),用的agent服务连接的被控端,用于大集群,高并发 ansible : 用python写的,也是模块化shell(就是命令),部署简单,不需要启动和安装agent等服务,用的ssh连接被控端,用于小集…