使用 Application Load Balancer 验证用户身份 - Elastic Load Balancing

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

使用 Application Load Balancer 验证用户身份

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

支持以下使用案例:

  • 通过符合 OpenID Connect () 标准的身份提供商 (IdP) 对用户进行身份验证。OIDC

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

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

准备使用OIDC合规的 IdP

如果您在 Application Load Balanc OIDC er 中使用符合要求的 IdP,请执行以下操作:

  • 在您的 Id OIDC P 中创建新应用程序。IdP DNS 必须是可公开解析的。

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

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

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

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

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

    • https://DNS/oauth2/idpresponse

    • https://CNAME/oauth2/idpresponse

准备使用 Amazon Cognito

应用程序负载均衡器的 Amazon Cognito 集成已在以下区域推出:

  • 美国东部(弗吉尼亚州北部)

  • 美国东部(俄亥俄州)

  • 美国西部(加利福尼亚北部)

  • 美国西部(俄勒冈)

  • 加拿大(中部)

  • 欧洲地区(斯德哥尔摩)

  • 欧洲地区(米兰)

  • 欧洲地区(法兰克福)

  • 欧洲(苏黎世)

  • 欧洲地区(爱尔兰)

  • 欧洲地区(伦敦)

  • 欧洲(巴黎)

  • 南美洲(圣保罗)

  • Asia Pacific (Tokyo)

  • 亚太地区 (首尔)

  • 亚太地区(大阪)

  • 亚太地区(孟买)

  • 亚太地区(新加坡)

  • 亚太地区(悉尼)

  • 亚太地区(雅加达)

  • 中东 (UAE)

  • 中东(巴林)

  • 非洲(开普敦)

  • 以色列(特拉维夫)

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

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

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

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

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

    注意:应用程序负载均衡器不支持 Amazon Cognito 发行的自定义访问令牌。有关更多信息,请参阅 Amazon Cognito 开发者指南中的代币生成前

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

  • 允许URLs在 Amazon Cognito 的回调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-01AuthenticateOidcActionConfig中的AuthenticateCognitoActionConfig和。

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

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

应用程序负载均衡器不支持URL编码的 Cookie 值。

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

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

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

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

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

负载均衡器必须能够与 IdP 令牌终端节点 (TokenEndpoint) 和 IdP 用户信息终端节点 (UserInfoEndpoint) 通信。应用程序负载均衡器仅在与这些端点通信IPv4时支持。如果您的 IdP 使用公有地址,请确保您的负载均衡器的安全组和您的网络ACLsVPC允许访问终端节点。使用内部负载均衡器或 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

以下是 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. 用户向托管在 A HTTPS pplication 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。由于大多数浏览器将 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网络令牌 (JWT) 格式声明。

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

这些令牌遵循JWT格式,但不是 ID 令牌。该JWT格式包括采用 base64 URL 编码的标头、有效载荷和签名,并在末尾包括填充字符。Application Load Balancer ECDSA 使用ES256(使用 P-256 和SHA256)来生成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。此外,作为安全最佳实践,我们建议您将目标限制为仅接收来自您的 Application Load Balancer 的流量。您可以通过将目标的安全组配置为引用负载均衡器的安全组 ID 来实现此目的。

为确保安全,您必须在根据声明进行任何授权之前验证签名,并验证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'])
注意事项
  • 这些示例不包括如何使用令牌中的签名验证发行者的签名。

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

超时

会话超时

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

  • 如果会话超时短于访问令牌过期时间,则负载均衡器将遵守会话超时。如果用户与 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,该cookie的访问令牌已过期,且具有非NULL刷新令牌,则负载均衡器会联系 IdP 以确定用户是否仍处于登录状态。

客户端注销登录页是未经身份验证的页面。这意味着它不能位于需要身份验证的 Application Load Balancer 规则的后面。

  • 当向目标发送请求时,应用程序必须将所有身份验证 cookie 的到期时间设置为 -1。Application Load Balancer 支持最大 16K 的 Cookie,因此最多可以创建 4 个分片发送给客户端。

    • 如果 IdP 有注销终端节点,则应发出指向 IdP 注销终端节点(例如 Amazon Cognito 开发者指南LOGOUT中记录的终端节点)的重定向。

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

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

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