Skip to main content

CORS 错误

什么是 CORS?

¥What is CORS?

跨源资源共享 (CORS) 是一种机制,浏览器和 Web 视图(例如为 Capacitor 和 Cordova 提供支持的浏览器和 Web 视图)出于安全原因,用于限制从脚本向不同来源的资源发出的 HTTP 和 HTTPS 请求,主要是为了保护用户的数据 并防止可能危及你的应用的攻击。

¥Cross-Origin Resource Sharing (CORS) is a mechanism that browsers and webviews — like the ones powering Capacitor and Cordova — use to restrict HTTP and HTTPS requests made from scripts to resources in a different origin for security reasons, mainly to protect your user's data and prevent attacks that would compromise your app.

为了知道外部源是否支持 CORS,服务器必须发送一些 特殊标头 供浏览器允许请求。

¥In order to know if an external origin supports CORS, the server has to send some special headers for the browser to allow the requests.

源是为你的 Ionic 应用或外部资源提供服务的协议、域和端口的组合。例如,在 Capacitor 中运行的应用以 capacitor://localhost (iOS) 或 http://localhost (Android) 作为其来源。

¥An origin is the combination of the protocol, domain, and port from which your Ionic app or the external resource is served. For example, apps running in Capacitor have capacitor://localhost (iOS) or http://localhost (Android) as their origin.

