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

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

使用 Application Load Balancer 验证用户身份

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

支持以下使用案例:

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

  • 通过 Amazon Cognito 支持的用户池通过社交 IdPs媒体(例如亚马逊或谷歌)对用户进行身份验证。 FaceBook

  • 通过企业身份、使用 SAML、OpenID Connect (OIDC) 或 OAuth、通过 Amazon Cognito 支持的用户群体验证用户身份。

准备使用符合 OIDC 条件的 IdP

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

  • 使用 IdP 创建新的 OIDC 应用程序。IdP 的 DNS 必须是可公开解析的。

  • 必须配置客户端 ID 和客户端密钥。

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

  • IdP 端点证书应由可信的公共证书颁发机构颁发。

  • 端点的 DNS 条目必须是可公开解析的,即使它们解析为私有 IP 地址也是如此。

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

    • https://DNS/oauth2/idpresponse

    • https://CNAME/oauth2/idpresponse

准备使用 Amazon Cognito

应用程序负载均衡器的 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

应用程序客户端设置中的回调 URL 必须全都使用小写字母。

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

准备使用亚马逊 CloudFront

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

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

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

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

配置用户身份验证

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

负载均衡器会向客户端发送会话 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 未经授权的错误。但是,如果用户的身份验证信息已过期,它会将客户端重定向到 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 进行身份验证后,会在请求标头中添加 AWSALBAuthNonce cookie。这不会改变应用程序负载均衡器处理来自 IdP 的重定向请求的方式。

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

注意

Cookie 过期与身份验证会话到期不同。Cookie 有效期是 Cookie 的一个属性,设置为 7 天。身份验证会话的实际长度由 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 编码的标头、有效负载和签名,并在末尾包含填充字符。应用程序负载均衡器使用 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 标头中获取密钥 ID 并使用它从终端节点查找公钥:

对于中国(北京),端点如下所示:

https://aws-elb-public-keys-prod-cn-north-1.s3.cn-north-1.amazonaws.com.cn/

以下示例显示了如何在 Python 3.x 中获取密钥 ID、公钥和有效负载:

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://aws-elb-public-keys-prod-cn-north-1.s3.cn-north-1.amazonaws.com.cn/' + 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 中获取密钥 ID、公钥和有效负载:

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://aws-elb-public-keys-prod-cn-north-1.s3.cn-north-1.amazonaws.com.cn/' + kid req = requests.get(url) pub_key = req.text # Step 3: Get the payload payload = jwt.decode(encoded_jwt, pub_key, algorithms=['ES256'])
注意事项
  • 这些示例不包括如何使用令牌中的签名验证发行者的签名。

  • 标准库与 JWT 格式的应用程序负载均衡器身份验证令牌中包含的填充不兼容。

  • Elastic Load Balancing 身份验证目前可用于中国(北京)地区。不支持中国(宁夏)地区。

超时

会话超时

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

  • 如果会话超时短于访问令牌过期时间,则负载均衡器将遵守会话超时。如果用户与 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 必须使访问令牌和刷新令牌过期,并将用户重定向回客户端注销登录页。

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