跳到主要内容
版本:v8

CORS 错误

什么是 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 才能发出。

在 Web 应用程序中,当发起跨域请求但服务器未在响应中返回必需的头部(未启用 CORS)时,就会发生 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 头部之外的 Content-Type 头部:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain
  • 或者如果使用了 ReadableStreamXMLHttpRequestUpload 中的事件监听器。

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

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

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 请求。

然后,服务器将使用 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
  • 未使用 ReadableStreamXMLHttpRequestUpload 中的事件监听器。

在我们的示例 API 中,GET 请求不需要预检,因为不发送 JSON 数据,因此应用程序不需要使用 Content-Type: application/json 头部。它们将始终是简单请求。

CORS 头部

服务器头部(响应)

HeaderValue描述
Access-Control-Allow-Originorigin*指定允许的源,例如 http://localhost:8100* 允许所有源。
Access-Control-Allow-Methodsmethods访问资源时允许的方法:GETHEADPOSTPUTDELETECONNECTOPTIONSTRACEPATCH
Access-Control-Allow-Headersheaders用于响应预检请求,指示在发出实际请求时可以使用哪些头部(除了简单头部之外,简单头部始终允许)。
Access-Control-Allow-Credentialstruefalse是否可以携带凭据发出请求。
Access-Control-Expose-Headersheaders指定浏览器允许访问的头部。
Access-Control-Max-Ageseconds指示预检请求的结果可以缓存多长时间。

浏览器头部(请求)

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

所有请求

HeaderValue描述
Originorigin指示请求的源。

预检请求

HeaderValue描述
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

平台
iOScapacitor://localhost
Androidhttp://localhost

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

Cordova 上的 Ionic WebView 3.x 插件

平台
iOSionic://localhost
Androidhttp://localhost

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

Cordova 上的 Ionic WebView 2.x 插件

平台
iOShttp://localhost:8080
Androidhttp://localhost:8080

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

浏览器中的本地开发

命令
ionic servehttp://localhost:8100http://YOUR_MACHINE_IP:8100
npm run startng serveIonic Angular 应用程序的 http://localhost:4200

如果您同时运行多个应用程序,端口号可能会更高。

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

有关如何在不同的 Web 和应用程序服务器中启用 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',
];

// 如果源在允许列表中或未定义(cURL、Postman 等),则反射该源
const corsOptions = {
origin: (origin, callback) => {
if (allowedOrigins.includes(origin) || !origin) {
callback(null, true);
} else {
callback(new Error('Origin not allowed by CORS'));
}
},
};

// 为所有路由启用预检请求
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 应用程序,请使用 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 数据
console.log(response.headers);
} catch (error) {
console.error(error.status);
console.error(error.error); // 错误消息字符串
console.error(error.headers);
}
}
}

2. 原生 + PWA

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

此外,请注意,浏览器或 WebView 将不会收到原始的 HTTPS 证书,而是代理发送的证书(如果提供)。您的代码中的 URL 可能需要重写以使用代理。

查看 cors-anywhere,这是一个可以在您自己的服务器上部署的 Node.js CORS 代理。不建议在生产环境中使用免费的托管 CORS 代理。

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

请注意,CORS 的存在是有原因的(保护用户数据并防止对应用程序的攻击)。尝试禁用 CORS 是不可能的或不建议的

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

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

来源