使用 Application Load Balancer 來驗證使用者身分 - Elastic Load Balancing

本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。

使用 Application Load Balancer 來驗證使用者身分

您可以設定 Application Load Balancer,以在使用者存取應用程式時安全地驗證使用者身分。這可讓您將驗證使用者的工作卸載到您的負載平衡器,使得您的應用程式可以專注在商業邏輯上。

支援下列使用案例:

  • 透過與 OpenID Connect (OIDC) 相容的身分提供者 (IdP) 驗證使用者。

  • 透過 Amazon Cognito 支援的使用者集區 FaceBook,透過社交媒體 IdPs (例如亞馬遜或 Google) 對使用者進行身份驗證。

  • 透過 Amazon Cognito 支援的使用者集區,使用 SAML、OpenID Connect (OIDC) 或 OAuth,透過公司身分來驗證使用者身分。

準備使用 OIDC 合規 IdP

如果您使用 OIDC 合規 IdP 搭配 Application Load Balancer,請執行下列動作:

  • 在 IdP 中建立新 OIDC 應用程式。IdP 的 DNS 必須可公開解析。

  • 您必須設定用戶端 ID 和用戶端機密。

  • 取得 IdP 發佈的以下端點:授權、字符和使用者資訊。您可以在此組態中找到此資訊。

  • IdP 端點憑證的發行者應是受信任的公用憑證授權單位。

  • 端點的 DNS 項目必須可公開解析,即使會解析為私有 IP 地址也可以。

  • 允許以下其中一個重新導向 URL 加入您的使用者將使用的 IdP 應用程式,其中的 DNS 是您的負載平衡器的網域名稱,而 CNAME 為您的應用程式的 DNS 別名:

    • https://DNS/oauth2/idpresponse

    • https://CNAME/oauth2/idpresponse

準備使用 Amazon Cognito

下列區域提供適用於應用程式負載平衡器的 Amazon Cognito 整合:

  • 美國東部 (維吉尼亞北部)

  • 美國東部 (俄亥俄)

  • 美國西部 (加利佛尼亞北部)

  • 美國西部 (奧勒岡)

  • 加拿大 (中部)

  • 歐洲 (斯德哥爾摩)

  • 歐洲 (米蘭)

  • 歐洲 (法蘭克福)

  • 歐洲 (蘇黎世)

  • 歐洲 (愛爾蘭)

  • 歐洲 (倫敦)

  • Europe (Paris)

  • 南美洲 (聖保羅)

  • 亞太區域 (東京)

  • 亞太區域 (首爾)

  • 亞太區域 (大阪)

  • 亞太區域 (孟買)

  • 亞太區域 (新加坡)

  • 亞太區域 (悉尼)

  • 亞太區域 (雅加達)

  • 中東 (阿拉伯聯合大公國)

  • Middle East (Bahrain)

  • 非洲 (開普敦)

  • 以色列 (特拉維夫)

如果您使用與 Amazon Cognito 使用者集區搭配 Application Load Balancer,請執行下列動作:

  • 建立使用者集區。如需詳細資訊,請參閱《Amazon Cognito 開發人員指南》中的Amazon Cognito 使用者集區

  • 建立使用者集區用戶端。您必須設定用戶端來產生用戶端機密,使用代碼授予流程,並支援負載平衡器使用的相同 OAuth 範圍。如需詳細資訊,請參閱《Amazon Cognito 開發人員指南》中的設定使用者集區應用程式用戶端

  • 建立使用者集區物件網域。如需詳細資訊,請參閱《Amazon Cognito 開發人員指南》中的為使用者集區新增網域名稱

  • 驗證請求的範圍傳回 ID 字符。例如,預設範圍 openid 會傳回 ID 字符,但 aws.cognito.signin.user.admin 範圍則不會。

    備註:應用程式負載平衡器不支援 Amazon Cognito 發行的自訂存取權杖。如需詳細資訊,請參閱 Amazon Cognito 開發人員指南中的預先產生權杖

  • 若要聯合社交或公司 IdP,請在聯合區段啟用 IdP。如需詳細資訊,請參閱《Amazon Cognito 開發人員指南》中的新增社交登入至使用者集區以 SAML 身分提供者新增登入至使用者集區

  • 允許 Amazon Cognito 的回呼 URL 欄位中使用以下重新導向 URL,其中 DNS 是負載平衡器的網域名稱,而 CNAME 為應用程式的 DNS 別名 (如果您有使用):

    • https://DNS/oauth2/idpresponse

    • https://CNAME/oauth2/idpresponse

  • 允許 IdP 應用程式的回呼 URL 中使用使用者集區網域。針對 IdP 使用此格式。例如:

    • https://domain-prefix.auth.region.amazoncognito.com/saml2/idpresponse

    • https://user-pool-domain/oauth2/idpresponse

