将未经身份验证的用户切换到经过身份验证的用户

时间:2020-07-25 20:29:12

标签: amazon-web-services amazon-cognito amazon-iam aws-appsync

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

我有:

  1. 用户池。这是为Google身份验证/常规认知身份验证设置的

  2. 身份池

    这通过Cognito身份提供程序链接到用户池。 经过身份验证/未经身份验证的角色具有附加的策略,使他们可以访问GraphQL API

  3. 使用AWS_IAM auth设置的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是否适合放入数据库?对于不同的设备,预期会有所不同吗?我正在尝试寻找其他可用的东西,但是干dry了。

这是上下文的“身份”部分中可用于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_”。因此,我将无法在客户端逻辑中对其进行匹配。

这完全有可能吗?还是我需要放弃未经身份验证的使用的想法,而直接使用用户池?

1 个答案:

答案 0 :(得分:1)

我会回答这个问题,我会坚持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"
}

我也在这里有一个Google,我删除了一些东西以隐藏我的帐户,等等,但是无论如何,您要使用的ID是cognito:username,实际上它的形式为{{1 }}。这是内部的,通常您不会显示给用户。因此,相反,您可以在Cognito中使用另一个声明Google_,该声明也可以用作here所提到的别名,但不能用于外部身份提供者。

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

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

最巧妙的方法是将数据本地存储在设备上,因此用户是否通过身份验证都无关紧要。如果由于某种原因您的应用程序确实需要将该数据存储在后端才能正常运行,而您无法重构,则可以使用定制的Userpool工作流并创建一个Pre-Signup Lambda,它可以接受自定义声明(我不会使用sts userArn似乎是错误的,但是您也可以使用它),最好是购物车的GUID,例如foo@gmail。所以我的意思是:

  • 当匿名用户首次访问该应用程序时,他们会 收到购物车的GUID,并保存其中的所有商品 购物车。
  • 如果他们选择注册,则可以传递自定义声明:bar@gmail,然后在Lambda函数中,您将在数据库中创建用户,并将购物车添加到他们的帐户中。
  • 猜测其他用户的GUID几乎是不可能的,但是如果出于安全考虑,则可以创建一个已签名的令牌。
  • 您可能要清理一段时间后不转移到注册用户的购物车。

如果您有任何疑问或不确定,请给我评论。我认为从内存来看,您需要使用预注册钩子,因为确认后无权访问在注册过程中传递的声明。您可能想在挂机前创建一个带有未确认标志的用户,然后在挂机后启用它们,我相信这样会更安全,以防万一您的池发生另一个故障,然后以脏状态创建用户。祝您好运,我通过Cognito进行了自我斗争并得以幸存!