浏览器在什么情况下会触发预检请求

预检请求(Preflight Request)是由浏览器发起的一种特定的 HTTP 请求,用于在实际发送跨源请求之前验证服务器是否允许该跨源请求。预检请求是 CORS(跨源资源共享)机制的一部分。当你尝试从一个域发送请求到另一个域时,浏览器需要保证安全性,因此会首先使用预检请求来询问目标域对该请求的安全限制。

什么是 CORS

CORS(跨源资源共享,Cross-Origin Resource Sharing)是一种安全功能,它允许网页上的代码安全地访问另一个域下的资源。在没有 CORS 的情况下,浏览器出于安全考虑遵循同源策略(Same-Origin Policy),即默认情况下,网页上的 JavaScript 只能从与该网页相同的域中加载和执行资源。CORS 通过添加特定的 HTTP 头,允许服务器指示任何域的代码都可访问其资源。

CORS 的工作原理

在一个 HTTP 请求中,可以分为简单请求和非简单请求,首先我们了解一下什么是简单请求。

简单请求

简单请求是指满足特定标准的请求,这些请求可以直接发出,无需进行 CORS 预检(preflight)流程。简单请求的标准主要涉及请求的方法(method)、头部(headers)和内容类型(Content-Type)。

简单请求只能使用以下三种 HTTP 方法之一:

  1. GET:用于请求数据。

  2. POST:用于发送数据到服务器。

  3. HEAD:用于获取资源的元数据,如响应头。

对于请求头,简单请求可以使用的 HTTP 头部非常有限,只包括一下几种:

  1. Accept:告诉服务器客户端能接受哪些媒体类型。例如,Accept: text/html 表示客户端可以接收 HTML 文件。

  2. Accept-Language:告诉服务器客户端能接受的语言列表。例如,Accept-Language: en-US, en;q=0.5 表示首选美国英语,但如果不可用,则接受其他类型的英语。

  3. Content-Language:告诉服务器请求体中的内容使用的语言。例如,Content-Language: de-DE 表示内容使用德国的德语。

  4. Content-Type:这是一个特殊的头部,只有以下三种值被视为简单请求:

    • application/x-www-form-urlencoded:表单默认的内容类型,提交的数据按键值对排列,值被编码在 URI 中。

    • multipart/form-data:用于 <input type="file">,即上传文件。

    • text/plain:数据以纯文本形式发送,不进行编码。

  5. Last-Event-ID:此请求头部与服务器发送事件(Server-Sent Events, SSE)相关,用于指定上一个接收的事件的 ID,使得服务端可以从断点继续发送事件。

  6. DPR (Device Pixel Ratio):此请求头部告诉服务器设备的像素比例,通常用于响应图片或其他资源的请求,以便服务器可以发送适合设备显示的资源。

  7. Downlink:此请求头部描述了用户设备的下行速度估计,以兆位每秒(Mbps)为单位。它可以帮助服务器根据用户当前的网络条件调整响应。

  8. Save-Data:如果此请求头部为 on,表明用户愿意为了节省数据而接受更低质量的资源。

  9. Viewport-Width:此请求头部表示用户设备的视窗宽度,服务器可以根据这个信息发送最适合的资源,特别是图片或布局。

  10. Width:此请求头部是在请求某个资源(如图片)时指定的,用于告诉服务器所请求图片的目标显示宽度。

除此之外,请求不能使用 XMLHttpRequest.upload 对象注册任何事件监听器和不能使用 ReadableStream 对象。

对于简单请求,浏览器直接发出请求,并在请求的 Origin 头部标明请求来源。服务器根据这个来源以及自身的 CORS 策略决定是否接受这个请求。如果接受,它需要在响应中包括 Access-Control-Allow-Origin 头部。浏览器会根据这个头部决定是否将响应暴露给发起请求的前端代码。

非简单请求

除了简单请求,剩下的都是非简单请求。非简单请求在发送实际数据之前,会使用 OPTIONS 方法先发送一个预检请求,询问服务器是否允许来自某源的请求。

预检请求的 HTTP 头中包含以下几个字段:

  1. Origin:表示请求来自哪个源。

  2. Access-Control-Request-Method:HTTP 方法。

  3. Access-Control-Request-Headers:额外的头部字段。

服务器需要响应这些预检请求,并在响应中明确哪些方法、头部和源是被允许的。如果服务器允许,那么浏览器将发送实际请求;如果服务器不允许,浏览器将拒绝发送实际请求。

我们在掘金上面删除一个沸点,并通过 network 来查看他的网络请求:

在上面的请求中,请求体和响应体都有携带了 Access-Control-Request-Method、Access-Control-Request-Headers 等 HTTP 头部信息,而在响应体中返回了 access-control-allow-origin、access-control-max-age 等 HTTP 首部字段。

如果预检成功,返回的 204 状态码,没有内容返回的同时表示成功,浏览器可以发送删除沸点的请求了。

