跳至主要内容
版本:v8

CORS 错误

什么是 CORS?

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

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

**来源** 是您的 Ionic 应用程序或外部资源提供的 **协议**、**域** 和 **端口** 的组合。例如,在 Capacitor 中运行的应用程序具有 capacitor://127.0.0.1(iOS)或 https://127.0.0.1(Android)作为其来源。

当您的应用程序提供的来源(例如使用 ionic servehttps://127.0.0.1:8100)和所请求资源的来源(例如 https://api.example.com)不匹配时,浏览器的同源策略生效,并且请求需要 CORS 才能完成。

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

注意

XMLHttpRequest 无法加载 https://api.example.com。请求的资源上没有“Access-Control-Allow-Origin”标头。因此,不允许来源 'https://127.0.0.1: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
  • 或者,如果使用了 ReadableStreamXMLHttpRequestUpload 中的事件侦听器。

如果满足以上任何条件,则使用 OPTIONS 方法向资源 URL 发送预检请求。

假设我们正在向 https://api.example.com 上的虚构 JSON API 发出 POST 请求,其 Content-Typeapplication/json。预检请求将类似于以下内容(出于清晰起见,省略了一些默认标头)

OPTIONS / HTTP/1.1
Host: api.example.com
Origin: https://127.0.0.1:8100
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type

如果服务器启用了 CORS,它将解析 Access-Control-Request-* 标头并了解 POST 请求尝试从 https://127.0.0.1:8100 发出,并具有自定义 Content-Type

然后,服务器将使用 Access-Control-Allow-* 标头来响应此预检,这些标头指示允许哪些来源、方法和标头。

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://127.0.0.1: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-Originorigin*指定允许的来源,例如 https://127.0.0.1:8100* 以允许所有来源。
Access-Control-Allow-Methodsmethods访问资源时允许使用哪些方法:GETHEADPOSTPUTDELETECONNECTOPTIONSTRACEPATCH
Access-Control-Allow-Headersheaders用于响应预检请求,指示在发出实际请求时可以使用哪些标头,除了简单标头,它们始终允许使用。
Access-Control-Allow-Credentialstruefalse请求是否可以使用凭据。
Access-Control-Expose-Headersheaders指定浏览器允许访问的标头。
Access-Control-Max-Ageseconds指示预检请求的结果可以缓存多长时间。

浏览器标头(请求)

浏览器会在向服务器发出的每个请求(包括预检请求)中自动发送适当的 CORS 标头。请注意,以下标头仅供参考,**不应在您的应用程序代码中设置**(浏览器会忽略它们)。

所有请求

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

预检请求

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

CORS 错误的解决方案

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

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

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

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

以下是一些您的 Ionic 应用可能从中提供的来源:

Capacitor

平台Origin
iOScapacitor://127.0.0.1
Androidhttps://127.0.0.1

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

Cordova 上的 Ionic WebView 3.x 插件

平台Origin
iOSionic://127.0.0.1
Androidhttps://127.0.0.1

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

Cordova 上的 Ionic WebView 2.x 插件

平台Origin
iOShttps://127.0.0.1:8080
Androidhttps://127.0.0.1:8080

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

浏览器中的本地开发

命令Origin
ionic servehttps://127.0.0.1:8100http://YOUR_MACHINE_IP:8100
npm run startng serve对于 Ionic Angular 应用,https://127.0.0.1:4200

如果您同时提供多个应用,则端口号可能会更高。

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

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

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

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

const allowedOrigins = [
'capacitor://127.0.0.1',
'ionic://127.0.0.1',
'https://127.0.0.1',
'https://127.0.0.1:8080',
'https://127.0.0.1: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

不要泄露您的密钥!

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

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

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

传统 Cordova 应用

对于传统 Cordova 应用,使用带有 Awesome Cordova Plugins 包装器的 HTTP 插件。请注意,此插件在浏览器中不起作用,因此应用程序的开发和测试必须始终在设备或模拟器中进行。

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

通过 HTTP/HTTPS 代理发送请求,该代理将它们绕过到外部资源并向响应添加必要的 CORS 标头。此代理必须是可信的或您控制的,因为它将拦截应用程序进行的大部分流量。

此外,请记住,如果提供代理,则浏览器或 Web 视图不会接收原始 HTTPS 证书,而是接收从代理发送的证书。您可能需要在代码中重写 URL 才能使用代理。

检查cors-anywhere以获取可以在您自己的服务器中部署的 Node.js CORS 代理。不建议在生产环境中使用免费托管的 CORS 代理。

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

请注意,CORS 的存在是有原因的(用户数据的安全以及防止对您的应用程序的攻击)。**尝试禁用 CORS 是不可能的或不可取的**。

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

如果您正在开发 PWA 或在浏览器中进行测试,则在 Google Chrome 中使用--disable-web-security标志或扩展来禁用 CORS 是一个非常糟糕的主意。您将面临各种攻击,您无法要求您的用户承担这种风险,并且您的应用程序在生产环境中将无法正常工作。

来源