CORS 錯誤
什麼是 CORS?
跨來源資源共享 (CORS) 是一種機制,瀏覽器和網頁檢視(如支援 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 錯誤在網頁應用程式中很常見。
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
- 或者,如果使用
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-*
標頭,並了解正嘗試從 https://127.0.0.1:8100
發出具有自訂 Content-Type
的 POST
請求。
然後,伺服器會使用 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
- 未使用
ReadableStream
或XMLHttpRequestUpload
中的事件偵聽器。
在我們的範例 API 中,GET
請求不需要進行預檢,因為未傳送 JSON 資料,因此應用程式不需要使用 Content-Type: application/json
標頭。它們始終是簡單請求。
CORS 標頭
伺服器標頭 (回應)
標頭 | 值 | 描述 |
---|---|---|
Access-Control-Allow-Origin | origin 或 * | 指定要允許的來源,例如 https://127.0.0.1:8100 或 * ,以允許所有來源。 |
Access-Control-Allow-Methods | 方法 | 存取資源時允許的方法:GET 、HEAD 、POST 、PUT 、DELETE 、CONNECT 、OPTIONS 、TRACE 、PATCH 。 |
Access-Control-Allow-Headers | 標頭 | 在回應預檢請求時使用,以指示在發出實際請求時可以使用哪些標頭,除了始終允許的簡單標頭之外。 |
Access-Control-Allow-Credentials | true 或 false | 是否可以使用憑證發出請求。 |
Access-Control-Expose-Headers | 標頭 | 指定瀏覽器允許存取的標頭。 |
Access-Control-Max-Age | 秒數 | 指示預檢請求的結果可以快取多久。 |
瀏覽器標頭 (請求)
瀏覽器會在每次向伺服器發出的請求中,自動傳送適當的 CORS 標頭,包括預檢請求。請注意,以下標頭僅供參考,不應在您的應用程式程式碼中設定(瀏覽器會忽略它們)。
所有請求
標頭 | 值 | 描述 |
---|---|---|
Origin | 來源 | 指示請求的來源。 |
預檢請求
標頭 | 值 | 描述 |
---|---|---|
Access-Control-Request-Method | 方法 | 用於讓伺服器知道在發出實際請求時將使用哪個方法。 |
Access-Control-Request-Headers | 標頭 | 用於讓伺服器知道在發出實際請求時將使用哪些非簡單標頭。 |
CORS 錯誤的解決方案
A. 在您控制的伺服器中啟用 CORS
最正確且最簡單的解決方案是啟用 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 | https://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 應用程式,請使用 Capacitor HTTP API。此 API 會修補 fetch
和 XMLHttpRequest
以使用原生程式庫。請注意,如果您也將應用程式部署到基於網頁的環境 (例如 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 是一個非常糟糕的主意。您將暴露於各種攻擊之下,您不能要求您的使用者承擔風險,而且您的應用程式在生產環境中將無法運作。