應用程式用戶端設定中的回呼 URL 必須全都使用小寫字母。

若要讓使用者設定負載平衡器以使用 Amazon Cognito 來驗證使用者身分,您必須授予使用者呼叫 cognito-idp:DescribeUserPoolClient 動作的許可。

準備使用 Amazon CloudFront

如果您在應用 Application Load Balancer 前面使用 CloudFront 散發,請啟用下列設定:

  • 轉寄要求標頭 (all) — 確保 CloudFront 不快取已驗證要求的回應。這可避免在驗證工作階段過期後從快取中提供回應。或者,若要在啟用快取時降低此風險, CloudFront 發行版的擁有者可以將 time-to-live (TTL) 值設定為在驗證 Cookie 到期之前過期。

  • 查詢字串轉送和快取 (全部) – 確保負載平衡器可存取向 ldP 驗證使用者身分時所需的查詢字串參數。

  • Cookie 轉送 (全部) — 確保將所有驗證 Cookie CloudFront 轉送至負載平衡器。

設定使用者身分驗證

您透過為一個或多個接聽程式規則建立驗證動作來設定使用者身分驗證。authenticate-cognitoauthenticate-oidc 動作類型僅支援使用 HTTPS 接聽程式。如需對應欄位的說明,請參閱 E lastic Load Balancing API 參考版本 2015-12- 01 AuthenticateOidcActionConfig中的AuthenticateCognitoActionConfig和。

負載平衡器會傳送工作階段 Cookie 給用戶端,以維護驗證狀態。此 Cookie 永遠包含 secure 屬性,因為使用者驗證需要 HTTPS 接聽程式。此 Cookie 包含具有 CORS (跨來源資源共享) 請求的 SameSite=None 屬性。

對於支援需要獨立用戶端身分驗證之多個應用程式的負載平衡器,每個具有身分驗證動作的接聽程式規則都應具有唯一的 Cookie 名稱。這可確保用戶端在路由傳送至規則中指定的目標群組之前,一律會使用 IdP 進行驗證。

Application Load Balancer 不支援 URL 編碼的 Cookie 值。

依預設,SessionTimeout 欄位會設為 7 天。如果您需要較短的工作階段,您可以將工作階段逾時設定為最短至 1 秒。如需詳細資訊,請參閱 工作階段逾時

根據您的應用程式適當地設定 OnUnauthenticatedRequest 欄位。例如:

  • 需要使用者使用社交或公司身分登入的應用程式 – 這是透過預設選項 (authenticate) 支援。如果使用者未登入,負載平衡器會將請求重新導向至 IdP 授權端點,並且 IdP 會提示使用者使用其使用者界面登入。

  • 為登入的使用者提供個人化檢視或對未登入的使用者提供一般檢視的應用程式 – 若要支援這類應用程式,請使用 allow 選項。如果使用者已登入,負載平衡器會提供使用者宣告,而應用程式可提供個人化的檢視。如果使用者未登入,負載平衡器會轉送不帶使用者宣告的請求,而應用程式可提供一般檢視。

  • 載入每隔幾秒鐘的單一頁面應用程式 — 如果您使用 JavaScript 此deny選項,負載平衡器會將 HTTP 401 未經授權的錯誤傳回給沒有驗證資訊的 AJAX 呼叫。但是,如果使用者具有已過期的身分驗證資訊,則負載平衡器會將用戶端重新導向至 IdP 授權端點。

負載平衡器必須能夠與 IdP 字符端點 (TokenEndpoint) 和 IdP 使用者資訊端點 (UserInfoEndpoint) 通訊。應用程式負載平衡器僅在與這些端點通訊時支援 IPv4。如果您的 IdP 使用公用位址,請確保負載平衡器的安全群組和 VPC 的網路 ACL 允許存取端點。使用內部負載平衡器或 IP 位址類型dualstack-without-public-ipv4,NAT 閘道可讓負載平衡器與端點通訊。如需詳細資訊,請參閱《Amazon VPC 使用者指南》中的 NAT 閘道基本概念

使用以下 create-rule 命令來設定使用者驗證。

aws elbv2 create-rule --listener-arn listener-arn --priority 10 \ --conditions Field=path-pattern,Values="/login" --actions file://actions.json

以下是指定 authenticate-oidc 動作和 forward 動作的 actions.json 檔案範例。AuthenticationRequestExtraParams 可讓您在身分驗證期間將額外的參數傳遞至 IdP。請依照身分提供者提供的文件來判斷受支援的欄位

