深入探讨跨域资源共享 (CORS) 和预检请求。学习如何处理 CORS 问题,保护您的 Web 应用程序以面向全球受众。
揭秘 CORS:深入探讨 JavaScript 预检请求处理
在不断发展的 Web 开发领域,安全性至关重要。跨域资源共享 (CORS) 是一种关键的安全机制,由 Web 浏览器实现,用于限制网页向与该网页所在的域不同的域发出请求。这是一项基本的安全功能,旨在防止恶意网站访问敏感数据。本综合指南将深入研究 CORS 的复杂性,特别关注预检请求的处理。我们将探讨 CORS 的“为什么”、“是什么”和“怎么做”,提供实际示例和针对全球开发人员遇到的常见问题的解决方案。
理解同源策略
CORS 的核心是同源策略 (SOP)。该策略是一种浏览器级别的安全机制,它限制在一个源上运行的脚本访问来自不同源的资源。一个源由协议(例如 HTTP 或 HTTPS)、域(例如 example.com)和端口(例如 80 或 443)定义。如果这三个组件完全匹配,则两个 URL 具有相同的源。
例如:
https://www.example.com/app1/index.html
和https://www.example.com/app2/index.html
具有相同的源(相同的协议、域和端口)。https://www.example.com/index.html
和http://www.example.com/index.html
具有不同的源(不同的协议)。https://www.example.com/index.html
和https://api.example.com/index.html
具有不同的源(不同的子域被认为是不同的域)。https://www.example.com:8080/index.html
和https://www.example.com/index.html
具有不同的源(不同的端口)。
SOP 旨在防止恶意脚本访问另一个网站上的敏感数据,例如 cookie 或用户身份验证信息。虽然对于安全性至关重要,但 SOP 也可能具有限制性,尤其是在需要合法的跨域请求时。
什么是跨域资源共享 (CORS)?
CORS 是一种机制,允许服务器指定允许访问其资源的源(域、方案或端口)。它本质上放宽了 SOP,允许受控的跨域访问。CORS 是使用在客户端(通常是 Web 浏览器)和服务器之间交换的 HTTP 标头实现的。
当浏览器发出跨域请求(即,向与当前页面不同的源发出的请求)时,它首先检查服务器是否允许该请求。这是通过检查服务器响应中的 Access-Control-Allow-Origin
标头来完成的。如果请求的源列在该标头中(或者如果标头设置为 *
,允许所有源),则浏览器允许该请求继续进行。否则,浏览器会阻止该请求,阻止 JavaScript 代码访问响应数据。
预检请求的作用
对于某些类型的跨域请求,浏览器会启动一个预检请求。这是一个 OPTIONS
请求,在实际请求之前发送到服务器。预检请求的目的是确定服务器是否愿意接受实际请求。服务器使用有关允许的方法、标头和其他限制的信息来响应预检请求。
当跨域请求满足任何以下条件时,就会触发预检请求:
- 请求方法不是
GET
、HEAD
或POST
。 - 该请求包含自定义标头(即,除浏览器自动添加的标头之外的标头)。
Content-Type
标头设置为除application/x-www-form-urlencoded
、multipart/form-data
或text/plain
之外的任何内容。- 该请求在正文中使用
ReadableStream
对象。
例如,Content-Type
为 application/json
的 PUT
请求将触发预检请求,因为它使用与允许的方法不同的方法和可能不允许的内容类型。
为什么需要预检请求?
预检请求对于安全性至关重要,因为它们为服务器提供了在执行潜在有害的跨域请求之前拒绝它们的机会。如果没有预检请求,恶意网站可能会在未经服务器明确同意的情况下向服务器发送任意请求。预检请求允许服务器验证该请求是否可接受,并防止潜在的有害操作。
在服务器端处理预检请求
正确处理预检请求对于确保您的 Web 应用程序正常且安全地运行至关重要。服务器必须使用适当的 CORS 标头响应 OPTIONS
请求,以指示是否允许实际请求。
以下是预检响应中使用的关键 CORS 标头的细分:
Access-Control-Allow-Origin
:此标头指定允许访问资源的源。它可以设置为特定源(例如,https://www.example.com
)或设置为*
以允许所有源。但是,出于安全原因,通常不鼓励使用*
,尤其是在服务器处理敏感数据的情况下。Access-Control-Allow-Methods
:此标头指定允许用于跨域请求的 HTTP 方法(例如,GET
、POST
、PUT
、DELETE
)。Access-Control-Allow-Headers
:此标头指定允许在实际请求中使用非标准 HTTP 标头的列表。如果客户端发送自定义标头(例如X-Custom-Header
或Authorization
),则需要此标头。Access-Control-Allow-Credentials
:此标头指示实际请求是否可以包含凭据,例如 cookie 或授权标头。如果客户端代码发送凭据并且服务器应该接受它们,则必须将其设置为true
。注意:当此标头设置为 `true` 时,`Access-Control-Allow-Origin` *不能*设置为 `*`。您必须指定源。Access-Control-Max-Age
:此标头指定浏览器可以缓存预检响应的最长时间(以秒为单位)。这可以通过减少发送的预检请求数量来帮助提高性能。
示例:使用 Express 在 Node.js 中处理预检请求
以下是使用 Express 框架在 Node.js 应用程序中处理预检请求的示例:
const express = require('express');
const cors = require('cors');
const app = express();
// 允许所有源的 CORS(仅用于开发目的!)
// 在生产环境中,指定允许的源以获得更好的安全性。
app.use(cors()); //or app.use(cors({origin: 'https://www.example.com'}));
// 用于处理 OPTIONS 请求(预检)的路由
app.options('/data', cors()); // 启用单个路由的 CORS。或指定 origin: cors({origin: 'https://www.example.com'})
// 用于处理 GET 请求的路由
app.get('/data', (req, res) => {
res.json({ message: '这是跨域数据!' });
});
// 处理预检和 POST 请求的路由
app.options('/resource', cors()); // 启用 DELETE 请求的预检请求
app.delete('/resource', cors(), (req, res, next) => {
res.send('delete resource')
})
const port = 3000;
app.listen(port, () => {
console.log(`服务器正在监听端口 ${port}`);
});
在此示例中,我们使用 cors
中间件来处理 CORS 请求。 为了获得更精细的控制,CORS 可以在每个路由的基础上启用。 注意:在生产环境中,强烈建议使用 origin
选项指定允许的源,而不是允许所有源。 使用 *
允许所有源可能会使您的应用程序暴露于安全漏洞。
示例:使用 Flask 在 Python 中处理预检请求
以下是使用 Flask 框架和 flask_cors
扩展在 Python 应用程序中处理预检请求的示例:
from flask import Flask, jsonify
from flask_cors import CORS, cross_origin
app = Flask(__name__)
CORS(app) # 启用所有路由的 CORS
@app.route('/data')
@cross_origin()
def get_data():
data = {“message”: “这是跨域数据!”}
return jsonify(data)
if __name__ == '__main__':
app.run(debug=True)
这是最简单的用法。 和以前一样,可以限制源。 有关详细信息,请参阅 flask-cors 文档。
示例:使用 Spring Boot 在 Java 中处理预检请求
以下是使用 Spring Boot 在 Java 应用程序中处理预检请求的示例:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@SpringBootApplication
public class CorsApplication {
public static void main(String[] args) {
SpringApplication.run(CorsApplication.class, args);
}
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/data").allowedOrigins("http://localhost:8080");
}
};
}
}
以及相应的控制器:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DataController {
@GetMapping("/data")
public String getData() {
return "这是跨域数据!";
}
}
常见的 CORS 问题和解决方案
尽管 CORS 具有重要性,但它通常是开发人员沮丧的根源。以下是一些常见的 CORS 问题及其解决方案:
-
错误:“在请求的资源上不存在 'Access-Control-Allow-Origin' 标头。”
此错误表明服务器未在响应中返回
Access-Control-Allow-Origin
标头。要修复此问题,请确保服务器已配置为包含该标头,并且已将其设置为正确的源或*
(如果合适)。解决方案:将服务器配置为在响应中包含 `Access-Control-Allow-Origin` 标头,将其设置为请求网站的源或 `*` 以允许所有源(谨慎使用)。
-
错误:“对预检请求的响应未通过访问控制检查:在预检响应中,请求标头字段 X-Custom-Header 不允许 Access-Control-Allow-Headers。”
此错误表明服务器不允许跨域请求中的自定义标头(在本例中为
X-Custom-Header
)。要修复此问题,请确保服务器在预检响应的Access-Control-Allow-Headers
标头中包含该标头。解决方案:将自定义标头(例如,`X-Custom-Header`)添加到服务器预检响应中的 `Access-Control-Allow-Headers` 标头中。
-
错误:“凭据标志为 'true',但 'Access-Control-Allow-Origin' 标头为 '*'。”
当
Access-Control-Allow-Credentials
标头设置为true
时,Access-Control-Allow-Origin
标头必须设置为特定源,而不是*
。这是因为允许来自所有源的凭据存在安全风险。解决方案:使用凭据时,将 `Access-Control-Allow-Origin` 设置为特定源,而不是 `*`。
-
未发送预检请求。
再次检查您的 Javascript 代码是否包含 `credentials: 'include'` 属性。还要检查您的服务器是否允许 `Access-Control-Allow-Credentials: true`。
-
服务器和客户端之间的配置冲突。
仔细检查您的服务器端 CORS 配置以及客户端设置。不匹配(例如,服务器仅允许 GET 请求,但客户端发送 POST)将导致 CORS 错误。
CORS 和安全最佳实践
虽然 CORS 允许受控的跨域访问,但遵循安全最佳实践以防止漏洞至关重要:
- 在生产环境中避免在
Access-Control-Allow-Origin
标头中使用*
。 这允许所有源访问您的资源,这可能存在安全风险。相反,请指定允许的确切源。 - 仔细考虑允许哪些方法和标头。 仅允许对您的应用程序正常运行绝对必要的方法和标头。
- 实施适当的身份验证和授权机制。 CORS 不能替代身份验证和授权。确保您的 API 受到适当安全措施的保护。
- 验证和清理所有用户输入。 这有助于防止跨站点脚本 (XSS) 攻击和其他漏洞。
- 保持您的服务器端 CORS 配置为最新状态。 定期审查和更新您的 CORS 配置,以确保其符合您的应用程序的安全要求。
不同开发环境中的 CORS
CORS 问题在各种开发环境和技术中可能表现出不同的方式。以下是几种常见情况中处理 CORS 的方法:
本地开发环境
在本地开发期间,CORS 问题尤其令人讨厌。浏览器通常会阻止从您的本地开发服务器(例如 localhost:3000
)到远程 API 的请求。可以使用多种技术来缓解此问题:
- 浏览器扩展:像“Allow CORS: Access-Control-Allow-Origin”这样的扩展可以临时禁用 CORS 限制以进行测试。但是,*切勿*在生产中使用这些扩展。
- 代理服务器:配置代理服务器,将请求从您的本地开发服务器转发到远程 API。这会使浏览器认为请求是“同源”的。像
http-proxy-middleware
(适用于 Node.js)之类的工具对此很有帮助。 - 配置服务器 CORS:即使在开发期间,最好将您的 API 服务器配置为明确允许来自您的本地开发源(例如
http://localhost:3000
)的请求。这模拟了真实世界的 CORS 配置,并有助于您尽早发现问题。
无服务器环境(例如 AWS Lambda、Google Cloud Functions)
无服务器功能通常需要仔细的 CORS 配置。许多无服务器平台提供内置的 CORS 支持,但正确配置它至关重要:
- 特定于平台的设置:使用该平台的内置 CORS 配置选项。例如,AWS Lambda 允许您直接在 API 网关设置中指定允许的源、方法和标头。
- 中间件/库:为了获得更大的灵活性,您可以使用中间件或库在您的无服务器函数代码中处理 CORS。这类似于在传统服务器环境中使用的方法(例如,在 Node.js Lambda 函数中使用 `cors` 包)。
- 考虑 `OPTIONS` 方法:确保您的无服务器函数正确处理
OPTIONS
请求。这通常涉及创建一个单独的路由,该路由返回适当的 CORS 标头。
移动应用程序开发(例如,React Native、Flutter)
对于原生移动应用程序(Android、iOS),CORS 的直接关注较少,因为它们通常不会像 Web 浏览器那样强制执行同源策略。但是,如果您的移动应用程序使用 Web 视图显示 Web 内容,或者您使用 React Native 或 Flutter 等利用 JavaScript 的框架,CORS 仍然可能相关:
- Web 视图:如果您的移动应用程序使用 Web 视图显示 Web 内容,则应用与 Web 浏览器相同的 CORS 规则。将您的服务器配置为允许来自 Web 内容的源的请求。
- React Native/Flutter:这些框架使用 JavaScript 发出 API 请求。虽然原生环境可能不会直接强制执行 CORS,但底层 HTTP 客户端(例如
fetch
)在某些情况下可能仍会表现出类似 CORS 的行为。 - 原生 HTTP 客户端:当直接从原生代码发出 API 请求时(例如,使用 Android 上的 OkHttp 或 iOS 上的 URLSession),CORS 通常不是一个因素。但是,您仍然需要考虑安全最佳实践,例如适当的身份验证和授权。
CORS 配置的全局注意事项
为全球可访问的应用程序配置 CORS 时,考虑以下因素至关重要:
- 数据主权:某些地区的法规规定数据必须位于该地区内。CORS 可能会涉及跨越边界访问资源,这可能违反数据驻留法律。
- 区域安全策略:不同的国家/地区可能具有不同的网络安全法规和准则,这些法规和准则会影响如何实施和保护 CORS。
- 内容分发网络 (CDN):确保您的 CDN 已正确配置,以传递必要的 CORS 标头。配置不当的 CDN 可能会剥离 CORS 标头,从而导致意外错误。
- 负载均衡器和代理:验证您基础架构中的任何负载均衡器或反向代理是否正确处理预检请求和传递 CORS 标头。
- 多语言支持:考虑 CORS 如何与您应用程序的国际化 (i18n) 和本地化 (l10n) 策略交互。确保 CORS 策略在您应用程序的不同语言版本中保持一致。
CORS 的测试和调试
有效地测试和调试 CORS 至关重要。以下是一些技术:
- 浏览器开发人员工具:浏览器的开发人员控制台是您的第一站。“网络”选项卡将显示预检请求和响应,从而揭示 CORS 标头是否存在以及是否正确配置。
- `curl` 命令行工具:使用 `curl -v -X OPTIONS
` 手动发送预检请求并检查服务器的响应标头。 - 在线 CORS 检查器:许多在线工具可以帮助验证您的 CORS 配置。只需搜索“CORS 检查器”即可。
- 单元和集成测试:编写自动化测试以验证您的 CORS 配置是否按预期工作。这些测试应涵盖成功的跨域请求以及 CORS 应阻止访问的场景。
- 日志记录和监视:实施日志记录以跟踪与 CORS 相关的事件,例如预检请求和被阻止的请求。监视您的日志以查找可疑活动或配置错误。
结论
跨域资源共享 (CORS) 是一种重要的安全机制,它允许对 Web 资源进行受控的跨域访问。了解 CORS 的工作原理,尤其是预检请求,对于构建安全可靠的 Web 应用程序至关重要。通过遵循本指南中概述的最佳实践,您可以有效地处理 CORS 问题并保护您的应用程序免受潜在的漏洞。请记住始终优先考虑安全性,并仔细考虑您的 CORS 配置的影响。
随着 Web 开发的不断发展,CORS 将继续成为 Web 安全的关键方面。随时了解最新的 CORS 最佳实践和技术对于构建安全且全球可访问的 Web 应用程序至关重要。