为什么本地使用 Webpack 进行 dev 开发时,不需要服务器端配置 CORS 的情况下访问到线上接口?

在使用 Webpack 进行本地开发时,经常会遇到需要从本地应用访问线上接口的场景。即使线上服务器没有显式配置 CORS(跨源资源共享),本地开发环境依然可以成功调用这些接口。这主要得益于 Webpack 开发服务器(通常是 webpack-dev-server)提供的代理功能。

Webpack Dev Server 可以配置一个代理(Proxy),该代理能将特定的 API 请求从本地开发服务器转发到指定的线上服务器。这个过程中,代理服务器作为中间人,将请求从 localhost(或其他自定义的本地域名)转发到线上 API。

浏览器的同源策略(SOP)阻止从一个源加载的文档或脚本与另一个源的资源进行交互。但是,如果使用 Webpack Dev Server 的代理功能,请求实际上是从本地开发环境发出,经过 Webpack Dev Server 转发到目标 API。因为这个 HTTP 请求首先发到同源的本地开发服务器,浏览器只认为这是一个本地请求,并不触发 CORS 预检请求。

如何在 NodeJs 中配置 CORS

在 NestJS 中配置 CORS 非常简单,NestJS 提供了内置的支持来处理跨源资源共享(CORS)问题。这可以通过在启动 HTTP 服务时直接在 NestJS 的应用设置中配置 CORS 选项来实现。

启用默认的 CORS 配置

在最简单的场景中,你可以启用默认的 CORS 配置,这允许任何源进行跨源请求:

ts 复制代码
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableCors(); // 启用CORS,使用默认配置
  await app.listen(3000);
}
bootstrap();

在这个配置中,enableCors()方法调用时没有参数,表示接受所有默认的 CORS 设置(例如允许任何域进行跨源请求)。

使用自定义 CORS 配置

如果你需要更精细地控制 CORS 相关的配置,例如指定允许的源、方法、HTTP 头等,你可以向 enableCors()方法提供一个配置对象:

ts 复制代码
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableCors({
    origin: "https://www.baidu.com", // 只允许来自https://example.com的请求
    methods: "GET,POST", // 只允许GET和POST请求方法
    allowedHeaders: "Content-Type,Authorization", // 明确允许的HTTP头
    credentials: true, // 支持发送cookies
  });
  await app.listen(3000);
}
bootstrap();

这个配置提供了更多控制,例如只允许特定的源和 HTTP 方法。

动态确定 CORS 配置

如果你需要根据请求动态确定是否允许 CORS,例如基于请求的某些特征或数据库中的配置,你可以传递一个函数到 origin 属性:

ts 复制代码
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.enableCors({
    origin: (origin, callback) => {
      const allowedOrigins = [
        "https://www.baidu.com",
        "https://www.bilibili.com/",
      ];
      if (allowedOrigins.includes(origin)) {
        callback(null, true);
      } else {
        callback(new Error("哎哟你干嘛~"), false);
      }
    },
    methods: "GET,POST",
    allowedHeaders: "Content-Type,Authorization",
    credentials: true,
  });

  await app.listen(3000);
}
bootstrap();

这里的 origin 属性是一个函数,它接收请求的源作为参数,并通过一个回调函数确定是否允许请求。

我们还可以使用 cors 中间来配置 CORS。

总结

预检请求使用 OPTIONS 方法,其目的是检查实际请求是否安全可接受。预检请求的响应中,服务器可以指明哪些源、哪些 HTTP 方法和头信息字段是可以接受的。如果预检请求失败,主请求不会被发出。这个机制帮助提高了网站的安全性,防止了不被允许的跨源请求可能造成的问题。

最后分享两个我的两个开源项目,它们分别是:

这两个项目都会一直维护的,如果你想参与或者交流学习,可以加我微信 yunmz777 如果你也喜欢,欢迎 star 🚗🚗🚗

相关推荐
expect7g11 分钟前
Flink-Checkpoint-1.源码流程
后端·flink
天天向上102413 分钟前
Vue 配置打包后可编辑的变量
前端·javascript·vue.js
00后程序员18 分钟前
Fiddler中文版如何提升API调试效率:本地化优势与开发者实战体验汇总
后端
芬兰y29 分钟前
VUE 带有搜索功能的穿梭框(简单demo)
前端·javascript·vue.js
好果不榨汁36 分钟前
qiankun 路由选择不同模式如何书写不同的配置
前端·vue.js
小蜜蜂dry36 分钟前
Fetch 笔记
前端·javascript
拾光拾趣录37 分钟前
列表分页中的快速翻页竞态问题
前端·javascript
小old弟38 分钟前
vue3,你看setup设计详解,也是个人才
前端
Lefan42 分钟前
一文了解什么是Dart
前端·flutter·dart
用户81221993672243 分钟前
C# .Net Core零基础从入门到精通实战教程全集【190课】
后端