首页 > 解决方案 > 将未经身份验证的用户切换为经过身份验证的用户

问题描述

我正在尝试将我的应用程序设置为允许未经身份验证的用户访问 AppSync API,如https://docs.aws.amazon.com/cognito/latest/developerguide/switching-identities.html中所述。理想情况下,他们将能够开始使用该应用程序,然后登录并保留他们的所有数据。

我有:

  1. 一个用户池。这是为 Google auth/regular cognito auth 设置的

  2. 身份池

    这通过 Cognito 身份提供者链接到用户池。经过身份验证/未经身份验证的角色附加了一个策略,使他们能够访问 GraphQL API

  3. 使用 AWS_IAM 身份验证设置的 AppSync API

我这样创建应用同步客户端:

val credentialsProvider = CognitoCachingCredentialsProvider(
    context,
    "us-east-2:abc...etc",
    Regions.US_EAST_2)
appSyncClient = AWSAppSyncClient.builder()
    .context(applicationContext)
    .awsConfiguration(awsConfiguration)
    .credentialsProvider(credentialsProvider)
    .build()

这工作正常,身份池为我创建了一个身份,我可以与 API 交互。好吧,它创建了两个匿名身份 ID,但它可以工作。当我登录时,真正的麻烦来了:

val hostedUIOptions: HostedUIOptions = HostedUIOptions.builder()
    .scopes("openid", "email", "aws.cognito.signin.user.admin")
    .build()
val signInUIOptions: SignInUIOptions = SignInUIOptions.builder()
    .hostedUIOptions(hostedUIOptions)
    .build()
 
runOnUiThread {
    mobileClient.showSignIn(
        mainActivity,
        signInUIOptions,
        object : Callback<UserStateDetails?> {
            override fun onResult(result: UserStateDetails?) {
                Log.i("AwsAuthSignIn", "onResult: " + result?.userState)
            }
 
            override fun onError(e: Exception?) {
                Log.i("AwsAuthSignIn", "onResult: " + result?.userState)
            }
 
        }
    )
}

之后,我看到它创建了一个与登录相关联的新身份,而不是使用旧身份。我认为它应该无缝地转移旧的身份 ID 以与经过身份验证的用户连接。

我还尝试调用 registerIdentityChangedListener 以查看它是否在登录时触发,但它没有。它仅在第一次获得未经身份验证的身份 ID 时触发。

此外,当我从两个不同的设备登录同一个帐户时,它会为用户池中的同一用户创建两个不同的身份 ID。由于我使用 identityId 来跟踪 RDB 记录所有权,这意味着同一用户在登录后会看到不同的项目。

那么identityId 是否适合放入数据库?预计不同设备会有所不同吗?我正在尝试寻找其他可以使用的东西,但我正在干涸。

这是用于 VTL 解析器的上下文的“身份”部分中可用的内容:

"identity": {
    "accountId": "________",
    "cognitoIdentityAuthProvider": "\"cognito-idp.us-east-2.amazonaws.com/us-east-2_______\",\"cognito-idp.us-east-2.amazonaws.com/us-east-2_______:CognitoSignIn:____________\"",
    "cognitoIdentityAuthType": "authenticated",
    "cognitoIdentityId": "us-east-2:___",
    "cognitoIdentityPoolId": "us-east-2:___",
    "sourceIp": [
        "_____"
    ],
    "userArn": "arn:aws:sts::_________:assumed-role/amplify-focaltodokotlin-prod-222302-authRole/CognitoIdentityCredentials",
    "username": "__________:CognitoIdentityCredentials"
}

“用户名”是唯一有意义的另一个,但是当我在我这边调用 AWSMobileClient.username 时,它​​会出现一种不同的格式:“Google_”。所以我无法在客户端逻辑中匹配它。

这是否可能,还是我需要放弃未经身份验证使用的想法并直接使用用户池?

标签: amazon-web-servicesamazon-cognitoamazon-iamaws-appsync

解决方案


我会回答这个问题,我会坚持 Cognito 的工作原理,而不是如何处理特定的 SDK。

