八股文:OPTIONS请求条件与浏览器安全策略CORS

1. 什么时候会发送OPTIONS请求

在 Web 开发中,OPTIONS 请求是一种 HTTP 请求方法,通常与 CORS(跨源资源共享,Cross-Origin Resource Sharing) 机制相关。它主要用于预检(preflight)请求,以检查服务器是否允许特定的跨域请求。以下是详细说明:

1.1. 发送OPTIONS请求条件

1.1.1. 跨域请求且不是简单请求

当发起的请求是跨域的(即请求的目标域名与当前页面域名不同),并且不满足"简单请求"的条件时,浏览器会先发送一个 OPTIONS 请求作为预检请求,以确认服务器是否允许该跨域操作。

简单请求的条件(必须同时满足):

  • 请求方法是以下之一:
    • GET
    • HEAD
    • POST
  • 请求头仅包含以下"安全"的头:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type(且值限于 application/x-www-form-urlencodedmultipart/form-datatext/plain
  • 不使用自定义头(如 X-Custom-Header)。
  • 不使用 ReadableStream 等复杂数据。

触发预检的例子

  • 使用 PUTDELETE 等非简单方法。
  • 设置了自定义请求头(如 AuthorizationX-API-Key)。
  • Content-Typeapplication/json

示例

php 复制代码
fetch('https://api.example.com/data', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json', // 非简单 Content-Type
    'X-Custom-Header': 'value',         // 自定义头
  },
  body: JSON.stringify({ key: 'value' }),
});

这种请求会触发一个 OPTIONS 预检请求。


1.1.2. 服务器要求预检

即使是简单请求,如果服务器在响应中明确要求客户端进行预检(例如通过特定的 CORS 配置),也可能触发 OPTIONS 请求。不过这种情况较少见,通常由服务器端的特殊策略决定。

1.1.3. 手动发送 OPTIONS 请求

开发者也可以通过代码手动发送 OPTIONS 请求(例如使用 fetchXMLHttpRequest),但这不是常见用法,通常用于测试服务器支持的 HTTP 方法或其他元信息。

sql 复制代码
fetch('https://api.example.com', { method: 'OPTIONS' });

1.2. OPTIONS 请求的作用

  • 询问服务器支持OPTIONS 请求会携带一些头部(如 Access-Control-Request-MethodAccess-Control-Request-Headers),询问服务器是否允许后续的实际请求。
  • 服务器响应 :服务器返回支持的方法、允许的头、域名等信息,通过 Access-Control-Allow-* 头部告诉浏览器是否可以继续。

示例请求

makefile 复制代码
OPTIONS /data HTTP/1.1
Host: api.example.com
Origin: http://localhost:3000
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, X-Custom-Header

示例响应

yaml 复制代码
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, X-Custom-Header

1.3. 如何避免不必要的 OPTIONS 请求?

  1. 使用简单请求
    • 如果可能,限制方法为 GET/POST/HEAD,避免自定义头,使用简单的 Content-Type
  1. 服务器端优化
    • 配置 CORS 允许常用的方法和头部,减少预检频率。
    • 使用 Access-Control-Max-Age 缓存预检结果,避免重复发送 OPTIONS
  1. 调整请求设计
    • 如果业务允许,避免跨域,直接部署到同一域名下。

1.4. 总结

  • 触发条件:跨域请求 + 非简单请求(如复杂方法或自定义头)。
  • 目的:预检服务器是否允许实际请求,确保安全性。
  • 常见场景 :现代前端(如 React、Vue)调用后端 API 时,涉及 application/json 或认证头。

如果你的项目中遇到了意外的 OPTIONS 请求,可以检查请求头和方法,或者告诉我具体场景,我可以帮你分析!

2. 什么是CORS?如何解决?

2.1. 什么是 CORS?

CORS(Cross-Origin Resource Sharing,跨源资源共享)是一种浏览器安全机制,用于限制网页中的脚本(如 JavaScript)从一个源(域名、协议、端口)访问另一个源的资源。它由浏览器强制执行,旨在防止恶意网站未经授权访问其他网站的数据。

2.1.1. 核心概念

  • 同源策略(Same-Origin Policy)
    • 浏览器的默认安全策略,只允许脚本访问与当前页面同源的资源。
    • 同源:协议(http/https)、域名(example.com)、端口(80/443)完全相同。
    • 示例:
      • http://example.comhttp://example.com/api 是同源。
      • http://example.comhttps://example.comhttp://api.example.com 不同源。
  • 跨源请求
    • 当请求的目标资源与当前页面不同源时,称为跨源请求。
    • 例如,从 http://localhost:3000 请求 https://api.example.com/data

2.1.2. CORS 如何工作

  • 当浏览器检测到跨源请求时,会根据请求类型(简单请求或非简单请求)执行不同的处理:
    1. 简单请求
      • 满足特定条件(例如使用 GETPOST,且 Content-Typeapplication/x-www-form-urlencoded 等)。
      • 浏览器直接发送请求,并在请求头中添加 Origin
      • 服务器通过 Access-Control-Allow-Origin 响应是否允许。
    1. 非简单请求(需要预检):
      • 使用复杂方法(如 PUTDELETE)或自定义头(如 Authorization)。
      • 浏览器先发送 OPTIONS 预检请求,询问服务器是否允许。
      • 服务器返回允许的方法、头等信息后,浏览器再发送实际请求。