当你的应用的来源(例如 http://localhost:8100ionic serve)与所请求的资源的来源(例如 https://api.example.com)不匹配时,浏览器的 同源政策 将生效,并且发出请求需要 CORS。

¥When the origin where your app is served (e.g. http://localhost:8100 with ionic serve) and the origin of the resource being requested (e.g. https://api.example.com) don't match, the browser's Same Origin Policy takes effect and CORS is required for the request to be made.

当发出跨域请求但服务器未在响应中返回所需的标头(未启用 CORS)时,CORS 错误在 Web 应用中很常见:

¥CORS errors are common in web apps when a cross-origin request is made but the server doesn't return the required headers in the response (is not CORS-enabled):

注意

XMLHttpRequest 无法加载 https://api.example.com。请求的资源上不存在 '访问控制允许来源' 标头。因此,不允许访问源“http://localhost:8100”。

¥XMLHttpRequest cannot load https://api.example.com. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8100' is therefore not allowed access.

CORS 工作原理

¥How does CORS work

预检请求

¥Request with preflight

默认情况下,当 Web 应用尝试发出跨源请求时,浏览器会在实际请求之前发送预检请求。需要此预检请求,以便了解外部资源是否支持 CORS 以及是否可以安全发送实际请求,因为它可能会影响用户数据。

¥By default, when a web app tries to make a cross-origin request the browser sends a preflight request before the actual request. This preflight request is needed in order to know if the external resource supports CORS and if the actual request can be sent safely, since it may impact user data.

在以下情况下,浏览器会发送预检请求:

¥A preflight request is sent by the browser if:

  • 方法是:

    ¥The method is:

    • PUT

    • DELETE

    • CONNECT

    • OPTIONS

    • TRACE

    • PATCH

  • 或者如果它的标题不是:

    ¥Or if it has a header other than:

    • 接受

      ¥Accept

    • 接受语言

      ¥Accept-Language

    • 内容语言

      ¥Content-Language

    • 内容类型

      ¥Content-Type

    • DPR

    • 下行

      ¥Downlink

    • 保存数据

      ¥Save-Data

    • 视口宽度

      ¥Viewport-Width

    • 宽度

      ¥Width

  • 或者,如果它具有 Content-Type 标头,而不是:

    ¥Or if it has a Content-Type header other than:

    • 应用/x-www-form-urlencoded

      ¥application/x-www-form-urlencoded

    • 多部分/表单数据

      ¥multipart/form-data

    • 文本/纯文本

      ¥text/plain

  • 或者如果使用 ReadableStreamXMLHttpRequestUpload 中的事件监听器。

    ¥Or if a ReadableStream or event listeners in XMLHttpRequestUpload are used.

如果满足上述任何条件,则会向资源 URL 发送带有 OPTIONS 方法的预检请求。

¥If any of the conditions above are met, a preflight request with the OPTIONS method is sent to the resource URL.

假设我们正在向 https://api.example.com 处的虚构 JSON API 发出 POST 请求,其中 Content-Typeapplication/json。预检请求将如下所示(为了清楚起见,省略了一些默认标头):

¥Let's suppose we are making a POST request to a fictional JSON API at https://api.example.com with a Content-Type of application/json. The preflight request would be like this (some default headers omitted for clarity):

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-* 标头并了解 POST 请求正尝试从 http://localhost:8100 使用自定义 Content-Type 发出。

¥If the server is CORS enabled, it will parse the Access-Control-Request-* headers and understand that a POST request is trying to be made from http://localhost:8100 with a custom Content-Type.

然后,服务器将响应此预检,通过使用 Access-Control-Allow-* 标头来允许来源、方法和标头:

¥The server will then respond to this preflight with which origins, methods, and headers are allowed by using the Access-Control-Allow-* headers:

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

如果返回的来源和方法与实际请求中的不匹配,或者使用的任何标头都是不允许的,则该请求将被浏览器阻止,并在控制台中显示错误。否则,请求将在预检后提出。

¥If the returned origin and method don't match the ones from the actual request, or any of the headers used are not allowed, the request will be blocked by the browser and an error will be shown in the console. Otherwise, the request will be made after the preflight.

在我们的示例中,由于 API 需要 JSON,因此所有 POST 请求都将具有 Content-Type: application/json 标头并且始终进行预检。

¥In our example, since the API expects JSON, all POST requests will have a Content-Type: application/json header and always be preflighted.

简单的请求

¥Simple requests

如果满足以下所有条件,某些请求始终被认为可以安全发送,并且不需要预检:

¥Some requests are always considered safe to send and don't need a preflight if they meet all of the following conditions:

  • 方法是:

    ¥The method is:

    • GET

    • HEAD

    • POST

  • 仅包含这些标头:

    ¥Have only these headers:

    • 接受

      ¥Accept

    • 接受语言

      ¥Accept-Language

    • 内容语言

      ¥Content-Language

    • 内容类型

      ¥Content-Type

    • DPR

    • 下行

      ¥Downlink

    • 保存数据

      ¥Save-Data

    • 视口宽度

      ¥Viewport-Width

    • 宽度

      ¥Width

  • Content-Type 标头是:

    ¥The Content-Type header is:

    • 应用/x-www-form-urlencoded

      ¥application/x-www-form-urlencoded

    • 多部分/表单数据

      ¥multipart/form-data

    • 文本/纯文本

      ¥text/plain

  • 未使用 ReadableStreamXMLHttpRequestUpload 中的事件监听器。

    ¥No ReadableStream or event listeners in XMLHttpRequestUpload are used.

在我们的示例 API 中,不需要对 GET 请求进行预检,因为没有发送 JSON 数据,因此应用不需要使用 Content-Type: application/json 标头。它们永远都是简单的请求。

¥In our example API, GET requests don't need to be preflighted because no JSON data is being sent, and so the app doesn't need to use the Content-Type: application/json header. They will always be simple requests.

CORS 标头

¥CORS Headers

服务器标头(响应)

¥Server Headers (Response)

标头描述
访问控制允许来源origin*指定允许的来源,例如 http://localhost:8100* 允许所有来源。
访问控制允许方法methods访问资源时允许使用哪些方法:GETHEADPOSTPUTDELETECONNECTOPTIONSTRACEPATCH
访问控制允许标头headers用于响应预检请求,以指示在发出实际请求时可以使用哪些标头,除了始终允许的 简单的标题 之外。
访问控制允许凭证truefalse是否可以使用凭据发出请求。
访问控制公开标头headers指定允许浏览器访问的标头。
访问控制最大年龄seconds指示预检请求的结果可以缓存多长时间。

浏览器标头(请求)

¥Browser Headers (Request)

浏览器会自动将每个请求中的 CORS 相应标头发送到服务器,包括预检请求。请注意,下面的标头仅供参考,不应在你的应用代码中设置(浏览器将忽略它们)。

¥The browser automatically sends the appropriate headers for CORS in every request to the server, including the preflight requests. Please note that the headers below are for reference only, and should not be set in your app code (the browser will ignore them).

所有请求

¥All Requests

标头描述
起源origin指示请求的来源。

预检请求

¥Preflight Requests

标头描述
访问控制请求方法method用于让服务器知道实际发出请求时将使用什么方法。
访问控制请求标头headers用于让服务器知道在发出实际请求时将使用哪些非简单标头。

CORS 错误的解决方案

¥Solutions for CORS Errors

A. 在你控制的服务器中启用 CORS

¥A. Enabling CORS in a server you control

正确且最简单的解决方案是通过从 Web 服务器或后端返回 正确的响应标头 并响应预检请求来启用 CORS,因为它允许继续使用 XMLHttpRequestfetch 或 Angular 中的 HttpClient 等抽象。

¥The correct and easiest solution is to enable CORS by returning the right response headers from the web server or backend and responding to preflight requests, as it allows to keep using XMLHttpRequest, fetch, or abstractions like HttpClient in Angular.

Ionic 应用可以从不同的源运行,但只能在 Access-Control-Allow-Origin 标头中指定一个源。因此,我们建议检查请求中 Origin 标头的值并将其反映在响应中的 Access-Control-Allow-Origin 标头中。

¥Ionic apps may be run from different origins, but only one origin can be specified in the Access-Control-Allow-Origin header. Therefore we recommend checking the value of the Origin header from the request and reflecting it in the Access-Control-Allow-Origin header in the response.

请注意,所有 Access-Control-Allow-* 标头都必须从服务器发送,并且不属于你的应用代码。

¥Please note that all of the Access-Control-Allow-* headers have to be sent from the server, and don't belong in your app code.

以下是你的 Ionic 应用可能提供服务的一些来源:

¥Here are some of the origins your Ionic app may be served from:

Capacitor

平台起源
iOScapacitor://localhost
安卓http://localhost

如果你更改了 Capacitor 配置中的默认值,请将 localhost 替换为你自己的主机名。

¥Replace localhost with your own hostname if you have changed the default in the Capacitor config.

Cordova 上的 Ionic WebView 3.x 插件

¥Ionic WebView 3.x plugin on Cordova

平台起源
iOSionic://localhost
安卓http://localhost

如果你更改了插件配置中的默认主机名,请将 localhost 替换为你自己的主机名。

¥Replace localhost with your own hostname if you have changed the default in the plugin config.

Cordova 上的 Ionic WebView 2.x 插件

¥Ionic WebView 2.x plugin on Cordova

平台起源
iOShttp://localhost:8080
安卓http://localhost:8080

如果你更改了插件配置中的默认端口,请将端口 8080 替换为你自己的端口。

¥Replace port 8080 with your own if you have changed the default in the plugin config.

浏览器本地开发

¥Local development in the browser

命令起源
ionic servehttp://localhost:8100http://YOUR_MACHINE_IP:8100
npm run startng servehttp://localhost:4200 用于 Ionic Angular 应用。

如果你同时服务多个应用,端口号可能会更高。

¥Port numbers can be higher if you are serving multiple apps at the same time.

允许任何来源使用 Access-Control-Allow-Origin: * 可以保证在所有情况下都有效,但可能会产生安全隐患(例如某些 CSRF 攻击),具体取决于服务器如何控制对资源的访问以及使用会话和 cookie。

¥Allowing any origin with Access-Control-Allow-Origin: * is guaranteed to work in all scenarios but may have security implications — like some CSRF attacks — depending on how the server controls access to resources and use sessions and cookies.

有关如何在不同的 Web 和应用服务器中启用 CORS 的更多信息,请查看 enable-cors.org

¥For more information on how to enable CORS in different web and app servers, please check enable-cors.org

可以使用 cors 中间件在 Express/Connect 应用中轻松启用 CORS:

¥CORS can be easily enabled in Express/Connect apps with the cors middleware:

const express = require('express');
const cors = require('cors');
const app = express();

const allowedOrigins = [
'capacitor://localhost',
'ionic://localhost',
'http://localhost',
'http://localhost:8080',
'http://localhost:8100',
];

// Reflect the origin if it's in the allowed list or not defined (cURL, Postman, etc.)
const corsOptions = {
origin: (origin, callback) => {
if (allowedOrigins.includes(origin) || !origin) {
callback(null, true);
} else {
callback(new Error('Origin not allowed by CORS'));
}
},
};

// Enable preflight requests for all routes
app.options('*', cors(corsOptions));

app.get('/', cors(corsOptions), (req, res, next) => {
res.json({ message: 'This route is CORS-enabled for an allowed origin.' });
});

app.listen(3000, () => {
console.log('CORS-enabled web server listening on port 3000');
});

B. 在你无法控制的服务器中解决 CORS

¥B. Working around CORS in a server you can't control

不要泄露你的密钥!

¥Don't leak your keys!

如果你尝试连接到第 3 方 API,请首先检查其文档,该文档可以安全地直接从应用(客户端)使用它,并且不会泄露任何秘密/私钥或凭据,因为它是 很容易在 Javascript 代码中以明文形式查看它们。许多 API 故意不支持 CORS,以迫使开发者在服务器中使用它们并保护重要信息或密钥。

¥If you are trying to connect to a 3rd-party API, first check in its documentation that is safe to use it directly from the app (client-side) and that it won't leak any secret/private keys or credentials, as it's easy to see them in clear text in Javascript code. Many APIs don't support CORS on purpose, in order to force developers to use them in the server and protect important information or keys.

1. 仅限原生的应用 (iOS/Android)

¥ Native-only apps (iOS/Android)

¥Capacitor Applications (Recommended)

对于 Capacitor 应用,请使用 Capacitor HTTP API。此 API 修补 fetchXMLHttpRequest 以使用原生库。请注意,如果你还将应用部署到基于 Web 的上下文,例如 PWA 或本地开发服务器(例如通过 ionic serve),你仍然需要为这些场景实现 CORS。

¥For Capacitor applications, use the Capacitor HTTP API. This API patches fetch and XMLHttpRequest to use native libraries. Please note that if you also deploy the application to a web-based context such as PWA or the local development server (via ionic serve for example) you still need to implement CORS for those scenarios.

旧版 Cordova 应用

¥Legacy Cordova Applications

对于旧版 Cordova 应用,请使用 带有 Awesome Cordova Plugins 封装器的 HTTP 插件。请注意,此插件无法在浏览器中工作,因此应用的开发和测试必须始终在设备或模拟器中完成。

¥For legacy Cordova applications, use the HTTP plugin with the Awesome Cordova Plugins wrapper. Please note that this plugin doesn't work in the browser, so the development and testing of the app must always be done in a device or simulator going forward.



import { Component } from '@angular/core';




import { HTTP } from '@awesome-cordova-plugins/http/ngx';



@Component({
selector: 'app-home',
templateUrl: './home.page.html',
styleUrls: ['./home.page.scss'],
})
export class HomePage {
constructor(private http: HTTP) {}

async getData() {
try {
const url = 'https://api.example.com';
const params = {};
const headers = {};

const response = await this.http.get(url, params, headers);

console.log(response.status);
console.log(JSON.parse(response.data)); // JSON data returned by server
console.log(response.headers);
} catch (error) {
console.error(error.status);
console.error(error.error); // Error message as string
console.error(error.headers);
}
}
}

2. 原生 + PWA

¥ Native + PWAs

通过 HTTP/HTTPS 代理发送请求,该代理将请求绕过到外部资源,并将必要的 CORS 标头添加到响应中。该代理必须受信任或在你的控制之下,因为它将拦截应用发出的大部分流量。

¥Send the requests through an HTTP/HTTPS proxy that bypasses them to the external resources and adds the necessary CORS headers to the responses. This proxy must be trusted or under your control, as it will be intercepting most traffic made by the app.

另外,请记住,浏览器或 webview 将不会收到原始的 HTTPS 证书,而是收到从代理发送的证书(如果提供)。可能需要在代码中重写 URL 才能使用代理。

¥Also, keep in mind that the browser or webview will not receive the original HTTPS certificates but the one being sent from the proxy if it's provided. URLs may need to be rewritten in your code in order to use the proxy.

检查 cors-anywhere 是否有可以部署在你自己的服务器中的 Node.js CORS 代理。不建议在生产中使用免费托管的 CORS 代理。

¥Check cors-anywhere for a Node.js CORS proxy that can be deployed in your own server. Using free hosted CORS proxies in production is not recommended.

C. 禁用 CORS 或浏览器 Web 安全

¥C. Disabling CORS or browser web security

请注意,CORS 的存在是有原因的(用户数据的安全性和防止针对你的应用的攻击)。尝试禁用 CORS 是不可能或不建议的。

¥Please be aware that CORS exists for a reason (security of user data and to prevent attacks against your app). It's not possible or advisable to try to disable CORS.

像 iOS 上的 UIWebView 这样的旧版 webview 不强制执行 CORS,但已被弃用,并且很可能很快就会消失。现代 Web 视图,如 iOS WKWebView 或 Android WebView(均由 Capacitor 使用)确实强制执行 CORS,并提供巨大的安全性和性能改进。

¥Older webviews like UIWebView on iOS don't enforce CORS but are deprecated and are very likely to disappear soon. Modern webviews like iOS WKWebView or Android WebView (both used by Capacitor) do enforce CORS and provide huge security and performance improvements.

如果你正在开发 PWA 或在浏览器中进行测试,那么使用 Google Chrome 中的 --disable-web-security 标志或扩展来禁用 CORS 是一个非常糟糕的主意。你将面临各种攻击,你不能要求你的用户承担风险,并且你的应用一旦投入生产就将无法运行。

¥If you are developing a PWA or testing in the browser, using the --disable-web-security flag in Google Chrome or an extension to disable CORS is a really bad idea. You will be exposed to all kind of attacks, you can't ask your users to take the risk, and your app won't work once in production.

来源

¥Sources