无法在 Amazon Cognito 用户池中验证客户端的秘密哈希

我被困在“Amazon Cognito Identity 用户池”流程中。

我尝试了所有可能的代码来验证 cognito 用户池中的用户。但我总是收到错误消息:“错误:无法验证客户端 4b fd 的秘密哈希”。**

这是代码:

AWS.config.region = 'us-east-1'; // Region
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
    IdentityPoolId: 'us-east-1:b64bb629-ec73-4569-91eb-0d950f854f4f'
});

AWSCognito.config.region = 'us-east-1';
AWSCognito.config.credentials = new AWS.CognitoIdentityCredentials({
    IdentityPoolId: 'us-east-1:b6b629-er73-9969-91eb-0dfffff445d'
});

AWSCognito.config.update({accessKeyId: 'AKIAJNYLRONAKTKBXGMWA', secretAccessKey: 'PITHVAS5/UBADLU/dHITesd7ilsBCm'})

var poolData = { 
    UserPoolId : 'us-east-1_l2arPB10',
    ClientId : '4bmsrr65ah3oas5d4sd54st11k'
};
var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);

var userData = {
     Username : 'ronakpatel@gmail.com',
     Pool : userPool
};

var cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);

cognitoUser.confirmRegistration('123456', true,function(err, result) {
if (err) {
    alert(err);
    return;
}
console.log('call result: ' + result);
});
stack overflow Unable to verify secret hash for client in Amazon Cognito Userpools
原文答案
author avatar

接受的答案

目前 AWS Cognito 似乎不能完美地处理客户端机密。它会在不久的将来工作,但目前它仍然是一个测试版。

对我来说,它适用于没有客户端密码的应用程序,但对于具有客户端密码的应用程序则失败。

因此,在您的用户池中尝试创建一个新应用程序而不生成客户端密码。然后使用该应用注册新用户或确认注册。


答案:

作者头像

根据文档: http://docs.aws.amazon.com/cognito/latest/developerguide/setting-up-the-javascript-sdk.html

Javascript SDK 不支持具有客户端密码的应用程序。

说明现在指出,在为用户池创建应用程序时,您需要取消选中“生成客户端密码”。

作者头像

这可能晚了几年,但只需取消选中“生成客户端密码”选项”,它将适用于您的 Web 客户端。

generate app client option

作者头像

由于其他人都发布了他们的语言,这里是节点(它在带有 browserify-crypto 的浏览器中工作,如果你使用 webpack 或 browserify 会自动使用):

const crypto = require('crypto');

...

crypto.createHmac('SHA256', clientSecret)
  .update(username + clientId)
  .digest('base64')
作者头像

我在 .net SDK 中遇到了同样的问题。

这是我解决的方法,以防其他人需要它:

public static class CognitoHashCalculator
{
    public static string GetSecretHash(string username, string appClientId, string appSecretKey)
    {
        var dataString = username + appClientId;

        var data = Encoding.UTF8.GetBytes(dataString);
        var key = Encoding.UTF8.GetBytes(appSecretKey);

        return Convert.ToBase64String(HmacSHA256(data, key));
    }

    public static byte[] HmacSHA256(byte[] data, byte[] key)
    {
        using (var shaAlgorithm = new System.Security.Cryptography.HMACSHA256(key))
        {
            var result = shaAlgorithm.ComputeHash(data);
            return result;
        }
    }
}

注册然后看起来像这样:

public class CognitoSignUpController
{
    private readonly IAmazonCognitoIdentityProvider _amazonCognitoIdentityProvider;

    public CognitoSignUpController(IAmazonCognitoIdentityProvider amazonCognitoIdentityProvider)
    {
        _amazonCognitoIdentityProvider = amazonCognitoIdentityProvider;
    }

    public async Task<bool> SignUpAsync(string userName, string password, string email)
    {
        try
        {
            var request = CreateSignUpRequest(userName, password, email);
            var authResp = await _amazonCognitoIdentityProvider.SignUpAsync(request);

            return true;
        }
        catch
        {
            return false;
        }
    }

    private static SignUpRequest CreateSignUpRequest(string userName, string password, string email)
    {
        var clientId = ConfigurationManager.AppSettings["ClientId"];
        var clientSecretId = ConfigurationManager.AppSettings["ClientSecretId"];

        var request = new SignUpRequest
        {
            ClientId = clientId,
            SecretHash = CognitoHashCalculator.GetSecretHash(userName, clientId, clientSecretId),
            Username = userName,
            Password = password,
        };

        request.UserAttributes.Add("email", email);
        return request;
    }
}
作者头像

Amazon 在他们的 Java 应用程序代码文档中提到 Computing SecretHash Values 是如何为 Amazon Cognito 的。这里的代码适用于 boto 3 Python SDK

app client details

您可以在 App clients 下的左侧菜单中找到您的 General settings 。获取那些 App client idApp client secret 以创建 SECRET_HASH 。为了您更好地理解,我注释掉了每一行的所有输出。

import hashlib
import hmac
import base64

app_client_secret = 'u8f323eb3itbr3731014d25spqtv5r6pu01olpp5tm8ebicb8qa'
app_client_id = '396u9ekukfo77nhcfbmqnrec8p'
username = 'wasdkiller'

# convert str to bytes
key = bytes(app_client_secret, 'latin-1')  # b'u8f323eb3itbr3731014d25spqtv5r6pu01olpp5tm8ebicb8qa'
msg = bytes(username + app_client_id, 'latin-1')  # b'wasdkiller396u9ekukfo77nhcfbmqnrec8p'

