⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 blog.csdn.net/Ta20220617/article/details/143473836 「baby bear 竟陵」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

跨域请求的背景和重要性

在现代 Web 开发中,跨域请求是一个常见且重要的概念。随着互联网应用的日益复杂,尤其是在涉及多个前端和后端服务的情况下,跨域问题经常会对应用的功能和用户体验造成影响。

背景

开发项目时,遇到一个需求,当时项目配套的线下商铺已由物业交付给我司运营,运营团队需要招商引资,经过公司考虑决定开发一个公众号交给运营团队,以便客户能够在线选择商铺。

公众号内嵌一个H5页面,做到用户点击平面图中单元格(每一个商铺号就是一个单元格)优先锁定商铺,由于商铺分布在多个区域,UI绘图也需要时间,再加需求紧迫,项目必须在三天内上线。

前端团队采用了 Canvas 技术,让用户能够直观地选择商铺单元格,并填写提交个人资料。开发过程中,前后端进行了接口联调,在测试环境中没有明显的问题。然而,当项目部署到微信公众号后,出现了跨域请求问题,直接是空白页面。

当时,前端因为配置代理的进度缓慢,跨域配置的解决方案转到了后端。这一问题突显了跨域请求在 Web 开发中的重要性,特别是在需要与多个服务进行交互时。

跨域请求的重要性

  • 安全性: 浏览器的同源政策旨在保护用户,防止恶意网站窃取信息。跨域请求需要经过严格的检查和配置,以确保数据传输的安全性。
  • 用户体验: 跨域请求的限制可能会导致用户在操作过程中遇到障碍,影响应用的流畅性和可用性。在我们的项目中,如果不及时解决跨域问题,将会直接影响客户体验和业务进展。
  • 业务需求: 在某些情况下,业务需求可能需要不同来源的资源交互。例如,在我们开发的微信公众号中,需要与后端服务进行数据交互,以完成用户的选择和定金缴纳等操作。
  • 快速迭代: 随着项目的推进,及时处理跨域问题是确保项目快速上线的重要环节。在短时间内解决跨域配置,能够为后续的功能扩展和业务发展打下良好的基础。

什么是跨域

跨域是指在 Web 应用中,由于浏览器的同源政策(Same-Origin Policy),不同源的网页之间进行交互时所遇到的限制。源的定义包括三个部分:协议(如 http 或 https)、域名(如 example.com)和端口(如 80 或 443)。只有当这三者都相同的时候,两个 URL 被认为是同源的。

为什么有同源政策?

通俗来说,浏览器厂商开发出来的浏览器都是有做安全限制的,当你打开某个网站时,浏览器就已经将请求标头中的origin属性改成了当前网站的域名。例如我访问bilibili,会是这样的一个origin,你在当前页面中做以下几种操作,均会出现跨域:

跨域的情形

1,http://www.bilibili.com(假设存在)

2,https://www.bilibili.com:8086(假设存在)

3,http://admin.www.bilibili.com(假设存在)

跨域原因解释

情况1跨域的原因是scheme(标识特定协议或资源类型的字符串)变了

情况2 跨域的原因是port(端口号)变了

情况3跨域的原因是host(域名,admin.www.bilibili.com是域名)变了

如何证明上述情况就是跨域?

跨不跨域框架说了算,来看看Springboot框架是如何认定为跨域的,先附上截图,然后给源码解释

处理请求相关的参数,并通过比较来判断是否跨域的源码

package org.springframework.web.cors;
public abstract class CorsUtils {
public CorsUtils() {
}

//方法名就直接体现了方法的作用,判断是否是跨域请求
public static boolean isCorsRequest(HttpServletRequest request) {
String origin = request.getHeader("Origin");
if (origin == null) {
return false;
} else {
UriComponents originUrl = UriComponentsBuilder.fromOriginHeader(origin).build();
String scheme = request.getScheme();
String host = request.getServerName();
int port = request.getServerPort();
//上面的代码是从请求体中获取协议,域名,端口的value值,拿到这些值就是为了和Origin作比较
//通过截图也能看到Origin包含了scheme,host,port,他们分别是https,www.bilibili.com,443
return !ObjectUtils.nullSafeEquals(scheme, originUrl.getScheme())
|| !ObjectUtils.nullSafeEquals(host, originUrl.getHost())
|| getPort(scheme, port) != getPort(originUrl.getScheme(), originUrl.getPort());
}
}
}

源码中不难看出来,在经过一番处理之后,会通过客户端传递的Origin中的信息和接口服务资源做协议,端口,域名的比对,只要有一处不一样那就是跨域,框架会告知浏览器跨域,具体的比对过程并不难,我已经贴出来了包名和类名,鼓励朋友们自己动手。

为什么是这样,而不是那样

既然服务器有处理请求,为什么你在浏览器上看不到响应回来的HTTP状态码,服务器应该要给客户端返回个状态码,取而代之的却是显示:此请求没有发起程序请求或者类似的其他提示,这都要归功于预检请求,也是浏览器厂商默认遵循的一个标准规范,属于 CORS(跨源资源共享)机制的一部分。

跨域提示截图

或者

预检请求

预检请求(Preflight Request)是 CORS(跨源资源共享)机制中的一个重要概念,用于在发送复杂的跨域请求之前,先向服务器发送一个 HTTP OPTIONS 请求,以确认服务器是否允许实际的请求。预检请求的目的是为了增强安全性,确保客户端在发送敏感数据时得到服务器的许可。

