如何使用 HTTPOnly Cookie 啟用 CORS 以保護令牌?
已發表: 2021-12-29在本文中,我們將了解如何使用 HTTPOnly cookie 啟用 CORS(跨源資源共享)以保護我們的訪問令牌。
如今,後端服務器和前端客戶端部署在不同的域中。 因此,服務器必須啟用 CORS 以允許客戶端在瀏覽器上與服務器通信。
此外,服務器正在實施無狀態身份驗證以獲得更好的可擴展性。 令牌在客戶端存儲和維護,但不像會話那樣在服務器端存儲和維護。 為了安全起見,最好將令牌存儲在 HTTPOnly cookie 中。
為什麼跨域請求會被阻止?
假設我們的前端應用程序部署在https://app.geekflare.com 。 https://app.geekflare.com加載的腳本只能請求同源資源。
每當我們嘗試向另一個域https://api.geekflare.com或另一個端口https://app.geekflare.com:3000或另一個方案http://app.geekflare.com發送跨域請求時,跨域請求將被瀏覽器阻止。
但是為什麼瀏覽器阻止的相同請求使用 curl 請求從任何後端服務器發送或使用郵遞員等工具發送而沒有任何 CORS 問題。 它實際上是為了安全保護用戶免受CSRF(跨站點請求偽造)之類的攻擊。
讓我們舉個例子,假設任何用戶在他們的瀏覽器中登錄了他們自己的 PayPal 賬戶。 如果我們可以從加載在另一個域malicious.com .com 上的腳本向paypal.com發送跨域請求,而沒有任何 CORS 錯誤/阻塞,就像我們發送同源請求一樣。
攻擊者可以通過將其轉換為短 URL 以隱藏實際 URL 來輕鬆發送他們的惡意頁面https://malicious.com/transfer-money-to-attacker-account-from-user-paypal-account 。 當用戶點擊一個惡意鏈接,該腳本域中加載malicious.com將發送跨域請求到PayPal用戶量轉移到攻擊者的PayPal帳戶將得到執行。 所有登錄到其 PayPal 帳戶並單擊此惡意鏈接的用戶都將損失金錢。 任何人都可以在 PayPal 帳戶用戶不知情的情況下輕鬆竊取資金。
由於上述原因,瀏覽器會阻止所有跨域請求。
什麼是CORS(跨域資源共享)?
CORS 是一種基於標頭的安全機制,服務器使用它來告訴瀏覽器從受信任的域發送跨域請求。
啟用了 CORS 標頭的服務器用於避免瀏覽器阻止跨域請求。
CORS 是如何工作的?
因為服務器已經在其 CORS 配置中定義了其受信任的域。 當我們向服務器發送請求時,響應將在其標頭中告訴瀏覽器所請求的域是否可信。
有兩種類型的 CORS 請求:
- 簡單的請求
- 預檢請求
簡單請求:

- 瀏覽器將請求發送到具有origin(https://app.geekflare.com) 的跨域域。
- 服務器用允許的方法和允許的來源發回相應的響應。
- 瀏覽器收到請求後,會檢查發送的origin header值( https://app.geekflare.com )和收到的access-control-allow-origin value( https://app.geekflare.com )是否相同或通配符(*)。 否則,它會拋出 CORS 錯誤。
預檢請求:

- 取決於來自跨域請求的自定義請求參數,如方法(PUT、DELETE)或自定義標頭或不同的內容類型等。瀏覽器將決定發送預檢 OPTIONS 請求以檢查實際請求是否可以安全發送或不。
- 瀏覽器收到響應(狀態碼:204,無內容)後,會檢查實際請求的access-control-allow參數。 如果服務器允許請求參數。 實際發送和接收的跨域請求
如果access-control-allow-origin: * ,則所有來源都允許響應。 但除非你需要它,否則它並不安全。
如何啟用 CORS?
要為任何域啟用 CORS,請啟用 CORS 標頭以允許來源、方法、自定義標頭、憑據等。
瀏覽器從服務器讀取 CORS 頭,只有在驗證請求參數後才允許來自客戶端的實際請求。
- Access-Control-Allow-Origin:指定確切的域(https ://app.geekflate.com、https: //lab.geekflare.com)或通配符(*)
- Access-Control-Allow-Methods:允許只有我們需要的 HTTP 方法(GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS)。
- Access-Control-Allow-Headers:只允許特定的 Headers(Authorization, csrf-token)
- Access-Control-Allow-Credentials:布爾值,用於允許跨源憑證(cookies、授權標頭)。
- Access-Control-Max-Age:告訴瀏覽器緩存預檢響應一段時間。
- Access-Control-Expose-Headers:指定可由客戶端腳本訪問的標頭。
要在 apache 和 Nginx 網絡服務器中啟用 CORS,請按照本教程進行操作。
在 ExpressJS 中啟用 CORS
讓我們舉一個沒有 CORS 的 ExpressJS 應用程序示例:
const express = require('express'); const app = express() app.get('/users', function (req, res, next) { res.json({msg: 'user get'}) }); app.post('/users', function (req, res, next) { res.json({msg: 'user create'}) }); app.put('/users', function (req, res, next) { res.json({msg: 'User update'}) }); app.listen(80, function () { console.log('CORS-enabled web server listening on port 80') })在上面的示例中,我們為 POST、PUT、GET 方法啟用了用戶 API 端點,但未啟用 DELETE 方法。
為了在 ExpressJS 應用程序中輕鬆啟用 CORS,您可以安裝cors
npm install cors訪問控制允許來源
為所有域啟用 CORS
app.use(cors({ origin: '*' }));為單個域啟用 CORS
app.use(cors({ origin: 'https://app.geekflare.com' }));如果您想允許源https://app.geekflare.com和https://lab.geekflare.com 的CORS
app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ] }));訪問控制允許方法
要為所有方法啟用 CORS,請在 ExpressJS 的 CORS 模塊中省略此選項。 但是為了啟用特定方法(GET、POST、PUT)。
app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'] }));訪問控制允許標題
用於允許除默認值之外的標頭與實際請求一起發送。
app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'] }));訪問控制允許憑據
如果您不想告訴瀏覽器在請求時允許憑據(即使withCredentials設置為 true ),請忽略此項。

