Elastic Load Balancing
Application Load Balancer
AWS 文档中描述的 AWS 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅 Amazon AWS 入门

使用 应用程序负载均衡器 验证用户身份

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

支持以下使用案例:

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

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

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

准备使用符合 OIDC 条件的 IdP

如果要将符合 OIDC 条件的 IdP 与 应用程序负载均衡器 一起使用,请执行以下操作:

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

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

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

    • https://DNS/oauth2/idpresponse

    • https://CNAME/oauth2/idpresponse

准备使用 Amazon Cognito

如果要将 Amazon Cognito 用户池 应用程序负载均衡器 一起使用,请执行以下操作:

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

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

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

  • 要与社交或企业 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

如果您在应用程序负载均衡器前使用 CloudFront 分配,请启用以下设置:

  • 查询字符串转发和缓存(全部)

  • Cookie 转发(全部)

  • 基于缓存的请求标头(全部)

配置用户身份验证

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

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

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

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

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

  • 具有每隔几秒就加载的 JavaScript 的单页面应用程序 - 默认情况下,在身份验证会话 Cookie 到期后,AJAX 调用将重定向至 IdP 并受阻。如果使用 deny 选项,则负载均衡器将针对这些 AJAX 调用返回“HTTP 401 未授权”错误。

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

[{ "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-cn: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-cn: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-cn:elasticloadbalancing:region-code:account-id:targetgroup/target-group-name/target-group-id", "Order": 2 }]

有关更多信息,请参阅 侦听器规则

身份验证流程

Elastic Load Balancing 使用 OIDC 授权代码流程,其中包括以下步骤。

  1. 当满足具有身份验证操作的规则的条件时,负载均衡器将检查请求标头中的身份验证会话 Cookie。如果 Cookie 不存在,则负载均衡器会将用户重定向到 IdP 授权终端节点,以便 IdP 可对用户进行身份验证。

  2. 验证用户身份之后,IdP 会使用授权授予代码将用户重定向回负载均衡器。负载均衡器会将此代码发送给 IdP 令牌终端节点,以获取 ID 令牌和访问令牌。

  3. 负载均衡器验证 ID 令牌之后,它将访问令牌与 IdP 用户信息终端节点交换以获取用户索赔。

  4. 负载均衡器将创建身份验证会话 Cookie 并将其发送到客户端,以便客户端的用户代理可在发出请求时将 Cookie 发送到负载均衡器。由于大多数浏览器将 Cookie 限制为 4K 大小,因此负载均衡器会将超出 4K 大小的 Cookie 分片为多个 Cookie。如果从 IdP 收到的用户索赔和访问令牌的总大小超出 11K 大小,则负载均衡器将返回错误。

  5. 负载均衡器将用户索赔发送到 HTTP 标头中的目标。有关更多信息,请参阅 用户索赔编码和签名验证

  6. 如果 IdP 在 ID 令牌中提供了有效的刷新令牌,则负载均衡器将保存刷新令牌并在访问令牌过期时使用刷新令牌刷新用户索赔,直至会话超时或 IdP 刷新失败。如果用户注销,刷新将失败并且负载均衡器会将用户重定向到 IdP 授权终端节点。这使负载均衡器能够在用户注销后删除会话。有关更多信息,请参阅 身份验证注销和会话超时

用户索赔编码和签名验证

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

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

x-amzn-oidc-accesstoken

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

x-amzn-oidc-identity

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

x-amzn-oidc-data

用户索赔(JSON Web 令牌 (JWT) 格式)。

需要完整用户索赔的应用程序可使用任何标准 JWT 库。JWT 格式包括 base64 URL 编码的标头、负载和签名。JWT 签名为 ECDSA + P-256 + SHA256。

JWT 标头为具有以下字段的 JSON 对象:

{ "alg": "algorithm", "kid": "12345678-1234-1234-1234-123456789012", "signer": "arn:aws-cn: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 并使用它从以下区域终端节点查找公钥:

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

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

import jwt import requests # 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'])

身份验证注销和会话超时

当应用程序需要注销经身份验证的用户时,应将身份验证会话 Cookie 的到期时间设置为 -1 并将客户端重定向到 IdP 注销终端节点(如果 IdP 支持一个终端节点)。为防止用户重复使用已删除的 Cookie,建议为访问令牌配置合理的短过期时间。如果客户端为负载均衡器提供了授权会话 Cookie(具有已到期的访问令牌和非 NULL 刷新令牌),负载均衡器将联系 IdP,以确定用户是否仍处于登录状态。

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

  • 如果会话超时短于访问令牌过期时间,则负载均衡器将遵守会话超时并在身份验证会话超时之后让用户再次登录。

  • 如果会话超时长于访问令牌过期时间并且 IdP 不支持刷新令牌,则负载均衡器会将身份验证会话一直保留到其超时,之后让用户再次登录。

  • 如果会话超时长于访问令牌过期时间并且 IdP 支持刷新令牌,则负载均衡器将在每次访问令牌到期时刷新用户会话。仅当身份验证会话超时或刷新流程失败之后,负载均衡器才会让用户再次登录。