[{ "Type": "authenticate-oidc", "AuthenticateOidcConfig": { "Issuer": "https://idp-issuer.com", "AuthorizationEndpoint": "https://authorization-endpoint.com", "TokenEndpoint": "https://token-endpoint.com", "UserInfoEndpoint": "https://user-info-endpoint.com", "ClientId": "abcdefghijklmnopqrstuvwxyz123456789", "ClientSecret": "123456789012345678901234567890", "SessionCookieName": "my-cookie", "SessionTimeout": 3600, "Scope": "email", "AuthenticationRequestExtraParams": { "display": "page", "prompt": "login" }, "OnUnauthenticatedRequest": "deny" }, "Order": 1 }, { "Type": "forward", "TargetGroupArn": "arn:aws:elasticloadbalancing:region-code:account-id:targetgroup/target-group-name/target-group-id", "Order": 2 }]

下列是會指定 authenticate-cognito 動作和 forward 動作的 actions.json 檔案的範例。

[{ "Type": "authenticate-cognito", "AuthenticateCognitoConfig": { "UserPoolArn": "arn:aws:cognito-idp:region-code:account-id:userpool/user-pool-id", "UserPoolClientId": "abcdefghijklmnopqrstuvwxyz123456789", "UserPoolDomain": "userPoolDomain1", "SessionCookieName": "my-cookie", "SessionTimeout": 3600, "Scope": "email", "AuthenticationRequestExtraParams": { "display": "page", "prompt": "login" }, "OnUnauthenticatedRequest": "deny" }, "Order": 1 }, { "Type": "forward", "TargetGroupArn": "arn:aws:elasticloadbalancing:region-code:account-id:targetgroup/target-group-name/target-group-id", "Order": 2 }]

如需詳細資訊,請參閱 接聽程式規則

身分驗證流程

下列網路圖是 Application Load Balancer 如何使用 OIDC 驗證使用者身分的視覺化表示。

Application Load Balancer 透過 OIDC 驗證使用者身分的方式

下面的帶編號項目著重解釋在前述網路圖中顯示的元素。

  1. 使用者將 HTTPS 請求傳送至在 Application Load Balancer 後方託管的網站。當規則的條件滿足某個驗證動作時,負載平衡器會請求標頭中檢查身分驗證工作階段 Cookie。

  2. 如果 Cookie 不存在,負載平衡器會將使用者重新導向到 IdP 授權端點,使得 IdP 可驗證使用者。

  3. 在使用者通過身分驗證之後,IdP 會將使用者重新傳送回負載平衡器並帶有一個授權授予代碼。

  4. 負載平衡器會向 IdP 權杖端點提供授權授予代碼。

  5. 在收到有效的授權授予代碼後,IdP 會向 Application Load Balancer 提供 ID 權杖和存取權杖。

  6. 然後,Application Load Balancer 會將存取權杖傳送到使用者資訊端點。

  7. 使用者資訊端點會將存取權杖交換為使用者宣告。

  8. Application Load Balancer 會將具有 AWSELB 身分驗證工作階段 Cookie 的使用者重新導向至原始 URI。由於大部分瀏覽器會將 Cookie 大小限制在 4K,負載平衡器會將大小大於 4K 的 Cookie 分成多個 Cookie 碎片。如果從 IdP 收到的使用者宣告和存取字符的總大小大於 11K 位元組,負載平衡器會傳回 HTTP 500 錯誤給用戶端,並且遞增 ELBAuthUserClaimsSizeExceeded 指標。

  9. Application Load Balancer 會驗證 Cookie,並將使用者資訊轉送至 X-AMZN-OIDC-* HTTP 標頭集中的目標。如需詳細資訊,請參閱 使用者宣告編碼和簽章驗證

  10. 此目標會將回應傳送至 Application Load Balancer。

  11. Application Load Balancer 會將最終回應傳送給使用者。

每個新的請求都會經歷步驟 1 到 11,而後續的請求則會經歷步驟 9 到 11。也就是說,只要 Cookie 尚未過期,每個後續請求都會從步驟 9 開始。

使用者在 IdP 進行身分驗證之後,系統會將 AWSALBAuthNonce Cookie 新增至請求標頭。這並不會變更 Application Load Balancer 處理來自 IdP 重新導向請求的方式。

如果 IdP 在 ID 字符中提供有效的重新整理字符,負載平衡器會儲存該重新整理字符,並在每次存取字符過期時使用它來重新整理使用者宣告,直到工作階段逾時或 IdP 重新整理失敗。如果使用者登出,重新整理會失敗,並且負載平衡器會將使用者重新導向至 IdP 授權端點。這可讓負載平衡器在使用者登出後捨棄工作階段。如需詳細資訊,請參閱 工作階段逾時