new_digest = hmac.new(key, msg, hashlib.sha256).digest()  # b'P$#xd6xc1xc0Uxcexc1$x17xa1=x18Lxc5x1bxa4xc8xea,x92xf5xb9xcdMxe4x084xf5x03~'
SECRET_HASH = base64.b64encode(new_digest).decode()  # UCQj1sHAVc7BJBehPRhMxRukyOoskvW5zU3kCDT1A34=

boto 3 文档中,我们可以看到很多时间询问 SECRET_HASH 。所以上面的代码行帮助你创建这个 SECRET_HASH

如果您不想使用 SECRET_HASH ,只需在创建应用时取消选中 Generate client secret

new app create

作者头像

对于任何有兴趣使用 AWS Lambda 使用 AWS JS 开发工具包注册用户的人,我执行以下步骤:

在 python 中创建另一个 lambda 函数来生成密钥:

import hashlib
import hmac
import base64

secretKey = "key"
clientId = "clientid"
digest = hmac.new(secretKey,
                  msg=username + clientId,
                  digestmod=hashlib.sha256
                 ).digest()
signature = base64.b64encode(digest).decode()

通过 AWS 中的 nodeJS 函数调用该函数。签名充当 Cognito 的秘密哈希

注意:答案很大程度上基于 George Campbell 在以下链接中的答案: Calculating a SHA hash with a string + secret key in python

作者头像

golang 的解决方案。似乎应该将其添加到 SDK 中。

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/base64"
)

func SecretHash(username, clientID, clientSecret string) string {
    mac := hmac.New(sha256.New, []byte(clientSecret))
    mac.Write([]byte(username + ClientID))
    return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}
作者头像

使用 SecretHash 的 NodeJS 解决方案

AWS 从 SDK 中删除密钥似乎很愚蠢,因为它不会在 NodeJS 中公开。

我通过拦截 fetch 并使用 @Simon Buchan 的答案添加散列键使其在 NodeJS 中工作。

认知.js

import { CognitoUserPool, CognitoUserAttribute, CognitoUser } from 'amazon-cognito-identity-js'
import crypto from 'crypto'
import * as fetchIntercept from './fetch-intercept'

const COGNITO_SECRET_HASH_API = [
  'AWSCognitoIdentityProviderService.ConfirmForgotPassword',
  'AWSCognitoIdentityProviderService.ConfirmSignUp',
  'AWSCognitoIdentityProviderService.ForgotPassword',
  'AWSCognitoIdentityProviderService.ResendConfirmationCode',
  'AWSCognitoIdentityProviderService.SignUp',
]

const CLIENT_ID = 'xxx'
const CLIENT_SECRET = 'xxx'
const USER_POOL_ID = 'xxx'

const hashSecret = (clientSecret, username, clientId) => crypto.createHmac('SHA256', clientSecret)
  .update(username + clientId)
  .digest('base64')

fetchIntercept.register({
  request(url, config) {
    const { headers } = config
    if (headers && COGNITO_SECRET_HASH_API.includes(headers['X-Amz-Target'])) {
      const body = JSON.parse(config.body)
      const { ClientId: clientId, Username: username } = body
      // eslint-disable-next-line no-param-reassign
      config.body = JSON.stringify({
        ...body,
        SecretHash: hashSecret(CLIENT_SECRET, username, clientId),
      })
    }
    return [url, config]
  },
})

const userPool = new CognitoUserPool({
  UserPoolId: USER_POOL_ID,
  ClientId: CLIENT_ID,
})

const register = ({ email, password, mobileNumber }) => {
  const dataEmail = { Name: 'email', Value: email }
  const dataPhoneNumber = { Name: 'phone_number', Value: mobileNumber }

  const attributeList = [
    new CognitoUserAttribute(dataEmail),
    new CognitoUserAttribute(dataPhoneNumber),
  ]

  return userPool.signUp(email, password, attributeList, null, (err, result) => {
    if (err) {
      console.log((err.message || JSON.stringify(err)))
      return
    }
    const cognitoUser = result.user
    console.log(`user name is ${cognitoUser.getUsername()}`)
  })
}

export {
  register,
}

fetch-inceptor.js(从 https://github.com/werk85/fetch-intercept/blob/develop/src/index.js 的 Fork 为 NodeJS 分叉和编辑)

let interceptors = []

if (!global.fetch) {
  try {
    // eslint-disable-next-line global-require
    global.fetch = require('node-fetch')
  } catch (err) {
    throw Error('No fetch available. Unable to register fetch-intercept')
  }
}
global.fetch = (function (fetch) {
  return (...args) => interceptor(fetch, ...args)
}(global.fetch))

const interceptor = (fetch, ...args) => {
  const reversedInterceptors = interceptors.reduce((array, _interceptor) => [_interceptor].concat(array), [])
  let promise = Promise.resolve(args)

  // Register request interceptors
  reversedInterceptors.forEach(({ request, requestError }) => {
    if (request || requestError) {
      promise = promise.then(_args => request(..._args), requestError)
    }
  })

  // Register fetch call
  promise = promise.then(_args => fetch(..._args))

  // Register response interceptors
  reversedInterceptors.forEach(({ response, responseError }) => {
    if (response || responseError) {
      promise = promise.then(response, responseError)
    }
  })

  return promise
}

const register = (_interceptor) => {
  interceptors.push(_interceptor)
  return () => {
    const index = interceptors.indexOf(_interceptor)
    if (index >= 0) {
      interceptors.splice(index, 1)
    }
  }
}

const clear = () => {
  interceptors = []
}

export {
  register,
  clear,
}
作者头像

上述问题陈述的快速解决方法是删除现有的“App Client”并创建一个未选中 Generate client secret 的新客户端

注意:不要忘记更改代码中的应用程序客户端字符串。

AWS Cognito