CORS跨域请求之简单请求和复杂请求

528 阅读6分钟

跨域资源共享(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 方法

只允许使用以下三种方法之一:

  • GET
  • POST
  • HEAD

2. HTTP 头

请求头中只允许包含以下几种标准的、无自定义值的首部字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type(且只能是以下三种值之一:application/x-www-form-urlencodedmultipart/form-datatext/plain

3. 请求不包含 XMLHttpRequestupload 属性,且请求中不使用自定义的 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)

复杂请求是指不符合简单请求条件的请求,例如使用了 PUTDELETE 等方法,或者请求头中包含了非标准字段。复杂请求需要浏览器在发送实际请求前,先发出一个预检请求(preflight request)。

1. 预检请求 (Preflight Request)

预检请求是浏览器在发送实际请求前自动发出的 OPTIONS 请求,目的是询问服务器是否允许该跨域请求。预检请求包含以下重要头字段:

  • Access-Control-Request-Method:指明实际请求将使用的 HTTP 方法。
  • Access-Control-Request-Headers:指明实际请求中包含的自定义头字段。

2. 服务器响应

服务器在接收到预检请求后,会根据请求头中的 OriginAccess-Control-Request-MethodAccess-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 请求中常见的重要响应头:

  1. Access-Control-Allow-Origin:指定允许的请求源,值可以是具体的域名或者 *(表示允许所有域)。
  2. Access-Control-Allow-Methods:指定允许的 HTTP 方法列表,如 GETPOSTPUT 等。
  3. Access-Control-Allow-Headers:指定允许的自定义头字段,如 AuthorizationX-Requested-With 等。
  4. Access-Control-Allow-Credentials:指示是否允许凭据(如 Cookies)随请求发送。设置为 true 时允许。
  5. Access-Control-Max-Age:指定预检请求的结果可以缓存的时间,单位为秒。

六、CORS 相关的常见问题

  1. 凭据的跨域请求:若需要发送凭据(如 Cookies),Access-Control-Allow-Credentials 头必须设置为 true,同时 Access-Control-Allow-Origin 不能为 *,必须是具体的域名。
  2. 跨域失败的常见原因
    • 服务器未正确配置 CORS 响应头。
    • 请求头或请求方法不符合 CORS 规范。
    • 预检请求未通过,导致实际请求被阻止。

七、总结

CORS 通过一系列的 HTTP 头字段,灵活地控制跨域请求的安全性和权限。深入理解简单请求和复杂请求的区别,以及预检请求的处理机制,有助于更好地调试和解决跨域相关的问题。

你不可以简单地理解为“简单请求不会跨域”。事实上,简单请求仍然是跨域请求,它只是由于其特殊的性质,不需要预检(preflight),但仍然受CORS机制的约束。


具体解释如下:

  1. 简单请求仍然会跨域:
  • 当你的前端代码(如JavaScript)请求一个与当前页面不同源的资源时,无论这个请求是简单请求还是复杂请求,都是跨域请求。这是因为请求的目标域与页面的源不同。
  • “不同源”指的是域名、协议或端口中的任意一个不同,都会触发跨域请求。
  1. 简单请求的跨域机制:
  • 对于简单请求,浏览器直接发送请求,不需要先发一个预检请求。但是它仍然会包含一个 Origin 头,这个头会标明请求发起的源。
  • 服务器根据 Origin 头决定是否允许这个跨域请求。如果允许,服务器会在响应中添加 Access-Control-Allow-Origin 头,并且浏览器会根据这个头来决定是否允许页面访问返回的数据。
  • 如果服务器没有返回 Access-Control-Allow-Origin 头或者头的值不匹配请求的源,浏览器将禁止页面访问响应数据。

小结:

  • 简单请求只是指它的条件较为宽松,不需要浏览器进行预检,但它仍然是跨域请求,并且仍然需要遵循CORS规范。
  • 跨域请求的关键不在于请求的简单与复杂,而在于请求的源与目标是否不同。如果源不同,无论请求是简单还是复杂,都是跨域请求。

所以,简单请求和复杂请求都是跨域请求,只是它们处理跨域请求的方式不同。