快速回顾一下,在 Cognito 中,当用户进行身份验证时,他们将获得 3 个令牌,访问权限、ID 和刷新。使用身份池,他们可以交换其中一个(我忘记是哪个)来获得短期凭证来担任角色。STS 是在引擎盖下使用的,这就是你在userArn那里看到的。您不想查看那个人的 ID,这是您的客户需要承担 IAM 角色的 STS 构造。

我会回到令牌,让我们看看id_token我最喜欢的:

{
  "at_hash": "l.......",
  "sub": ".....",
  "cognito:groups": [
    "ap-southeast-......_Google",
    "kibana"
  ],
  "email_verified": false,
  "cognito:preferred_role": "arn:aws:iam::...:role/...",
  "iss": "https://cognito-idp.ap-southeast-2.amazonaws.com/ap-southeast-...",
  "phone_number_verified": false,
  "custom:yourapp:foo": "Bar",
  "cognito:username": "Google_......",
  "nonce": ".........",
  "cognito:roles": [
    "arn:aws:iam::.....",
    "arn:aws:iam::....."
  ],
  "aud": ".....",
  "identities": [
    {
      "userId": "....",
      "providerName": "Google",
      "providerType": "Google",
      "issuer": null,
      "primary": "true",
      "dateCreated": "1583907536164"
    }
  ],
  "token_use": "id",
  "auth_time": 1596366937,
  "exp": 1596370537,
  "iat": 1596366937,
  "email": "test@test.com"
}

我这里也有一个谷歌,我删除了一些东西来隐藏我的帐户等,但无论如何你想要使用的 idcognito:username实际上是Google_. 这是内部的,通常您不会向用户展示。因此,在 Cognito 中,您可以使用另一个声明preferred_username,它也可以用作此处提到的登录别名,但不适用于外部身份提供者。

您可以使用创建自定义声明来帮助在 UI 上显示信息,这些信息将以 为前缀custom:,我在这里有一个:custom:yourapp:foo。但是可能已经有一个可供您使用,例如email可以从 Google 获得的。当您创建外部身份提供商时,您将配置要从 Google 映射的声明,电子邮件将在那里,因此在您的应用程序中您可以阅读email声明,但您应该cognito:username在应用程序的后端使用,但请记住如果用户删除并重新创建他们的帐户,我不知道您会再次获得相同的 ID。您可能更希望用户能够定义一个preferred_username注册时,您可以在 UI 中显示它,但不要使用它来保存数据,使用cognito:username声明。

现在开始使用该应用程序,然后登录并保留他们的所有数据。通常,这将通过将所有数据保存在设备的本地存储中来实现,而不是后端。原因是,如果用户没有经过身份验证(不包括在打开应用程序时创建会话),那么就无法验证他们foo@gmail.com实际上foo@gmail.com是在使用您的 API 时所看到的未经身份验证的角色。我可以点击你的 API 并说我是,foo@gmail即使我是bar@gmail.

最简洁的方法是将数据本地存储在设备上,因此用户是否经过身份验证无关紧要。如果由于某种原因您的应用程序确实需要将此数据存储在后端以运行,并且您无法重构,那么您可以做的是使用自定义用户池工作流程,并创建一个Pre-Signup Lambda,它可以采用自定义声明(我不会使用 sts userArn 似乎是错误的,但你也可以使用它),最好是购物车的 GUID,例如custom:yourapp:cartGuid。所以我的意思是:

  • 当匿名用户第一次访问该应用程序时,他们将获得一个购物车的 GUID,并保存该购物车中的所有商品。
  • 如果他们选择注册,他们可以传入自定义声明:custom:yourapp:cartGuid,然后在您的 Lambda 函数中,您将在数据库中创建用户,并将购物车添加到他们的账户。
  • 猜测另一个用户的 GUID 几乎是不可能的,但如果这是一个安全问题,那么您可以创建一个签名令牌。
  • 您可能想要清理在一定时间后未移动到注册的用户购物车。

如果您有任何问题或不确定,请给我评论。我从记忆中相信您需要使用 pre-signup 挂钩,因为 post-confirm 无权访问在注册过程中传递的声明。您可能希望在 pre-hook 中创建带有未确认标志的用户,然后在 post-hook 中启用它们,我认为这更安全,以防您的池发生另一个故障,然后您在脏状态下创建了用户。祝我好运,我自己经历了 Cognito 战斗并幸存下来!


推荐阅读