CORS 简单请求与预检请求详解

在现代Web开发中,跨域资源共享(Cross-Origin Resource Sharing,CORS)是一个非常重要的安全机制,主要用于允许浏览器从不同源服务器访问资源。了解CORS的基本原理和其具体的请求类型,有助于开发者更好地控制前后端通信的安全性。本文将详细介绍CORS中的简单请求与预检请求。

什么是CORS?

CORS的定义

CORS是一个W3C标准,它允许浏览器安全地处理跨域请求。跨域请求指的是在不同域、协议或端口之间发起的HTTP请求。例如,假设前端应用运行在 http://example.com,而它需要向 http://api.example.com 获取数据,这就是一个跨域请求。

同源策略的限制

浏览器的同源策略(Same-Origin Policy,SOP)是Web安全模型的一部分,旨在阻止从一个源加载的文档与另一个源的资源进行不安全的交互。为了避免这些限制,CORS允许开发者定义哪些跨域请求是允许的。

CORS的工作原理

CORS的工作依赖于HTTP请求头和响应头的配合,通过设置特定的CORS相关头部,浏览器和服务器之间的跨域请求可以被安全地执行。

请求头

当浏览器发起跨域请求时,它会自动在请求中附加一些CORS相关的头部,最常见的是Origin头,用于指明请求的源。

响应头

服务器可以通过设置响应头来告知浏览器哪些跨域请求是被允许的。例如,Access-Control-Allow-Origin 用于明确允许哪些源可以访问资源。

CORS请求的分类

根据请求的复杂程度和安全性,CORS请求可以分为两类:

  • 简单请求 (Simple Requests)
  • 预检请求 (Preflight Requests)

接下来我们将详细讨论这两类请求的区别及其各自的特点。

简单请求(Simple Requests)

什么是简单请求?

简单请求是指那些不需要预检的跨域请求。这类请求必须满足一定的条件,包括使用特定的HTTP方法和头部。浏览器会直接发送简单请求而无需进行额外的安全检查。

简单请求的条件

根据CORS规范,只有符合以下所有条件的请求才被视为简单请求:

  • 使用的HTTP方法 :仅限于 GETPOST、和 HEAD

  • 头部限制:请求头只能包含浏览器自动设置的以下三类头部:

    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (仅限值为 application/x-www-form-urlencodedmultipart/form-datatext/plain)
  • 不使用自定义头部 :如果请求头中包含其他任何自定义头部,则该请求将不再被视为简单请求。

浏览器的行为

对于简单请求,浏览器会直接发送请求,并且服务器通过在响应中设置CORS相关的头部来指明是否允许该请求。如果允许,浏览器将处理服务器返回的响应;否则,浏览器将阻止跨域请求并报错。

例子

假设我们有一个跨域请求,前端代码如下:

1
2
3
4
5
6
7
8
fetch('https://api.example.com/data', {
method: 'GET',
headers: {
'Accept': 'application/json',
}
})
.then(response => response.json())
.then(data => console.log(data));

如果服务器允许跨域请求,它将在响应头中返回 Access-Control-Allow-Origin: *,从而允许所有来源的请求。

预检请求(Preflight Requests)

什么是预检请求?

当跨域请求不满足简单请求的条件时,浏览器会自动在正式请求之前发送一次OPTIONS请求,这个请求被称为预检请求。预检请求的目的是让浏览器确认服务器是否允许该类型的请求。

预检请求的触发条件

预检请求通常在以下情况下触发:

  • 使用了简单请求之外的HTTP方法,例如 PUTDELETE、或 PATCH
  • 请求头中包含了自定义的头部字段。
  • Content-Type 值为除 application/x-www-form-urlencodedmultipart/form-datatext/plain 之外的其他类型,例如 application/json

预检请求的流程

预检请求是由浏览器自动处理的,开发者无需手动发起。其流程如下:

  1. OPTIONS 请求 :浏览器首先发送一个 OPTIONS 请求,询问服务器是否允许该跨域请求。这个请求中包含了以下头部:
    • Origin: 指明请求的源。
    • Access-Control-Request-Method: 说明即将发送的实际请求使用了哪种HTTP方法。
    • Access-Control-Request-Headers: 说明实际请求中使用了哪些自定义头部。
  2. 服务器响应 :服务器接收到 OPTIONS 请求后,通过返回相应的CORS头部来决定是否允许跨域请求。典型的响应头包括:
    • Access-Control-Allow-Origin: 允许的请求源。
    • Access-Control-Allow-Methods: 允许的HTTP方法。
    • Access-Control-Allow-Headers: 允许的请求头。
  3. 实际请求 :如果服务器允许该跨域请求,浏览器才会发起实际的请求;否则,请求将被阻止。

例子

以下代码使用了 PUT 方法,并且请求头中包含了自定义的 Authorization 头部:

1
2
3
4
5
6
7
8
9
10
fetch('https://api.example.com/update', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token'
},
body: JSON.stringify({ name: 'new name' })
})
.then(response => response.json())
.then(data => console.log(data));

在这种情况下,浏览器会首先发送预检请求以确认服务器是否允许 PUT 请求和 Authorization 头部的使用。

CORS响应头详解

在CORS机制中,服务器通过设置以下头部来管理跨域请求的权限:

  • Access-Control-Allow-Origin : 指定允许的请求源,可以是具体的域名或 * 表示允许所有源。
  • Access-Control-Allow-Methods : 指定允许的HTTP方法,例如 GETPOSTPUT 等。
  • Access-Control-Allow-Headers : 指定允许的自定义请求头,例如 AuthorizationX-Requested-With
  • Access-Control-Allow-Credentials : 是否允许发送带有身份验证信息的请求,例如cookies。值为 truefalse

常见问题及解决方案

为什么跨域请求会失败?

最常见的原因是服务器未正确设置CORS响应头,或者浏览器阻止了不安全的跨域请求。开发者应确保服务器设置了正确的 Access-Control-Allow-Origin 等头部。

如何调试CORS问题?

可以使用浏览器的开发者工具来查看跨域请求的详细信息,检查请求和响应的头部,找出导致问题的具体原因。

总结

CORS是Web开发中的重要机制,能够确保跨域资源请求的安全性。理解简单请求与预检请求的区别,并正确配置服务器的CORS头部,可以帮助开发者避免跨域问题并提升用户体验。