使用 Application Load Balancer 验证用户身份 - Elastic Load Balancing
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅中国的 Amazon Web Services 服务入门

使用 Application Load Balancer 验证用户身份

可以将 Application Load Balancer 配置为在用户访问应用程序时安全验证用户的身份。这使您可以将验证用户身份的工作交给负载均衡器完成,以便应用程序可以专注于其业务逻辑。

支持以下使用案例:

  • 通过符合 OpenID Connect (OIDC) 条件的身份提供商 (IdP) 验证用户身份。

  • 通过社交 IdP(如 Amazon、Facebook 或 Google)、通过 Amazon Cognito 支持的用户池验证用户的身份。

  • 通过企业身份、使用 SAML、LDAP 或 Microsoft AD、通过 Amazon Cognito 支持的用户池验证用户身份。

准备使用符合 OIDC 条件的 IdP

如果要将符合 OIDC 条件的 IdP 与 Application Load Balancer 一起使用,请执行以下操作:

  • 使用 IdP 创建新的 OIDC 应用程序。必须配置客户端 ID 和客户端密钥。

  • 获取 IdP 发布的以下终端节点:授权终端节点、令牌终端节点和用户信息终端节点。可以在配置中找到此信息。

  • 允许在 IdP 应用程序中使用以下重定向 URL 之一(无论您的用户将使用哪种 IdP 应用程序),其中 DNS 是负载均衡器的域名,CNAME 是应用程序的 DNS 别名:

    • https://DNS/oauth2/idpresponse

    • https://CNAME/oauth2/idpresponse

准备使用 Amazon Cognito

如果您将 Amazon Cognito 用户池与 Application Load Balancer 结合使用,请执行以下操作:

  • 创建用户池。有关更多信息,请参阅 Amazon Cognito 开发人员指南中的 Amazon Cognito 用户池

  • 创建用户池客户端。必须将客户端配置为生成客户端密钥,使用代码授予流程并支持与负载均衡器所用相同的 OAuth 范围。有关更多信息,请参阅 Amazon Cognito 开发人员指南中的配置用户池应用程序客户端

  • 创建用户池域。有关更多信息,请参阅 Amazon Cognito 开发人员指南中的为用户池添加域名

  • 验证请求的范围是否将返回 ID 令牌。例如,默认范围 openid 将返回 ID 令牌,但 aws.cognito.signin.user.admin 范围不返回 ID 令牌。

  • 要与社交或企业 IdP 联合,请在联合身份验证部分中启用 IdP。有关更多信息,请参阅 Amazon Cognito 开发人员指南中的将社交登录添加到用户池将“使用 SAML IdP 登录”添加到用户池

  • 允许在 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

要使 IAM 用户能够将负载均衡器配置为使用 Amazon Cognito 验证用户身份,必须授予该用户调用 cognito-idp:DescribeUserPoolClient 操作的权限。

准备使用 Amazon CloudFront

如果您在 Application Load Balancer 之前使用 CloudFront 分配,请启用以下设置:

  • 转发请求标头(全部)– 确保 CloudFront 不缓存经过身份验证的请求的响应。这可避免在身份验证会话过期后从缓存提供响应。或者,要在启用缓存时降低此风险,CloudFront 分配的所有者可以将生存时间 (TTL) 值设置为在身份验证 Cookie 过期之前到期。

  • 查询字符串转发和缓存(全部)– 确保负载均衡器能够访问使用 IdP 对用户进行身份验证所需的查询字符串参数。

  • Cookie 转发(全部)– 确保 CloudFront 将所有身份验证 Cookie 转发到负载均衡器。

配置用户身份验证

通过为一个或多个侦听器规则创建身份验证操作来配置用户身份验证。HTTPS 侦听器仅支持 authenticate-cognitoauthenticate-oidc 操作类型。有关对应字段的描述,请参阅 Elastic Load Balancing API 参考版本 2015-12-01 中的 AuthenticateCognitoActionConfigAuthenticateOidcActionConfig

负载均衡器会向客户端发送会话 Cookie 以保持身份验证状态。由于用户身份验证需要 HTTPS 侦听器,因此该 Cookie 始终包含 secure 属性。此 Cookie 包含 CORS(跨源资源共享)请求的 SameSite=None 属性。

对于支持多个需要独立客户端身份验证的应用程序的负载均衡器,具有身份验证操作的每个侦听器规则应具有唯一的 Cookie 名称 这可确保客户端在路由到规则中指定的目标组之前始终使用 IdP 进行身份验证。

Application Load Balancer 不支持 URL 编码的 Cookie 值。

默认情况下,SessionTimeout 字段设置为 7 日。如果需要更短的会话,可将会话超时配置为短至 1 秒。有关更多信息,请参阅 会话超时

