如何使用 HTTPOnly Cookie 啟用 CORS 以保護令牌?

已發表: 2021-12-29

在本文中,我們將了解如何使用 HTTPOnly cookie 啟用 CORS(跨源資源共享)以保護我們的訪問令牌。

如今,後端服務器和前端客戶端部署在不同的域中。 因此,服務器必須啟用 CORS 以允許客戶端在瀏覽器上與服務器通信。

此外,服務器正在實施無狀態身份驗證以獲得更好的可擴展性。 令牌在客戶端存儲和維護,但不像會話那樣在服務器端存儲和維護。 為了安全起見,最好將令牌存儲在 HTTPOnly cookie 中。

為什麼跨域請求會被阻止?

假設我們的前端應用程序部署在https://app.geekflare.comhttps://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 請求:

  • 簡單的請求
  • 預檢請求

簡單請求:

CORS-simple request flow 告訴它發送一個跨域請求,但是當它收到響應時。它檢查標題。

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

預檢請求:

CORS-Preflight Request Image which show the flow of cross-origin request with OPTIONS preflight request before sending actual request for verifying headers.

  • 取決於來自跨域請求的自定義請求參數,如方法(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.comhttps://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 進行跨域請求。