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

预检请求(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 🚗🚗🚗

相关推荐
(⊙o⊙)~哦21 分钟前
JavaScript substring() 方法
前端
无心使然云中漫步44 分钟前
GIS OGC之WMTS地图服务,通过Capabilities XML描述文档,获取matrixIds,origin,计算resolutions
前端·javascript
Bug缔造者1 小时前
Element-ui el-table 全局表格排序
前端·javascript·vue.js
xnian_1 小时前
解决ruoyi-vue-pro-master框架引入报错,启动报错问题
前端·javascript·vue.js
罗政2 小时前
[附源码]超简洁个人博客网站搭建+SpringBoot+Vue前后端分离
vue.js·spring boot·后端
Karoku0662 小时前
【网站架构部署与优化】web服务与http协议
linux·运维·服务器·数据库·http·架构
麒麟而非淇淋2 小时前
AJAX 入门 day1
前端·javascript·ajax
2401_858120532 小时前
深入理解MATLAB中的事件处理机制
前端·javascript·matlab
阿树梢2 小时前
【Vue】VueRouter路由
前端·javascript·vue.js
拾光师3 小时前
spring获取当前request
java·后端·spring