app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true }));訪問控制最大年齡
提示瀏覽器將預檢響應信息緩存在緩存中指定秒。 如果您不想緩存響應,請忽略此項。
app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600 }));緩存的預檢響應將在瀏覽器中可用 10 分鐘。
訪問控制公開標題
app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600, exposedHeaders: ['Content-Range', 'X-Content-Range'] }));如果我們將通配符(*) 放在exposedHeaders 中,它就不會暴露Authorization 標頭。 所以我們必須像下面這樣顯式地公開
app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600, exposedHeaders: ['*', 'Authorization', ] }));以上也將公開所有標頭和授權標頭。
什麼是 HTTP cookie?
cookie 是服務器將發送到客戶端瀏覽器的一小段數據。 在以後的請求中,瀏覽器將在每個請求中發送與同一域相關的所有 cookie。
Cookie 有它的屬性,可以定義它以使 cookie 以我們需要的方式工作。
- 名稱cookie 的名稱。
- value: cookie-name對應的cookie數據
- 域: cookie 將僅發送到定義的域
- 路徑: cookies 僅在定義的 URL 前綴路徑之後發送。 假設我們已經定義了我們的 cookie 路徑,如 path='admin/'。 Cookie 不是針對 URL https://geekflare.com/expire/ 發送的,而是使用 URL 前綴 https://geekflare.com/admin/ 發送的
- Max-Age/Expires(number in second): cookie 何時到期。 cookie 的生命週期使 cookie 在指定時間後失效。
- HTTPOnly(Boolean):後端服務器可以訪問該 HTTPOnly cookie,但當為 true 時不能訪問客戶端腳本。
- 安全(布爾): Cookie 僅在為真時通過 SSL/TLS 域發送。
- sameSite(string [Strict, Lax, None]):用於啟用/限制跨站點請求發送的 cookie。 要了解有關 cookie
sameSite更多詳細信息,請參閱 MDN。 它接受三個選項 Strict、Lax、None。 對於 cookie 配置 sameSite=None,cookie 安全值設置為 true。
為什麼 HTTPOnly cookie 用於令牌?
將服務器發送的訪問令牌存儲在客戶端存儲中,如本地存儲、索引數據庫和cookie (HTTPOnly 未設置為 true)更容易受到 XSS 攻擊。 假設您的任何一個頁面是否容易受到 XSS 攻擊。 攻擊者可能會濫用瀏覽器中存儲的用戶令牌。
HTTPOnly cookie 僅由服務器/後端設置/獲取,而不是在客戶端。
客戶端腳本被限制訪問該 HTTPonly cookie。 因此 HTTPOnly cookie 不易受到 XSS 攻擊,並且更安全。 因為它只能由服務器訪問。
在啟用 CORS 的後端啟用 HTTPOnly cookie
在 CORS 中啟用 Cookie 需要在應用程序/服務器中進行以下配置。
- 將 Access-Control-Allow-Credentials 標頭設置為 true。
- Access-Control-Allow-Origin 和 Access-Control-Allow-Headers 不應是通配符 (*)。
- Cookie sameSite 屬性應為 None。
- 要將 sameSite 值啟用為 none,請將安全值設置為 true:使用 SSL/TLS 證書啟用後端以在域名中工作。
讓我們看看在檢查登錄憑據後在 HTTPOnly cookie 中設置訪問令牌的示例代碼。
const express = require('express'); const app = express(); const cors = require('cors'); app.use(cors({ origin: [ 'https://app.geekflare.com', 'https://lab.geekflare.com' ], methods: ['GET', 'PUT', 'POST'], allowedHeaders: ['Content-Type', 'Authorization', 'x-csrf-token'], credentials: true, maxAge: 600, exposedHeaders: ['*', 'Authorization' ] })); app.post('/login', function (req, res, next) { res.cookie('access_token', access_token, { expires: new Date(Date.now() + (3600 * 1000 * 24 * 180 * 1)), //second min hour days year secure: true, // set to true if your using https or samesite is none httpOnly: true, // backend only sameSite: 'none' // set to none for cross-request }); res.json({ msg: 'Login Successfully', access_token }); }); app.listen(80, function () { console.log('CORS-enabled web server listening on port 80') });您可以通過在後端語言和網絡服務器中實現上述四個步驟來配置 CORS 和 HTTPOnly cookie。
您可以按照 apache 和 Nginx 的本教程按照上述步驟啟用 CORS。
跨域請求的 withCredentials
Credentials(Cookie, Authorization) 默認與同源請求一起發送。 對於跨域,我們必須將 withCredentials 指定為 true。
XMLHttpRequest API:
var xhr = new XMLHttpRequest(); xhr.open('GET', 'http://api.geekflare.com/user', true); xhr.withCredentials = true; xhr.send(null);獲取 API:
fetch('http://api.geekflare.com/user', { credentials: 'include' });jQuery Ajax:
$.ajax({ url: 'http://api.geekflare.com/user', xhrFields: { withCredentials: true } });軸:
axios.defaults.withCredentials = true結論
我希望上面的文章可以幫助您了解 CORS 的工作原理,並為服務器中的跨域請求啟用 CORS。 為什麼在 HTTPOnly 中存儲 cookie 是安全的,以及如何在客戶端中使用 withCredentials 進行跨域請求。
