前言

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

准备工作

使用前后端分离开源项目 youlai-boot + vue3-element-admin 做跨域请求测试 。

其中 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 { @Bean
public 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 添加断点,然后通过改造后(移除反向代理)的 vue3-element-admin 发出跨域请求。

可以看出 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 { @Bean
public 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 配置源码

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

基础的跨域共享配置

@Configuration
public class CorsConfig { @Bean
public 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)));

前/后端源码

完整项目源码地址如下,如果有相关问题可以通过项目 关于我们 添加交流群。

Gitee Github
前端 vue3-element-admin vue3-element-admin
后端 youlai-boot youlai-boot

SpringBoot 配置CORS处理前后端分离跨域配置无效问题解析的更多相关文章

  1. nginx-springboot-vue前后端分离跨域配置

    nginx-springboot-vue前后端分离跨域配置 引言 接着上篇--简单的springboot-vue前后端分离登录Session拦截的demo,其中跨域是通过springboot后端全局设 ...

  2. Django前后端分离跨域请求问题

    一.问题背景 之前使用django+vue进行前后端分离碰到跨域请求问题,跨域(域名或者端口不同)请求问题的本质是由于浏览器的同源策略导致的,当请求的响应不是处于同一个域名和端口下,浏览器不会接受响应 ...

  3. nginx反向代理实现前后端分离&跨域问题

    1.代理和跨域 1.1 正向代理 1)用户希望代理服务器帮助其和要访问服务器之间实现通信,需要: a.用户IP报文的目的IP=代理服务器IP: b.用户报文端口号=代理服务器监听端口号: c.HTTP ...

  4. 前后端分离跨域 关于前后端分离开发环境下的跨域访问问题(angular proxy=>nginx )

    前后端分离后遇到了跨域访问的问题: angular1中使用proxy很麻烦,最后还是失败结束:最后总结3种方法如下: 本人使用的第一种方法,只是开发环境下使用很方便! 1:禁掉谷歌的安全策略(Turn ...

  5. 关于.Net Core 前后端分离跨域请求时 ajax并发请求导致部分无法通过验证解决办法。

    项目中有这样一个页面.页面加载的时候会同时并发6个ajax请求去后端请求下拉框. 这样会导致每次都有1~2个“浏览器预请求”不通过. 浏览器为什么会自动发送“预请求”?请看以面连接 https://b ...

  6. spingsecurity 前后端分离跨域,ajax无用户信息

    1.自测时用的postman没有任何问题 2.和前端对接时发现登录不上,ajax Error 出错:{"readyState":0,"responseText" ...

  7. node与vue结合的前后端分离跨域问题

    第一点:node作为服务端提供数据接口,vue使用axios访问接口, 安装axios npm install axios --save 安装完成后在main.js中增加一下配置: import ax ...

  8. Beego和Vue的前后端分离跨域问题处理

    VUE封装的请求头(注意请求头,跨域要用到) 路径 utils/mereq.js import request from '@/utils/request' import qs from 'qs' e ...

  9. nginx配置反向代理解决前后端分离跨域问题

    摘自<AngularJS深度剖析与最佳实践>P132 nginx配置文件如下: server { listen ; server_name your.domain.name; locati ...

  10. springboot前后端分离跨域

    @Configurationpublic class CrossConfig implements WebMvcConfigurer { @Override public void addCorsMa ...

随机推荐

  1. Object.definedProperty和Proxy的对比

    Object.definedProperty和Proxy经常用来实现对象的劫持 Object.definedProperty是vue2.x拦截对象属性变化的方法,再结合订阅-发布者模式和观察者模式实现 ...

  2. 删除oracle

    完全卸载oracle11g步骤: 1. 开始->设置->控制面板->管理工具->服务 停止所有Oracle服务. 2. 开始->程序->Oracle - OraHo ...

  3. Manage your references to .Net assemblies Dynamics 365 for Operations VS projects

    (Dynamics 365 for Operations was previously known as the New Dynamics AX) Dynamics 365 for Operation ...

  4. Java基础学习——复利计算

    面对一个问题的时候,首先应该确定问题中存在多少参数,对每个参数进行定义.并知晓所写代码目标.思路清晰,

  5. 《Unix/Linux系统编程》第十二周学习笔记

    <Unix/Linux系统编程>第十二周学习笔记 MySQL数据库简介 MySQL是一个关系型数据库管理系统,是最流行的关系型数据库管理系统之一.在 WEB 应用方面,MySQL 是最好的 ...

  6. [Unity框架]资源管理02:热更新

    这里可以分成资源打包.资源更新下载.资源加载卸载3个部分 一.资源打包 参考链接: https://blog.uwa4d.com/archives/TechSharing_59.html https: ...

  7. week3题解

    1.寄包柜 看到题目最容易想到开二位数组 但数据量过大,因此需要map #include <bits/stdc++.h> using namespace std; map<int,m ...

  8. clion环境配置

    如果是学生:直接使用学校的邮箱,可以直接注册使用 环境配置:下载:https://sourceforge.net/projects/mingw-w64/

  9. CSS pointer-events 属性

    pointer-events 属性用于设置元素是否对鼠标事件做出反应. CSS 语法 pointer-events: auto|none; 属性值 属性值 描述 auto 默认值,设置该属性链接可以正 ...

  10. Android笔记--Activity--启停活动页面

    Activity启动 从当前页面跳转到新的页面:startActivity(new Intent(原页面.this,目标页面.class)) 而若是从当前页面返回到上一个页面,相当于关闭当前页面,使用 ...