注意

Cookie 到期時間與身分驗證工作階段到期時間不同。Cookie 到期時間是 Cookie 的一種屬性,設為 7 天。身分驗證工作階段的實際長度取決於在 Application Load Balancer 上為身分驗證功能設定的工作階段逾時。此工作階段逾時包含在 Auth Cookie 值中,該值也經過加密。

使用者宣告編碼和簽章驗證

負載平衡器成功驗證使用者之後,它會將從 IdP 收到的使用者宣告傳送到目標。負載平衡器會簽署使用者宣告,讓應用程式可以驗證簽章並驗證宣告是由負載平衡器傳送。

負載平衡器會新增下列 HTTP 標頭:

x-amzn-oidc-accesstoken

來自字符端點的存取字符,純文字格式。

x-amzn-oidc-identity

來自使用者資訊端點的主旨欄位 (sub),純文字格式。

注意:子宣告是識別特定使用者的最佳方法。

x-amzn-oidc-data

使用者宣告,JSON Web 字符 (JWT) 格式。

存取權杖和使用者宣告與 ID 權杖不同。存取權杖和使用者宣告僅允許存取伺服器資源,而 ID 權杖會附帶其他資訊來對使用者進行身分驗證。應用程序負載平衡器在對用戶進行身份驗證時創建一個新的訪問令牌,並僅將訪問令牌和聲明傳遞給後端,但是它不會傳遞 ID 令牌信息。

這些字符採用 JWT 格式,但不是 ID 字符。JWT 格式包含使用 base64 URL 編碼的標頭、承載和簽章,而且尾端包含填補字元。Application Load Balancer 會使用 ES256 (使用 P-256 和 SHA256 的 ECDSA) 來產生 JWT 簽章。

JWT 標頭是 JSON 物件,具有下列欄位:

{ "alg": "algorithm", "kid": "12345678-1234-1234-1234-123456789012", "signer": "arn:aws:elasticloadbalancing:region-code:account-id:loadbalancer/app/load-balancer-name/load-balancer-id", "iss": "url", "client": "client-id", "exp": "expiration" }

JWT 承載為 JSON 物件,其中包含從 IdP 使用者資訊端點收到的使用者宣告。

{ "sub": "1234567890", "name": "name", "email": "alias@example.com", ... }

由於負載平衡器不會加密使用者宣告,我們建議您將目標群組設定為使用 HTTPS。如果將您的目標群組設定為使用 HTTP,請務必使用安全群組限制對您的負載平衡器的流量。

為了確保安全性,您必須在根據宣告執行任何授權之前驗證簽章,並驗證 JWT 標頭中的signer欄位是否包含預期的 Application Load Balancer ARN。

若要取得公有金鑰,請從 JWT 標頭取得金鑰 ID,並用其在端點查閱公有金鑰。每個 AWS 區域的端點如下:

https://public-keys.auth.elb.region.amazonaws.com/key-id

對於 AWS GovCloud (US),端點如下所示:

https://s3-us-gov-west-1.amazonaws.com/aws-elb-public-keys-prod-us-gov-west-1/key-id https://s3-us-gov-east-1.amazonaws.com/aws-elb-public-keys-prod-us-gov-east-1/key-id

下列範例顯示如何在 Python 3.x 中取得金鑰 ID、公有金鑰和承載:

import jwt import requests import base64 import json # Step 1: Validate the signer expected_alb_arn = 'arn:aws:elasticloadbalancing:region-code:account-id:loadbalancer/app/load-balancer-name/load-balancer-id' encoded_jwt = headers.dict['x-amzn-oidc-data'] jwt_headers = encoded_jwt.split('.')[0] decoded_jwt_headers = base64.b64decode(jwt_headers) decoded_jwt_headers = decoded_jwt_headers.decode("utf-8") decoded_json = json.loads(decoded_jwt_headers) received_alb_arn = decoded_json['signer'] assert expected_alb_arn == received_alb_arn, "Invalid Signer" # Step 2: Get the key id from JWT headers (the kid field) kid = decoded_json['kid'] # Step 3: Get the public key from regional endpoint url = 'https://public-keys.auth.elb.' + region + '.amazonaws.com/' + kid req = requests.get(url) pub_key = req.text # Step 4: Get the payload payload = jwt.decode(encoded_jwt, pub_key, algorithms=['ES256'])

下列範例顯示如何在 Python 2.7 中取得金鑰 ID、公有金鑰和承載:

