跳至主要內容

標籤「安全性」的 2 篇文章

查看所有標籤

在 Amazon Cognito 設定密碼金鑰 (WebAuthn)

在使用 AWS SAM 建立的檔案儲存 API 的使用者認證中,我使用了 Amazon Cognito。最近我新增了透過密碼金鑰 (WebAuthn) 登入的功能,因此總結一下設定內容。

前提條件:使用密碼金鑰所需的 Cognito 設定

要在 Cognito 中使用密碼金鑰,必須具備以下所有條件。

要件本次的架構
UserPool 層級ESSENTIALS 以上
管理式登入v2 (新的登入 UI)
自訂網域login.example.com (用於 Relying Party ID)

Cognito 的密碼金鑰必須從管理式登入 v2 UI 中註冊及使用。在 LITE 層級 (免費) 中無法使用 WebAuthn,因此需要 ESSENTIALS 層級。

認證流程

密碼金鑰註冊流程

第一次登入時使用密碼,然後從帳戶設定中註冊密碼金鑰。

密碼金鑰登入流程

註冊後,可以透過「使用密碼金鑰登入」按鈕直接認證。

設定內容

為了新增密碼金鑰,我在 template.yaml (SAM 模板) 上進行了僅 6 行的變更。

變更前

UserPool:
Type: AWS::Cognito::UserPool
Properties:
# ...
Policies:
PasswordPolicy:
MinimumLength: 8
# ...
MfaConfiguration: "OFF"

變更後

UserPool:
Type: AWS::Cognito::UserPool
Properties:
# ...
Policies:
PasswordPolicy:
MinimumLength: 8
# ...
SignInPolicy:
AllowedFirstAuthFactors:
- PASSWORD
- WEB_AUTHN # ← 新增密碼金鑰
MfaConfiguration: "OFF"
WebAuthnRelyingPartyID: login.example.com # ← 指定 RP ID
WebAuthnUserVerification: required # ← 強制要求生物認證

各參數的意義

SignInPolicy.AllowedFirstAuthFactors

初步認證步驟中可用的認證方式列表。只有 PASSWORD 時僅能使用密碼,若加上 WEB_AUTHN 則能選擇密碼金鑰。

WebAuthnRelyingPartyID

WebAuthn 的 Relying Party ID (RP ID)。密碼金鑰將綁定到此網域生成和儲存,因此必須與 實際提供登入頁面的網域一致

本次直接使用自訂網域 login.example.com。若使用 Cognito 的預設網域 (xxx.auth.ap-northeast-1.amazoncognito.com),則需指定該網域。

WebAuthnUserVerification

使用密碼金鑰時的用戶確認等級。

說明
required強制要求生物認證或 PIN 等的身份確認
preferred儘可能要求身份確認,但可不要求
discouraged省略身份確認 (不使用生物認證等)

為了提高安全性,選擇了 required

管理式登入的 UI

在管理式登入 v2 的界面中,密碼金鑰設定後,登入畫面將自動新增「使用密碼金鑰登入」按鈕。第一次註冊時需先使用密碼登入,然後可在帳戶設定中新增密碼金鑰。

部署

sam build
sam deploy --no-confirm-changeset

由於 samconfig.toml 中已定義堆疊名稱、區域及參數,因此每次部署時不需要指定選項。

總結

啟用 Cognito 密碼金鑰的重點總結如下:

  1. 設定為 ESSENTIALS 層級 (LITE 不支持 WebAuthn)
  2. 使用 管理式登入 v2
  3. 指定自訂網域 (或 Cognito 預設網域) 為 RP ID
  4. SignInPolicy.AllowedFirstAuthFactors 中增加 WEB_AUTHN
  5. 使用 WebAuthnUserVerification: required 強制要求生物認證

僅透過 6 行的變更,即可使用密碼金鑰登入。此時仍保留密碼,逐步向密碼金鑰過渡,這正是 Cognito 的便利之處。

什麼是 CORS?從安全性角度為初學者解說

説明 Web 瀏覽器的安全功能 CORS (Cross-Origin Resource Sharing),涵蓋「為什麼需要」和「有什麼風險」,為初學者量身打造的解説。正確理解 CORS 能夠實現安全的 Web 開發。

CORS 出現的背景:同源政策 (Same-Origin Policy)

在 1990 年代初期,JavaScript 被引入瀏覽器時,Web 安全的概念幾乎不存在。當時,惡意網站可以自由存取其他網站的資料,容易導致工作階段劫持 (Session Hijacking) 和資料竊取。

為了解決這個問題,引入了一項稱為同源政策 (Same-Origin Policy) 的限制。這是一個簡單而強大的規則:「從網頁加載的 JavaScript 無法存取該網頁不同源的資料」。

例如,從 https://www.example.com 加載的 JavaScript 無法存取 https://www.bank.com 的資料。這樣,即使使用者登入銀行網站後訪問惡意網站,銀行資訊也不會被盜取。

什麼是源 (Origin)

源 (Origin) 由以下三個要素決定:

  • 協議 (Protocol)http://https://
  • 主機 (Host/網域)example.comapi.example.com
  • 連接埠 (Port)808080

例如,

