CORS Errors
什么是 CORS?
跨域资源共享(CORS) 是一种机制,浏览器和 Webview(如为 Capacitor 和 Cordova 提供支持的 Webview)出于安全原因(主要是为了保护用户数据并防止可能危及应用的攻击),使用该机制来限制脚本向不同来源的资源发出的 HTTP 和 HTTPS 请求。
为了了解外部来源是否支持 CORS,服务器必须发送一些特殊标头才能让浏览器允许请求。
来源 是提供 Ionic 应用或外部资源的协议、域名和端口的组合。例如,在 Capacitor 中运行的应用以 capacitor://localhost(iOS)或 http://localhost(Android)作为其来源。
当提供应用的来源(例如使用 ionic serve 时的 http://localhost:8100)与所请求资源的来源(例如 https://api.example.com)不匹配时,浏览器的同源策略就会生效,并且需要 CORS 才能发出请求。
当发出跨域请求但服务器未在响应中返回必需标头(即未启用 CORS)时,Web 应用中经常会出现 CORS 错误:
XMLHttpRequest 无法加载 https://api.example.com。所请求的资源上没有 'Access-Control-Allow-Origin' 标头。因此不允许来源 'http://localhost:8100' 进行访问。
CORS 的工作原理
带有预检的请求
默认情况下,当 Web 应用尝试发出跨域请求时,浏览器会在实际请求之前发送一个预检请求。发送此预检请求是为了了解外部资源是否支持 CORS 以及实际请求是否可以安全发送,因为它可能会影响用户数据。
如果满足以下条件,浏览器会发送预检请求:
- 请求方法为:
- PUT
- DELETE
- CONNECT
- OPTIONS
- TRACE
- PATCH
- 或者,如果它具有以下之外的标头:
- Accept
- Accept-Language
- Content-Language
- Content-Type
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
- 或者,如果它具有
Content-Type标头且其值不是以下之一:- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
- 或者,如果使用了
XMLHttpRequestUpload中的ReadableStream或事件监听器。
如果满足上述任一条件,就会向资源 URL 发送一个使用 OPTIONS 方法的预检请求。
假设我们向位于 https://api.example.com 的虚构 JSON API 发出一个 Content-Type 为 application/json 的 POST 请求。预检请求将如下所示(为清晰起见,省略了一些默认标头):
OPTIONS / HTTP/1.1
Host: api.example.com
Origin: http://localhost:8100
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
如果服务器启用了 CORS,它将解析 Access-Control-Request-* 标头,并理解尝试从 http://localhost:8100 发出一个带有自定义 Content-Type 的 POST 请求。
然后,服务器将使用 Access-Control-Allow-* 标头来响应此预检,指明允许的来源、方法和标头:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:8100
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type
如果返回的来源和方法与实际请求的不匹配,或者使用的任何标头不被允许,浏览器将阻止该请求,并在控制台中显示错误。否则,预检之后将发出实际请求。
在我们的示例中,由于 API 期望 JSON 数据,所有 POST 请求都将具有 Content-Type: application/json 标头,并且始终需要预检。
简单请求
某些请求如果满足以下所有条件,则始终被认为是安全的,无需预检:
- 请求方法为:
- GET
- HEAD
- POST
- 仅具有以下标头:
- Accept
- Accept-Language
- Content-Language
- Content-Type
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
Content-Type标头的值为以下之一:- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
- 未使用
XMLHttpRequestUpload中的ReadableStream或事件监听器。
在我们的示例 API 中,GET 请求不需要预检,因为不会发送 JSON 数据,因此应用不需要使用 Content-Type: application/json 标头。它们将始终是简单请求。
CORS 标头
服务器标头(响应)
| 标头 | 值 | 描述 |
|---|---|---|
| Access-Control-Allow-Origin | origin 或 * | 指定允许的来源,如 http://localhost:8100 或 * 以允许所有来源。 |
| Access-Control-Allow-Methods | methods | 访问资源时允许使用的方法:GET、HEAD、POST、PUT、DELETE、CONNECT、OPTIONS、TRACE、PATCH。 |
| Access-Control-Allow-Headers | headers | 用于响应预检请求,指示在发出实际请求时可以使用哪些标头(除了简单标头,这些标头始终允许)。 |
| Access-Control-Allow-Credentials | true 或 false | 请求是否可以使用凭据。 |
| Access-Control-Expose-Headers | headers | 指定浏览器允许访问的标头。 |
| Access-Control-Max-Age | seconds | 指示预检请求的结果可以缓存多长时间。 |
浏览器标头(请求)
浏览器会在每个请求(包括预检请求)中自动向服务器发送适当的 CORS 标头。请注意,以下标头仅供参考,不应在应用代码中设置(浏览器将忽略它们)。
所有请求
| 标头 | 值 | 描述 |
|---|---|---|
| Origin | origin | 指示请求的来源。 |
预检请求
| 标头 | 值 | 描述 |
|---|---|---|
| Access-Control-Request-Method | method | 用于让服务器知道发出实际请求时将使用哪种方法。 |
| Access-Control-Request-Headers | headers | 用于让服务器知道发出实际请求时将使用哪些非简单标头。 |