import jwt import requests import base64 import json # Step 1: Validate the signer expected_alb_arn = 'arn:aws:elasticloadbalancing:region-code:account-id:loadbalancer/app/load-balancer-name/load-balancer-id' encoded_jwt = headers.dict['x-amzn-oidc-data'] jwt_headers = encoded_jwt.split('.')[0] decoded_jwt_headers = base64.b64decode(jwt_headers) decoded_json = json.loads(decoded_jwt_headers) received_alb_arn = decoded_json['signer'] assert expected_alb_arn == received_alb_arn, "Invalid Signer" # Step 2: Get the key id from JWT headers (the kid field) kid = decoded_json['kid'] # Step 3: Get the public key from regional endpoint url = 'https://public-keys.auth.elb.' + region + '.amazonaws.com/' + kid req = requests.get(url) pub_key = req.text # Step 4: Get the payload payload = jwt.decode(encoded_jwt, pub_key, algorithms=['ES256'])
考量事項
  • 這些範例並不包括如何使用權杖中的簽章來驗證發行者的簽章。

  • 標準程式庫與 JWT 格式的 Application Load Balancer 驗證權杖中包含的填補不相容。

逾時

工作階段逾時

重新整理字符和工作階段逾時的共同運作方式如下所示:

  • 如果工作階段逾時短於存取字符過期,負載平衡器會採用該工作階段逾時。如果使用者有 IdP 的主動工作階段,應該就不會提示使用者再次登入。否則,系統會將使用者重新導向至登入。

    • 如果 IdP 工作階段逾時較 Application Load Balancer 工作階段逾時更久,則使用者不需要提供憑證即可重新登入。而且,IdP 會使用新的授權授予代碼重新導向至 Application Load Balancer。即使沒有重新登入,授權碼也只能使用一次。

    • 如果 IdP 工作階段逾時較 Application Load Balancer 工作階段逾時一樣久或更久,則會要求使用者提供憑證以重新登入。使用者登入後,IdP 會使用新的授權授予代碼重新導向至 Application Load Balancer,其餘的身分驗證流程會繼續進行,直到請求到達後端為止。

  • 如果工作階段逾時較存取權杖過期時間更久,並且 IdP 不支援重新整理權杖,負載平衡器會保留該身分驗證工作階段,直到其逾時為止。然後,負載平衡器會要求使用者再次登入。

  • 如果工作階段逾時為長於存取字符過期,並且 IdP 支援重新整理字符,負載平衡器會在每次存取字符過期時重新整理該使用者工作階段。負載平衡器只會在驗證工作階段逾或重新整理流程失敗時,才會將使用者再次登入。

用戶端登入逾時

用戶端必須在 15 分鐘內啟動並完成身分驗證程序。如果用戶端無法在 15 分鐘的限制內完成身分驗證,則會從負載平衡器收到 HTTP 401 錯誤。此逾時限制無法變更或移除。

例如,如果使用者透過 Application Load Balancer 載入登入頁面,則必須在 15 分鐘內完成登入程序。如果使用者在 15 分鐘逾時過期後嘗試登入,負載平衡器會傳回 HTTP 401 錯誤。使用者必須重新整理頁面並嘗試再次登入。

身分驗證登出

當應用程式需要將已驗證的使用者登出時,您應該將驗證工作階段 Cookie 的過期時間設定為 -1,並將用戶端重新導向至 IdP 登出端點 (如果 IdP 有支援)。為了防止使用者重複使用已刪除的 Cookie,建議您將存取字符設定為合理的簡短過期時間。如果用戶端向負載平衡器提供的工作階段 Cookie 具有過期的存取權杖 (具有非 NULL 重新整理權杖),負載平衡器會聯絡 IdP 以判斷使用者是否仍處於登入狀態。

用戶端登出登陸頁面是未經驗證的頁面。這表示其不能位於需要身分驗證的 Application Load Balancer 規則之後。

  • 請求傳送至目標後,應用程式必須將所有身分驗證 Cookie 的到期時間設定為 -1。Application Load Balancer 支援的 Cookie 大小上限為 16K,因此最多會建立 4 個碎片以傳送給用戶端。

    • 如果 IdP 具有登出端點,則應發出一個至 IdP 登出端點 (例如《Amazon Cognito 開發人員指南》中記錄的登出端點) 的重新導向。

    • 如果 IdP 沒有登出端點,請求會回到用戶端登出登陸頁面,並登入程序會重新啟動。

  • 假設 IdP 具有登出端點,則 IdP 必須使存取權杖和重新整理權杖過期,並將使用者重新導向回用戶端登出登陸頁面。

  • 後續請求會遵循原始身分驗證流程。