如何使用boto3为AWS Cognito创建SECRET_HASH?

12

我希望使用boto3和python为AWS Cognito创建/计算SECRET_HASH。这将被合并到我的warrant分支中。

我配置了我的cognito应用程序客户端以使用应用程序客户端密钥。但是,这破坏了以下代码。

def renew_access_token(self):
    """
    Sets a new access token on the User using the refresh token.

    NOTE:
    Does not work if "App client secret" is enabled. 'SECRET_HASH' is needed in AuthParameters.
    'SECRET_HASH' requires HMAC calculations.

    Does not work if "Device Tracking" is turned on.
    https://dev59.com/_FoU5IYBdhLWcg3wc2tW#40875783

    'DEVICE_KEY' is needed in AuthParameters. See AuthParameters section.
    https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_InitiateAuth.html
    """
    refresh_response = self.client.initiate_auth(
        ClientId=self.client_id,
        AuthFlow='REFRESH_TOKEN',
        AuthParameters={
            'REFRESH_TOKEN': self.refresh_token
            # 'SECRET_HASH': How to generate this?
        },
    )

    self._set_attributes(
        refresh_response,
        {
            'access_token': refresh_response['AuthenticationResult']['AccessToken'],
            'id_token': refresh_response['AuthenticationResult']['IdToken'],
            'token_type': refresh_response['AuthenticationResult']['TokenType']
        }
    )

当我运行这个程序时,我收到了以下异常:
botocore.errorfactory.NotAuthorizedException: 
An error occurred (NotAuthorizedException) when calling the InitiateAuth operation: 
Unable to verify secret hash for client <client id echoed here>.

这个回答告诉我使用cognito客户端密钥需要SECRET_HASH。

aws API参考文档中的AuthParameters部分说明如下:

对于REFRESH_TOKEN_AUTH/REFRESH_TOKEN:USERNAME(必需),SECRET_HASH(如果应用程序客户端配置了客户端密钥,则必需),REFRESH_TOKEN(必需),DEVICE_KEY

boto3文档指出,SECRET_HASH是

使用用户池客户端的秘密密钥和消息中的用户名加上客户端ID计算得出的密钥散列消息认证代码(HMAC)。

文档解释了所需内容,但没有说明如何实现。

2个回答

13

下面的get_secret_hash方法是我用Python为Cognito用户池实现编写的解决方案,以下是示例用法:

import boto3
import botocore
import hmac
import hashlib
import base64


class Cognito:
    client_id = app.config.get('AWS_CLIENT_ID')
    user_pool_id = app.config.get('AWS_USER_POOL_ID')
    identity_pool_id = app.config.get('AWS_IDENTITY_POOL_ID')
    client_secret = app.config.get('AWS_APP_CLIENT_SECRET')
    # Public Keys used to verify tokens returned by Cognito:
    # http://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html#amazon-cognito-identity-user-pools-using-id-and-access-tokens-in-web-api
    id_token_public_key = app.config.get('JWT_ID_TOKEN_PUB_KEY')
    access_token_public_key = app.config.get('JWT_ACCESS_TOKEN_PUB_KEY')

    def __get_client(self):
        return boto3.client('cognito-idp')

    def get_secret_hash(self, username):
        # A keyed-hash message authentication code (HMAC) calculated using
        # the secret key of a user pool client and username plus the client
        # ID in the message.
        message = username + self.client_id
        dig = hmac.new(self.client_secret, msg=message.encode('UTF-8'),
                       digestmod=hashlib.sha256).digest()
        return base64.b64encode(dig).decode()

    # REQUIRES that `ADMIN_NO_SRP_AUTH` be enabled on Client App for User Pool
    def login_user(self, username_or_alias, password):
        try:
            return self.__get_client().admin_initiate_auth(
                UserPoolId=self.user_pool_id,
                ClientId=self.client_id,
                AuthFlow='ADMIN_NO_SRP_AUTH',
                AuthParameters={
                    'USERNAME': username_or_alias,
                    'PASSWORD': password,
                    'SECRET_HASH': self.get_secret_hash(username_or_alias)
                }
            )
        except botocore.exceptions.ClientError as e:
            return e.response

2
对于 API 需要用户名的所有情况,包括 REFRESH_TOKEN_AUTH 流程中不需要 AuthParameters 的情况,SECRET_HASH 计算是否始终使用用户名作为输入? - simpleuser
3
好东西。我刚刚在dig = hmac.new(...)行收到了这个错误“TypeError:key:期望字节或字节数组,但得到'str'”。解决方法是在hmac.new的第一个参数处执行简单的bytearray(self.client_secret,“utf-8”) - CarlosR
@simpleuser 我也想知道 - Rony Tesler
@RonyTesler 我发现答案是在身份验证过程的参数中,对于所有使用SECRET_HASH的地方都使用用户名,但是在进行REFRESH_TOKEN_AUTH时,还需要使用用户的UUID和REFRESH_TOKEN。 - simpleuser
@simpleuser 用户的UUID是什么?无论如何,用户名是JWT令牌的一部分,所以你应该有刷新令牌和过期的JWT令牌,因此你应该能够访问到用户名。 - Rony Tesler
@RonyTesler 当您进行用户认证时,您提供他们的用户名和密码,并使用其用户名生成的SECRET_HASH。AWS响应包括访问令牌作为“AccessToken”,刷新令牌作为“RefreshToken”,以及身份令牌作为“IdToken”;一旦认证成功,通过他们的用户名获取用户信息,响应将在“Username”字段中返回他们的UUID。在刷新访问令牌时,使用RefreshToken和UUID,而不需要再次提供用户名和密码,会得到一个新的AccessToken和IdToken。IdToken与AccessToken一起用于请求中。 - simpleuser

2

当我尝试上述解决方案时,我也遇到了TypeError错误。这是对我有效的解决方案:

import hmac
import hashlib
import base64

# Function used to calculate SecretHash value for a given client
def calculateSecretHash(client_id, client_secret, username):
    key = bytes(client_secret, 'utf-8')
    message = bytes(f'{username}{client_id}', 'utf-8')
    return base64.b64encode(hmac.new(key, message, digestmod=hashlib.sha256).digest()).decode()

# Usage example
calculateSecretHash(client_id, client_secret, username)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接