视应用程序的情况设置 OnUnauthenticatedRequest 字段。例如:

  • 需要用户使用社交或企业身份登录的应用程序 – 这由默认选项 authenticate 支持。如果用户未登录,则负载均衡器会将请求重定向到 IdP 授权终端节点并且 IdP 将提示用户使用其用户界面登录。

  • 为已登录用户提供个性化视图或为未登录用户提供常规视图的应用程序 – 要支持此类型的应用程序,请使用 allow 选项。如果用户已登录,则负载均衡器将提供用户索赔并且应用程序可以提供个性化视图。如果用户未登录,则负载均衡器将转发请求而不提供用户索赔并且应用程序可以提供常规视图。

  • 具有每隔几秒钟就加载的 JavaScript 的单页应用程序 – 如果您使用 deny 选项,负载均衡器将向没有身份验证信息的 AJAX 调用返回“HTTP 401 Unauthorized(HTTP 401 未授权)”错误。但是,如果用户的身份验证信息已过期,它会将客户端重定向到 IdP 授权终端节点。

负载均衡器必须能够与 IdP 令牌终端节点 (TokenEndpoint) 和 IdP 用户信息终端节点 (UserInfoEndpoint) 通信。验证负载均衡器的安全组和 VPC 的网络 ACL 是否允许至这些终端节点的出站访问。验证您的 VPC 可以访问 Internet。如果您有面向内部的负载均衡器,请使用 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

以下是 actions.json 文件,该文件指定 authenticate-oidc 操作和 forward 操作。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. 用户向在 Application Load Balancer 后面托管的网站发送 HTTPS 请求。当满足具有身份验证操作的规则的条件时,负载均衡器将检查请求标头中的身份验证会话 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 在 ID 令牌中提供了有效的刷新令牌,则负载均衡器将保存刷新令牌并在访问令牌过期时使用刷新令牌刷新用户索赔,直至会话超时或 IdP 刷新失败。如果用户注销,刷新将失败并且负载均衡器会将用户重定向到 IdP 授权终端节点。这使负载均衡器能够在用户注销后删除会话。有关更多信息,请参阅会话超时

注意

Cookie 过期与身份验证会话到期不同。Cookie 有效期是 Cookie 的一个属性,该属性被设置为 40 年。有效期较长的原因是为了确保浏览器始终重放 cookie。身份验证会话的实际长度由 Application Load Balancer 上为身份验证功能配置的会话超时确定。此会话超时包含在身份验证 cookie 值中,该值也经过加密。

用户申请编码和签名验证

在负载均衡器成功验证用户身份之后,它会将从 IdP 收到的用户索赔发送给目标。负载均衡器先为用户索赔签名,以便应用程序可以验证该签名并验证索赔是负载均衡器发送的。

负载均衡器添加以下 HTTP 标头:

x-amzn-oidc-accesstoken

令牌终端节点中的访问令牌(明文格式)。

x-amzn-oidc-identity

用户信息终端节点中的主题字段 (sub)(明文格式)。

x-amzn-oidc-data

用户声明(JSON Web 令牌 (JWT) 格式)。

访问令牌和用户声明与 ID 令牌不同。访问令牌和用户声明仅允许访问服务器资源,而 ID 令牌带有的额外信息以对用户进行身份验证。Application Load Balancer 对用户进行身份验证,并且仅将访问令牌和声明传递给后端,但不传递 ID 令牌信息。

这些令牌遵循 JWT 格式,但不是 ID 令牌。JWT 格式包含 base64 URL 编码的标头、有效负载和签名,并在末尾包含填充字符。JWT 签名为 ECDSA + P-256 + SHA256。

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 标头中获取密钥 ID 并使用它从终端节点查找公钥:

以下示例演示如何在 Python 3.x 中获取公钥:

import jwt import requests import base64 import json # Step 1: Get the key id from JWT headers (the kid field) 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) kid = decoded_json['kid'] # Step 2: 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 3: Get the payload payload = jwt.decode(encoded_jwt, pub_key, algorithms=['ES256'])

以下示例演示如何在 Python 2.7 中获取公钥:

import jwt import requests import base64 import json # Step 1: Get the key id from JWT headers (the kid field) 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) kid = decoded_json['kid'] # Step 2: 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 3: Get the payload payload = jwt.decode(encoded_jwt, pub_key, algorithms=['ES256'])

请注意,标准库与 JWT 格式的 Application Load Balancer 身份验证令牌中包含的填充不兼容。

Timeout

会话超时

刷新令牌和会话超时将一起运行,如下所示:

  • 如果会话超时短于访问令牌过期时间,则负载均衡器将遵守会话超时。如果用户与 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 支持最大 16K 的 Cookie,因此最多可以创建 4 个分片发送给客户端。

    • 如果 IdP 具有注销终端节点,它应该发出重定向到 IdP 注销终端节点,例如 Amazon Cognito 开发人员指南中记录的注销终端节点

    • 如果 IdP 没有注销终端节点,请求将返回到客户端注销登录页面,并重新启动登录过程。

  • 假设 IdP 具有注销终端节点,IdP 必须使访问令牌和刷新令牌过期,并将用户重定向回客户端注销登录页。

  • 后续请求遵循原始身份验证流程。