URL協議主機連接埠
https://www.example.com/pageHTTPSwww.example.com443 (預設)https://www.example.com
https://api.example.com/dataHTTPSapi.example.com443 (預設)https://api.example.com

由於主機不同,它們被視為不同的源

同源政策防止的事項

JavaScript 的 XHR (XMLHttpRequest)Fetch API 對不同源的請求受到限制。

例:evil.com 上的惡意指令碼

fetch('https://bank.example.com/api/transfer', {
method: 'POST',
body: JSON.stringify({ amount: 1000000 })
});

沒有同源政策的話,惡意網站 (evil.com) 的 JavaScript 可以在使用者登入銀行網站的狀態下發送轉帳請求。防止這種情況就是同源政策的目的

為什麼需要 CORS

然而,現代 Web 開發經常涉及多個源的協作。

  • 前端https://www.example.com
  • API 伺服器https://api.example.com
  • CDN / 靜態文件https://cdn.example.com

這些是由同一公司營運的正當通訊。但如果受到同源政策限制,應用程式就無法運作。

這時候 CORS (Cross-Origin Resource Sharing) 就派上用場了。

什麼是 CORS:明確允許存取

CORS 是伺服器明確聲明「允許來自此源的請求」的機制

伺服器只需返回以下回應標頭,瀏覽器就會放寬限制。

Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type, Authorization

除非伺服器表示「允許」,否則瀏覽器不會將請求結果傳回 JavaScript。這樣既能實現跨源存取,又能保持安全性。

CORS 的安全風險:常見的設定錯誤

雖然 CORS 很便利,但設定不當會造成安全漏洞。

錯誤:允許所有源

Access-Control-Allow-Origin: *

這表示「世界上任何人都可以存取此伺服器」。

// https://evil.com 的 JavaScript
fetch('https://api.example.com/user/profile')
.then(r => r.json())
.then(data => {
// 竊取使用者個人資料的處理
console.log(data);
});

對於包含認證憑證(如 Cookie)的請求特別危險,使用者在登入 api.example.com 後訪問 evil.com 時,個人資訊可能被盜取。

fetch('https://api.example.com/user/profile', {
credentials: 'include' // 包含 Cookie
})

包含認證資訊時,不能使用 Access-Control-Allow-Origin: *。必須明確指定特定的源

Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Credentials: true

錯誤:直接允許使用者指定的 URL

// 危險的實現範例(伺服器端)
const origin = request.headers.get('Origin');
response.headers.set('Access-Control-Allow-Origin', origin); // 原樣返回!

這樣做會允許來自 https://evil.com 的請求,回應會帶有 Access-Control-Allow-Origin: https://evil.com,容易被濫用。

正確做法是準備白名單,只允許列表中的源

const allowedOrigins = [
'https://www.example.com',
'https://admin.example.com'
];

if (allowedOrigins.includes(origin)) {
response.headers.set('Access-Control-Allow-Origin', origin);
}

錯誤:允許所有標頭

Access-Control-Allow-Headers: *

這表示「接受任何標頭」,允許透過自訂標頭注入惡意資料的攻擊。

只列出必要的標頭

Access-Control-Allow-Headers: Content-Type, Authorization

CORS 預檢請求:瀏覽器的事前確認

對於簡單請求以外的請求(GET、HEAD、POST),瀏覽器會自動發送 OPTIONS 方法的請求來確認「這樣可以嗎?」這被稱為預檢請求 (Preflight Request)。

1. JavaScript 嘗試發送 PUT 請求

2. 瀏覽器自動發送 OPTIONS 預檢請求
OPTIONS /api/resource HTTP/1.1
Origin: https://www.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type

3. 伺服器回應「OK」
HTTP 200 OK
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods: PUT
Access-Control-Allow-Headers: Content-Type

4. 瀏覽器發送實際的 PUT 請求

如果伺服器不支援 OPTIONS 方法,預檢就會失敗,實際請求也不會被發送。

要點:

  • 在白名單中明確指定允許的源
  • 使用 credentials: true 處理包含 Cookie 認證的請求
  • 只允許必要的方法和標頭
  • OPTIONS 預檢請求

常見疑問

Q. 遇到 CORS 錯誤。可以允許所有源來解決嗎?

A: 不可以。可能會暫時有效,但在生產環境中允許 * 是安全風險。需要重新檢查伺服器端設定或重新設計 API。

Q. 想在本地開發時存取不同連接埠的源。可以嗎?

A: 在本地開發環境中只停用 CORS 是可以的。

Q. 從行動應用程式呼叫 API 時,CORS 有影響嗎?

A: CORS 是瀏覽器的安全功能,所以對行動應用程式沒有影響。取而代之,需要使用 API 金鑰或 OAuth 實作認證和授權。

總結

重點說明
CORS 的目的在保持瀏覽器安全性的同時允許跨源存取
危險的設定對需要認證的 API 使用 Access-Control-Allow-Origin: *
正確的設定在白名單中明確指定允許的源
包含 Cookie 時必須指定 Access-Control-Allow-Credentials: true 和具體的源
預檢PUT/DELETE 等複雜請求需要瀏覽器用 OPTIONS 進行事前確認

CORS 不只是「導致錯誤的原因」,而是 Web 安全的重要機制。設定錯誤可能導致安全事故,因此需要謹慎處理。

參考資料