Skip to main content

CORS 错误

什么是 CORS?

🌐 What is CORS?

跨源资源共享(CORS) 是一种机制,浏览器和网页视图——如驱动 Capacitor 和 Cordova 的那些——用来限制脚本对不同源的资源发起的 HTTP 和 HTTPS 请求,以出于安全原因,主要是为了保护用户的数据并防止可能危及你的应用的攻击。

为了知道外部来源是否支持 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:8100 使用 ionic serve)与被请求资源的源(例如 https://api.example.com)不匹配时,浏览器的 同源策略 生效,该请求需要 CORS 才能进行。

当触发跨域请求但服务器未在响应中返回所需的标头(未启用 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):

note

XMLHttpRequest 无法加载 https://api.example.com。请求的资源上没有 'Access-Control-Allow-Origin' 头。因此不允许来源 'http://localhost:8100' 访问。

CORS 工作原理

🌐 How does CORS work

预检请求

🌐 Request with preflight

默认情况下,当一个网络应用尝试进行跨源请求时,浏览器会在实际请求之前发送一个预检请求。发送这个预检请求是为了确定外部资源是否支持 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:

  • 方法是:
    • 放置
    • 删除
    • 连接
    • 选项
    • 跟踪
    • 补丁
  • 或者如果它有除了以下之外的标题:
    • 接受
    • 接受语言
    • 内容语言
    • 内容类型
    • 朝鲜民主主义人民共和国
    • 下行
    • 保存数据
    • 视口宽度
    • 宽度
  • 或者如果它有一个除了以下之外的 Content-Type 头部:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain
  • 或者如果在 XMLHttpRequestUpload 中使用了 ReadableStream 或事件监听器。

如果满足上述任何条件,将会向资源 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-* 头,并理解从 http://localhost:8100 正在尝试发起一个带有自定义 Content-TypePOST 请求。

🌐 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:

  • 方法是:
    • 获取
    • 发布
  • 只保留这些标题:
    • 接受
    • 接受语言
    • 内容语言
    • 内容类型
    • 朝鲜民主主义人民共和国
    • 下行
    • 保存数据
    • 视口宽度
    • 宽度
  • Content-Type 头部是:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain
  • XMLHttpRequestUpload 中未使用任何 ReadableStream 或事件监听器。

在我们的示例 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)

头部描述
Access-Control-Allow-Originorigin*指定允许的来源,例如 http://localhost:8100* 表示允许所有来源。
Access-Control-Allow-Methodsmethods访问资源时允许的方法:GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH
Access-Control-Allow-Headersheaders用于响应预检请求,指示在实际请求中可以使用哪些头部,除了始终允许的简单头部
Access-Control-Allow-Credentialstruefalse请求是否可以携带凭证。
Access-Control-Expose-Headersheaders指定浏览器允许访问的头部。
Access-Control-Max-Ageseconds指示预检请求结果可以缓存的时间。

浏览器标头(请求)

🌐 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

描述
Access-Control-Request-Methodmethod用于让服务器知道在实际请求时将使用什么方法。
Access-Control-Request-Headersheaders用于让服务器知道在实际请求时将使用哪些非简单的头。

CORS 错误的解决方案

🌐 Solutions for CORS Errors

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

🌐 A. Enabling CORS in a server you control

正确且最简单的解决方案是通过从 Web 服务器或后端返回正确的响应头并响应预检请求来启用 CORS,因为这可以继续在 Angular 中使用 XMLHttpRequestfetch 或像 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
Androidhttp://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
Androidhttp://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
Androidhttp://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 serve适用于 Ionic Angular 应用的 http://localhost:4200

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

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

允许任何来源使用 Access-Control-Allow-Origin: * 在所有场景下都能保证工作,但可能会有安全隐患——例如某些 CSRF 攻击——这取决于服务器如何控制对资源的访问以及如何使用会话和 cookies。

🌐 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.

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

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

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!

如果你正在尝试连接第三方 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)

🌐 1. Native-only apps (iOS/Android)

🌐 Capacitor Applications (Recommended)

对于电容器应用,请使用 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

🌐 2. 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.

另外,请记住,浏览器或网页视图将不会接收原始的 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 代理不推荐。

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,但它们已经被弃用,很可能很快就会消失。像 iOS WKWebView 或 Android WebView(两者都被 Capacitor 使用)这样的现代 WebView 会强制执行 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