跨域资源共享(CORS, Cross-Origin Resource Sharing)机制主要用于解决浏览器的同源策略限制,使得前端页面可以请求不同域名、协议或端口上的资源。CORS 规范定义了不同的请求类型及其处理方式,包括简单请求和复杂请求。深入了解这些内容可以帮助你更好地理解浏览器的跨域行为。
一、同源策略与跨域
同源策略是一种浏览器安全机制,只有当请求的协议、域名和端口号都相同时,浏览器才允许发送 AJAX 请求。这一策略限制了跨站脚本攻击 (XSS) 的可能性,但也带来了跨域资源共享的需求。
二、CORS 基本概念
CORS 机制通过在 HTTP 头中增加一些特殊字段来允许浏览器与跨域的服务器进行交互。以下是一些关键的 HTTP 头字段:
- Origin:请求源,表示发起请求的页面的来源。
- Access-Control-Allow-Origin:服务器响应中的字段,指定允许的请求源。
- Access-Control-Allow-Methods:服务器响应中的字段,指定允许的 HTTP 方法。
- Access-Control-Allow-Headers:服务器响应中的字段,指定允许的自定义请求头。
- Access-Control-Allow-Credentials:服务器响应中的字段,指定是否允许发送凭据(如 Cookies)。
三、简单请求(Simple Request)
简单请求是指不需要预检的跨域请求。要构成简单请求,必须满足以下所有条件:
1. HTTP 方法
只允许使用以下三种方法之一:
GETPOSTHEAD
2. HTTP 头
请求头中只允许包含以下几种标准的、无自定义值的首部字段:
AcceptAccept-LanguageContent-LanguageContent-Type(且只能是以下三种值之一:application/x-www-form-urlencoded、multipart/form-data、text/plain)
3. 请求不包含 XMLHttpRequest 的 upload 属性,且请求中不使用自定义的 HTTP 头。
请求流程:
- 浏览器发起请求时,会自动在请求头中添加
Origin字段,该字段包含请求发起页面的源(协议、域名、端口)。 - 服务器根据
Origin判断是否允许请求,并在响应头中返回Access-Control-Allow-Origin字段。如果允许跨域,浏览器将继续处理响应数据。
示例:
fetch('https://api.example.com/data', {
method: 'GET',
headers: {
'Content-Type': 'text/plain'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
在上面的例子中,GET 请求符合简单请求的条件,因此浏览器可以直接发出请求,并处理返回的结果。
四、复杂请求(Complex Request)
复杂请求是指不符合简单请求条件的请求,例如使用了 PUT、DELETE 等方法,或者请求头中包含了非标准字段。复杂请求需要浏览器在发送实际请求前,先发出一个预检请求(preflight request)。
1. 预检请求 (Preflight Request)
预检请求是浏览器在发送实际请求前自动发出的 OPTIONS 请求,目的是询问服务器是否允许该跨域请求。预检请求包含以下重要头字段:
- Access-Control-Request-Method:指明实际请求将使用的 HTTP 方法。
- Access-Control-Request-Headers:指明实际请求中包含的自定义头字段。
2. 服务器响应
服务器在接收到预检请求后,会根据请求头中的 Origin、Access-Control-Request-Method 和 Access-Control-Request-Headers 决定是否允许请求。如果允许,服务器会返回以下头字段:
- Access-Control-Allow-Origin:允许的请求源。
- Access-Control-Allow-Methods:允许的方法列表。
- Access-Control-Allow-Headers:允许的自定义头字段列表。
- Access-Control-Max-Age:指定预检请求的结果可以缓存的时间,避免重复预检。
3. 实际请求
如果预检请求通过,浏览器将继续发送实际的 HTTP 请求,并根据服务器的响应处理结果。
示例:
fetch('https://api.example.com/data', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token'
},
body: JSON.stringify({ key: 'value' })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
在这个例子中,PUT 请求使用了复杂的 HTTP 方法和自定义的 Authorization 头,因此会触发预检请求。
五、CORS 中的重要响应头
以下是 CORS 请求中常见的重要响应头:
- Access-Control-Allow-Origin:指定允许的请求源,值可以是具体的域名或者
*(表示允许所有域)。 - Access-Control-Allow-Methods:指定允许的 HTTP 方法列表,如
GET、POST、PUT等。 - Access-Control-Allow-Headers:指定允许的自定义头字段,如
Authorization、X-Requested-With等。 - Access-Control-Allow-Credentials:指示是否允许凭据(如 Cookies)随请求发送。设置为
true时允许。 - Access-Control-Max-Age:指定预检请求的结果可以缓存的时间,单位为秒。
六、CORS 相关的常见问题
- 凭据的跨域请求:若需要发送凭据(如 Cookies),
Access-Control-Allow-Credentials头必须设置为true,同时Access-Control-Allow-Origin不能为*,必须是具体的域名。 - 跨域失败的常见原因:
-
- 服务器未正确配置 CORS 响应头。
- 请求头或请求方法不符合 CORS 规范。
- 预检请求未通过,导致实际请求被阻止。
七、总结
CORS 通过一系列的 HTTP 头字段,灵活地控制跨域请求的安全性和权限。深入理解简单请求和复杂请求的区别,以及预检请求的处理机制,有助于更好地调试和解决跨域相关的问题。
你不可以简单地理解为“简单请求不会跨域”。事实上,简单请求仍然是跨域请求,它只是由于其特殊的性质,不需要预检(preflight),但仍然受CORS机制的约束。
具体解释如下:
- 简单请求仍然会跨域:
- 当你的前端代码(如JavaScript)请求一个与当前页面不同源的资源时,无论这个请求是简单请求还是复杂请求,都是跨域请求。这是因为请求的目标域与页面的源不同。
- “不同源”指的是域名、协议或端口中的任意一个不同,都会触发跨域请求。
- 简单请求的跨域机制:
- 对于简单请求,浏览器直接发送请求,不需要先发一个预检请求。但是它仍然会包含一个 Origin 头,这个头会标明请求发起的源。
- 服务器根据 Origin 头决定是否允许这个跨域请求。如果允许,服务器会在响应中添加 Access-Control-Allow-Origin 头,并且浏览器会根据这个头来决定是否允许页面访问返回的数据。
- 如果服务器没有返回 Access-Control-Allow-Origin 头或者头的值不匹配请求的源,浏览器将禁止页面访问响应数据。
小结:
- 简单请求只是指它的条件较为宽松,不需要浏览器进行预检,但它仍然是跨域请求,并且仍然需要遵循CORS规范。
- 跨域请求的关键不在于请求的简单与复杂,而在于请求的源与目标是否不同。如果源不同,无论请求是简单还是复杂,都是跨域请求。
所以,简单请求和复杂请求都是跨域请求,只是它们处理跨域请求的方式不同。