跳至主要內容
版本:v8

CORS 錯誤

什麼是 CORS?

跨來源資源共享 (CORS) 是一種機制,瀏覽器和網頁檢視(如支援 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 錯誤在網頁應用程式中很常見。

注意

XMLHttpRequest 無法載入 https://api.example.com。所請求的資源上沒有 'Access-Control-Allow-Origin' 標頭。因此,不允許來源 'https://127.0.0.1:8100' 存取。

CORS 如何運作

帶有預檢的請求

預設情況下,當網頁應用程式嘗試發出跨來源請求時,瀏覽器會在實際請求之前傳送預檢請求。需要此預檢請求,以了解外部資源是否支援 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-* 標頭,並了解正嘗試從 https://127.0.0.1:8100 發出具有自訂 Content-TypePOST 請求。

然後,伺服器會使用 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
  • 未使用 ReadableStreamXMLHttpRequestUpload 中的事件偵聽器。

在我們的範例 API 中,GET 請求不需要進行預檢,因為未傳送 JSON 資料,因此應用程式不需要使用 Content-Type: application/json 標頭。它們始終是簡單請求。

CORS 標頭

伺服器標頭 (回應)

標頭描述
Access-Control-Allow-Originorigin*指定要允許的來源,例如 https://127.0.0.1:8100*,以允許所有來源。
Access-Control-Allow-Methods方法存取資源時允許的方法:GETHEADPOSTPUTDELETECONNECTOPTIONSTRACEPATCH
Access-Control-Allow-Headers標頭在回應預檢請求時使用,以指示在發出實際請求時可以使用哪些標頭,除了始終允許的簡單標頭之外。
Access-Control-Allow-Credentialstruefalse是否可以使用憑證發出請求。
Access-Control-Expose-Headers標頭指定瀏覽器允許存取的標頭。
Access-Control-Max-Age秒數指示預檢請求的結果可以快取多久。

瀏覽器標頭 (請求)

瀏覽器會在每次向伺服器發出的請求中,自動傳送適當的 CORS 標頭,包括預檢請求。請注意,以下標頭僅供參考,不應在您的應用程式程式碼中設定(瀏覽器會忽略它們)。

所有請求

標頭描述
Origin來源指示請求的來源。

預檢請求

標頭描述
Access-Control-Request-Method方法用於讓伺服器知道在發出實際請求時將使用哪個方法。
Access-Control-Request-Headers標頭用於讓伺服器知道在發出實際請求時將使用哪些非簡單標頭。

CORS 錯誤的解決方案

A. 在您控制的伺服器中啟用 CORS

最正確且最簡單的解決方案是啟用 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 servehttps://127.0.0.1:4200,適用於 Ionic Angular 應用程式。

如果您同時提供多個應用程式,連接埠號碼可能會更高。

允許任何來源使用 Access-Control-Allow-Origin: * 保證在所有情況下都能運作,但可能會產生安全隱患,例如某些 CSRF 攻擊,具體取決於伺服器如何控制對資源的存取以及使用會話和 Cookie。

有關如何在不同的網頁和應用程式伺服器中啟用 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 以使用原生程式庫。請注意,如果您也將應用程式部署到基於網頁的環境 (例如 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 Proxy 發送請求,該 Proxy 會將它們繞過到外部資源,並將必要的 CORS 標頭新增至回應中。此 Proxy 必須是受信任的或在您的控制之下,因為它會攔截應用程式產生的大部分流量。

此外,請記住,瀏覽器或 WebView 不會收到原始的 HTTPS 憑證,而是收到 Proxy 發送的憑證 (如果已提供)。可能需要在您的程式碼中重寫 URL,才能使用 Proxy。

請參閱 cors-anywhere,了解可以在您自己的伺服器中部署的 Node.js CORS Proxy。不建議在生產環境中使用免費託管的 CORS Proxy。

C. 停用 CORS 或瀏覽器網頁安全

請注意,CORS 的存在是有原因的 (使用者資料的安全以及防止針對您的應用程式進行攻擊)。嘗試停用 CORS 是不可能或不建議的

舊版 WebView (例如 iOS 上的 UIWebView) 不會強制執行 CORS,但已過時且很可能很快就會消失。現代 WebView (例如 iOS WKWebView 或 Android WebView,兩者都由 Capacitor 使用) 會強制執行 CORS,並提供巨大的安全性和效能改進。

如果您正在開發 PWA 或在瀏覽器中測試,則在 Google Chrome 中使用 --disable-web-security 標誌或使用擴充功能停用 CORS 是一個非常糟糕的主意。您將暴露於各種攻擊之下,您不能要求您的使用者承擔風險,而且您的應用程式在生產環境中將無法運作。

來源