2.1.3. 常见的 CORS 错误

  • 浏览器控制台报错:
csharp 复制代码
Access to fetch at 'https://api.example.com' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
  • 原因:服务器未返回适当的 CORS 头部,或者不允许当前源。

2.2. 如何解决 CORS 问题?

解决 CORS 问题通常需要服务器端和客户端的配合,但主要依赖服务器端配置。以下是常见的解决方案:

2.2.1. 1. 服务器端解决(推荐)

服务器需要返回正确的 CORS 头部,允许跨源访问。具体的实现取决于服务器技术栈:

  • 设置 Access-Control-Allow-Origin
    • 指定允许访问的源,或者使用 * 表示允许所有源。
    • 示例(Node.js + Express):
javascript 复制代码
const express = require('express');
const app = express();

app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'http://localhost:3000'); // 指定源
  // 或 res.header('Access-Control-Allow-Origin', '*'); // 允许所有源
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  next();
});

app.get('/data', (req, res) => {
  res.json({ message: 'Hello' });
});

app.listen(5000);
  • 处理预检请求
    • 对于非简单请求,服务器需要响应 OPTIONS 请求。
    • 示例(Express):
javascript 复制代码
app.options('*', (req, res) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.sendStatus(204); // 无内容
});
  • 使用现成中间件
    • Node.js/Express :使用 cors 中间件。
php 复制代码
const cors = require('cors');
app.use(cors()); // 默认允许所有源
// 或指定选项
app.use(cors({ origin: 'http://localhost:3000' }));
    • Spring Boot (Java)
less 复制代码
@CrossOrigin(origins = "http://localhost:3000")
@RestController
public class MyController {
    @GetMapping("/data")
    public String getData() {
        return "Hello";
    }
}
    • Nginx
bash 复制代码
location / {
    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization';
        return 204;
    }
    add_header 'Access-Control-Allow-Origin' '*';
}

2.2.2. 2. 客户端解决(临时或开发环境)

如果无法修改服务器,或者只是开发阶段,可以使用以下方法绕过 CORS 限制:

  • 代理服务器
    • 在本地开发时,通过代理将跨域请求转发到目标服务器。
    • Webpack Dev Server 示例
css 复制代码
module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: 'https://api.example.com',
        changeOrigin: true,
        pathRewrite: { '^/api': '' },
      },
    },
  },
};

请求 http://localhost:3000/api/data 会代理到 https://api.example.com/data

  • 浏览器插件
    • 使用如 "Allow CORS: Access-Control-Allow-Origin" 的 Chrome 扩展,临时禁用浏览器的 CORS 限制(仅限开发测试)。
  • JSONP(不推荐):
    • 利用 <script> 标签绕过 CORS,仅适用于 GET 请求,老技术,已被现代方案取代。

2.2.3. 3. 生产环境最佳实践

  • 避免使用 *
    • 用具体域名替换 Access-Control-Allow-Origin: *,提高安全性。
  • 支持带凭证的请求
    • 如果需要发送 Cookie 或认证头(如 Authorization),设置:
arduino 复制代码
res.header('Access-Control-Allow-Origin', 'http://localhost:3000'); // 不能用 *
res.header('Access-Control-Allow-Credentials', 'true');

客户端也需设置 withCredentials

php 复制代码
fetch('https://api.example.com', { credentials: 'include' });
  • 缓存预检请求
    • 添加 Access-Control-Max-Age 头部,避免重复发送 OPTIONS
arduino 复制代码
res.header('Access-Control-Max-Age', '86400'); // 缓存一天

2.3. 总结

  • CORS 是什么:浏览器限制跨源请求的安全机制。
  • 解决方法
    1. 服务器端 :配置 CORS 头部(如 Access-Control-Allow-Origin),推荐使用中间件。
    2. 客户端:开发时用代理,生产环境尽量避免绕过。
  • 关键点:根据请求类型(简单/非简单)和业务需求调整配置。
相关推荐
Senar3 小时前
如何判断浏览器是否开启硬件加速
前端·javascript·数据可视化
HtwHUAT3 小时前
实验四 Java图形界面与事件处理
开发语言·前端·python
利刃之灵3 小时前
01-初识前端
前端
codingandsleeping3 小时前
一个简易版无缝轮播图的实现思路
前端·javascript·css
天天扭码3 小时前
一分钟解决 | 高频面试算法题——最大子数组之和
前端·算法·面试
全宝4 小时前
🌏【cesium系列】01.vue3+vite集成Cesium
前端·gis·cesium
拉不动的猪4 小时前
简单回顾下插槽透传
前端·javascript·面试
烛阴4 小时前
Fragment Shader--一行代码让屏幕瞬间变黄
前端·webgl
爱吃鱼的锅包肉5 小时前
Flutter路由模块化管理方案
前端·javascript·flutter
风清扬雨5 小时前
Vue3具名插槽用法全解——从零到一的详细指南
前端·javascript·vue.js