CORS 错误
什么是 CORS?
**跨域资源共享 (CORS)** 是一种机制,浏览器和 Web 视图(例如为 Capacitor 和 Cordova 提供支持的那些)使用该机制出于安全原因限制从脚本到不同来源的资源的 HTTP 和 HTTPS 请求,主要是为了保护用户的 数据并防止会危及您的应用程序的攻击。
为了知道外部来源是否支持 CORS,服务器必须发送一些特殊标头,以便浏览器允许请求。
**来源** 是您的 Ionic 应用程序或外部资源提供的 **协议**、**域** 和 **端口** 的组合。例如,在 Capacitor 中运行的应用程序具有 capacitor://127.0.0.1
(iOS)或 https://127.0.0.1
(Android)作为其来源。
当您的应用程序提供的来源(例如使用 ionic serve
的 https://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
- 或者,如果使用了
ReadableStream
或XMLHttpRequestUpload
中的事件侦听器。
如果满足以上任何条件,则使用 OPTIONS
方法向资源 URL 发送预检请求。
假设我们正在向 https://api.example.com
上的虚构 JSON API 发出 POST
请求,其 Content-Type
为 application/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-Origin | origin 或 * | 指定允许的来源,例如 https://127.0.0.1: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 | 用于告知服务器在发出实际请求时将使用哪些非简单标头。 |
CORS 错误的解决方案
A. 在您控制的服务器中启用 CORS
正确且最简单的解决方案是通过返回来自 Web 服务器或后端的正确响应头并响应预检请求来启用 CORS,因为这允许继续使用XMLHttpRequest
、fetch
或 Angular 中的抽象(如HttpClient
)。
Ionic 应用可能从不同的来源运行,但只能在Access-Control-Allow-Origin
标头中指定一个来源。因此,我们建议检查请求中Origin
标头的值,并在响应中的Access-Control-Allow-Origin
标头中反映它。
请注意,所有Access-Control-Allow-*
标头都必须从服务器发送,并且不属于您的应用程序代码。
以下是一些您的 Ionic 应用可能从中提供的来源:
Capacitor
平台 | Origin |
---|---|
iOS | capacitor://127.0.0.1 |
Android | https://127.0.0.1 |
如果您在 Capacitor 配置中更改了默认值,请将localhost
替换为您自己的主机名。
Cordova 上的 Ionic WebView 3.x 插件
平台 | Origin |
---|---|
iOS | ionic://127.0.0.1 |
Android | https://127.0.0.1 |
如果您在插件配置中更改了默认值,请将localhost
替换为您自己的主机名。
Cordova 上的 Ionic WebView 2.x 插件
平台 | Origin |
---|---|
iOS | https://127.0.0.1:8080 |
Android | https://127.0.0.1:8080 |
如果您在插件配置中更改了默认值,请将端口8080
替换为您自己的端口。
浏览器中的本地开发
命令 | Origin |
---|---|
ionic serve | https://127.0.0.1:8100 或http://YOUR_MACHINE_IP:8100 |
npm run start 或ng 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 应用,使用Capacitor HTTP API。此 API 修补了fetch
和XMLHttpRequest
以使用原生库。请注意,如果您还将应用程序部署到基于 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 是一个非常糟糕的主意。您将面临各种攻击,您无法要求您的用户承担这种风险,并且您的应用程序在生产环境中将无法正常工作。