在浏览器中,内容可以加载各种资源,如 JavaScript 文件、图片、音频、视频等。但如果没有安全策略的限制,将会面临多种安全威胁,例如跨站脚本攻击(XSS)、SQL 注入、跨站点请求伪造(CSRF)等。为了保障用户隐私和数据安全,浏览器实施了最核心的安全策略:同源策略。
什么是同源策略?
同源策略是一个重要的安全策略,用于限制一个源的文档或脚本如何与另一个源的资源进行交互。如果两个 URL 的协议、主机和端口都相同,则称这两个 URL 同源。
同源策略主要表现在以下三个方面,构成了现代Web安全的基石:
- DOM 访问限制:阻止网页脚本(如 JavaScript)访问其他源的 DOM,防止恶意网站窃取敏感信息。
- Web 数据限制:限制了从其他源加载 Web 数据(例如通过
XMLHttpRequest 或 Fetch API)。默认情况下,这些请求只能发送到与当前网页同源的地址,以防止 CSRF 等攻击。
- 网络通信限制:浏览器会阻止从一个源发出的请求获取来自其他源的响应,确保只有受信任的源能与服务器通信。
出于上述安全原因,浏览器默认限制从脚本内发起的跨源 HTTP 请求。这意味着,使用 XMLHttpRequest 和 Fetch API 通常只能访问同域资源,除非目标服务器明确允许跨域访问。
CORS:跨源资源共享机制
“浏览器限制”更准确的理解是:跨站请求可以被正常发起,但返回结果可能被浏览器拦截。为了在安全的前提下实现合法的跨域数据交换,W3C 推出了 跨源资源共享(CORS) 机制。
CORS 允许服务器通过设置特定的 HTTP 响应头,来声明哪些外部源有权访问其资源。当浏览器发起跨域请求时,会根据服务器返回的这些 CORS 头部决定是否允许前端 JavaScript 访问响应数据。
根据请求的复杂性,CORS 将请求分为两类:简单请求和非简单请求(需预检请求)。
简单请求
满足以下所有条件的请求被视为简单请求,不会触发预检:
- 方法限制:仅使用 GET、HEAD、POST 方法之一。
- 标头限制:除用户代理自动设置的标头外,仅允许使用以下标头:
Accept、Accept-Language、Content-Language、Content-Type(值仅限于 application/x-www-form-urlencoded、multipart/form-data、text/plain)。
- 请求中没有使用
ReadableStream 对象。
对于简单请求,浏览器会直接发出请求,并在请求头中自动添加 Origin 字段,服务器通过响应头 Access-Control-Allow-Origin 来决定是否允许跨域。
预检请求
不满足简单请求条件的请求(例如使用了 PUT、DELETE 方法,或 Content-Type 为 application/json,或设置了自定义头),浏览器会首先自动发起一个 OPTIONS 方法的预检请求到服务器,以获知服务器是否允许该实际请求。
例如,一个携带自定义头 x-secsdk-csrf-token 的 POST 请求,浏览器会先发送 OPTIONS 请求进行“询问”。
预检请求的关键头部:
Access-Control-Request-Method:告知服务器,实际请求将使用的 HTTP 方法(如 POST)。
Access-Control-Request-Headers:告知服务器,实际请求将携带的自定义头部字段列表。
服务器响应的关键头部:
Access-Control-Allow-Origin:允许跨域请求的源,可以是具体域名或 *(通配符,但有使用限制)。
Access-Control-Allow-Methods:允许使用的 HTTP 方法列表。
Access-Control-Allow-Headers:允许携带的请求头列表。
Access-Control-Max-Age:指定本次预检响应的有效时间(秒),在此期间内不再发送预检请求。
一旦预检请求通过,浏览器才会发出实际的 POST 请求。这就是为什么某些 POST 请求看起来会发送两次(一次 OPTIONS 预检,一次实际的 POST)的根本原因。
附带凭证的请求与安全限制
当请求需要携带 Cookie 等身份凭证时(通过在 XMLHttpRequest 或 Fetch 中设置 withCredentials = true),安全性要求更高:
- 服务器不能将
Access-Control-Allow-Origin 设置为通配符 *,必须指定明确的域名(如 Access-Control-Allow-Origin: https://example.com)。
- 同样,
Access-Control-Allow-Headers 和 Access-Control-Allow-Methods 也应尽量避免使用 *,而提供明确的列表。
- 服务器响应可能需要包含
Access-Control-Allow-Credentials: true 头部。
完整的CORS请求流程
下图清晰地展示了简单请求与需预检请求的完整流程差异:

总结
预检请求是 CORS 机制中用于保障安全的重要环节。它通过一次“前置询问”(OPTIONS 请求),让服务器有机会对非简单的跨域HTTP请求进行审查和授权,声明允许的方法、头部和来源。这有效防止了恶意网站滥用跨域请求,保护了用户数据和服务器资源。理解预检请求,对于前端开发者处理网络请求与API交互至关重要。
|