何时触发预检请求

预检请求通常在以下情况下触发:

复杂请求:

  • 当使用的 HTTP 方法不是简单请求中的 GET 或 POST(如 PUT、DELETE)。
  • 当请求中包含自定义头部(例如,X-Custom-Header)。
  • 当 Content-Type 的值不是简单请求允许的类型(如 application/x-www-form-urlencoded、multipart/form-data 或 text/plain)。

服务器端的 CORS 配置:

  • 只有在服务器配置了 CORS,并明确允许来自特定源的请求时,预检请求才会返回成功。

预检请求关服务器什么事情

完全不瞎说,有没有预检请求,依旧是springboot框架说了算,先附上原图,在附上部分源码

当我从知乎页面上请求我本机的服务接口时

服务器处理预检请求

首先服务器确实收到了该次请求,截图如下:

处理预检请求的截图:

OPTIONS请求就是预检请求的请求方式,这里解释不了为什么,只能回答这就是规范

处理预检请求的源码:

public static boolean isPreFlightRequest(HttpServletRequest request) {
//先判断是不是OPTIONS请求,若是,则表示是预检请求
return HttpMethod.OPTIONS.matches(request.getMethod())
//预检请求时,http请求头一定要给Origin
&& request.getHeader("Origin") != null
//预检请求时,会给定名为Access-Control-Request-Method的请求头
&& request.getHeader("Access-Control-Request-Method") != null;
}

服务器如何处理跨域呢,允许还是不允许?

允许还是不允许,完全看程序员如何设置跨域规则,跨域策略,不做深入讲解,但是教你如何避开雷区,先看看核心逻辑的截图

服务器会判断当前是否是预检请求,如果是,则会调用一个处理内部请求的方法,如图

关键点:allowOrigin为什么为null,checkOrigin方法到底做了什么比较

知识点回顾

问题到这里很清晰了,当程序执行到ObjectUtils.isEmpty(this.allowedOrigins)或者this.allowedOrigins.contains("*"),if语句的条件不成立了,因为this.allowedOrigins并不包含客户端的域名,也就是例子中的https://www.bilbili.com或者https://www.zhihu.com,我们要处理的正是allowedOrigins,

private List<String> allowedOrigins;

他是以数组的形式被持有的,有很多个API可以给这个数组初始化值,在我的代码中,只展示一种,因为我们要学的不是API,而是发现问题,拆分问题,解决问题的心法,API什么的不重要。

以上介绍了什么是跨域,跨域的情形,以及预检请求作为web浏览器的规范,以及服务器如何处理预检请求,浏览器对于未通过的预检请求会以什么形式展示给用户,接下来告诉大家如何解决这种小小的问题~

springboot解决跨域的方式非常之多,但是从最底层解决,往往能学到更多指定问题之外的知识

SpringBoot允许跨域的后端代码

@Configuration
public class CorsConfig {

@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
//config.setAllowCredentials(true); // 允许发送凭据,雷区
config.addAllowedOrigin("*"); //允许任意域名跨域访问接口
config.addAllowedHeader("*"); // 允许所有头部信息
config.addAllowedMethod("*"); // 允许所有请求方法
source.registerCorsConfiguration("/**", config); // 应用于所有路径
return new CorsFilter(source);
}
}

这段配置足已解决前端跨域问题,之前说的雷区就是允许发送凭据的代码和config.addAllowedOrigin("*");不可以一起使用,否则会报错。到这里,一切OK,前端跨域的问题已经解决~

给大家一段便捷的JS代码用来测试跨域问题,JS代码不做解释,相信看懂不成问题

模拟跨域的JS代码

var xhr = new XMLHttpRequest();
xhr.open('post', 'http://localhost:8081/admin/captcha/v1/generateCaptcha');
xhr.setRequestHeader('Content-Type', 'application/json'); // 设置请求头
xhr.setRequestHeader('authracation', 'abcdefghijklmnopqrstuvwxyz'); // 设置请求头
xhr.onload = function(e) {
var xhr = e.target;
console.log(xhr.responseText);
};

xhr.send('{}');

这段js代码,按F12,在浏览器的控制台中直接执行,支持IE和Google浏览器,亲测有效,需要根据实际的请求进行微调,不要在你自己的WEB项目或者API文档页面打开,否则无法达到测试跨域的效果,具体原因,我相信你理解了上面的知识点之后应该能明白。解决问题的代码很少,但是知识点并不少,留心处处皆学问哈

文章目录
  1. 1. 跨域请求的背景和重要性
    1. 1.0.1. 背景
    2. 1.0.2. 跨域请求的重要性
  • 2. 什么是跨域
    1. 2.0.1. 为什么有同源政策?
    2. 2.0.2. 跨域的情形
    3. 2.0.3. 跨域原因解释
  • 3. 如何证明上述情况就是跨域?
  • 4. 为什么是这样,而不是那样
  • 5. 预检请求
    1. 5.0.1. 何时触发预检请求
    2. 5.0.2. 复杂请求:
    3. 5.0.3. 服务器端的 CORS 配置:
  • 6. 预检请求关服务器什么事情
  • 7. 服务器如何处理跨域呢,允许还是不允许?
  • 8. 知识点回顾
  • 9. SpringBoot允许跨域的后端代码
  • 10. 模拟跨域的JS代码