【问题标题】:Unable to verify secret hash for client in Amazon Cognito Userpools无法在 Amazon Cognito 用户池中验证客户端的秘密哈希
【发布时间】:2016-09-23 03:51:31
【问题描述】:

我被困在“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);
});

【问题讨论】:

  • 接受的答案不再有效。如何生成秘密哈希的说明在这里docs.aws.amazon.com/cognito/latest/developerguide/…
  • 是的,请查看下面的 @Simon Buchan 答案以获取 JavaScript 实现。效果很好。
  • Note that Generate client secret must be unchecked when creating a web app; the Amazon Cognito Identity SDK for JavaScript doesn’t support apps that have a client secret simply because the client secret could be easily viewed in your code. aws.amazon.com/blogs/mobile/…

标签: amazon-web-services amazon-cognito


【解决方案1】:

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

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

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

【讨论】:

  • 仅供参考:这只是发生在我身上,就在现在。它仍在以这种方式工作,2017 年 1 月。当我创建一个没有 client_secret 的应用程序时,我能够使用 JS SDK。当我使用 client_secret 创建应用程序时,我遇到了与原始问题相同的失败。
  • 截至 2017 年 4 月 21 日,当为 App Client 启用密钥时,它仍然无法使用 AWS CLI。 aws cognito-idp admin-initiate-auth \ --region ap-northeast-1 \ --user-pool-id MY_POOL_ID \ --client-id MY_CLIENT_ID \ --auth-flow ADMIN_NO_SRP_AUTH \ --auth-parameters USERNAME=username @gmail.com,PASSWORD=som3PassW0rd
  • 截至 2018 年 1 月,这仍然不受支持。 Github repo 上的文档github.com/aws/amazon-cognito-identity-js 提到了它:"When creating the App, the generate client secret box must be unchecked because the JavaScript SDK doesn't support apps that have a client secret."
  • 2018 年 5 月 19 日,同样的错误我们需要在没有客户端密码的情况下创建应用程序。
  • 2018 年 9 月 12 日——同样的问题。即使不使用生成秘密的客户端,无论用户是否经过身份验证,我都会得到 400。尽管如此,应用仍按预期运行。
【解决方案2】:

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

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

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

【讨论】:

  • 这对我在服务器端使用 Node.js 有效。谢谢医生!
【解决方案3】:

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

【讨论】:

  • 请注意,客户端创建后无法编辑,如有需要请新建一个。
  • 如果您创建一个新的应用程序客户端并且您有一个使用 Cognito 身份验证提供程序的身份池(在“联合身份”上),请记住使用新应用程序的 id 更新应用程序客户端 id 字段客户。
  • 感谢@Tiisetso Tjabane ?
  • 只是为了澄清,您不需要创建新的用户池。您需要在用户池下创建一个新的 App Client。因此,如果您已经有一个包含用户的用户池,则无需创建一个全新的用户池,只需在该用户池中创建一个新的应用客户端即可。
【解决方案4】:

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

const crypto = require('crypto');

...

crypto.createHmac('SHA256', clientSecret)
  .update(username + clientId)
  .digest('base64')

【讨论】:

  • 这是一个简单且最好的 Node.js 内置解决方案,感谢@simon
【解决方案5】:

我在 .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;
    }
}

【讨论】:

  • 确认这仍然需要并且在 v3.5 AWS .NET SDK(预览版)中仍然有效。
【解决方案6】:

Amazon 在他们的 Java 应用程序代码文档中提及 Computing SecretHash ValuesAmazon Cognito 的关系。在这里,此代码适用于 boto 3 Python SDK

您可以在General settings 下的左侧菜单中找到您的App clients。获取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$#\xd6\xc1\xc0U\xce\xc1$\x17\xa1=\x18L\xc5\x1b\xa4\xc8\xea,\x92\xf5\xb9\xcdM\xe4\x084\xf5\x03~'
SECRET_HASH = base64.b64encode(new_digest).decode()  # UCQj1sHAVc7BJBehPRhMxRukyOoskvW5zU3kCDT1A34=

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

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

【讨论】:

  • 对我来说,这只有在我将 msg = bytes(app_client_id + username, 'latin-1') 切换为 msg = bytes(username + app_client_id, 'latin-1') 时才有效。为了清楚起见,我切换了 clientId 和用户名的顺序,使用户名首先出现。
  • 非常感谢@JoshWolff,我错误地交换了app_client_idusername。但是我将正确的输出显示为根据username + app_client_id 显示的注释。非常感谢。
  • 完全没问题! @Kushan Gunasekera
【解决方案7】:

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

在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

【讨论】:

    【解决方案8】:

    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))
    }
    

    【讨论】:

      【解决方案9】:

      使用 SecretHash 的 NodeJS 解决方案

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

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

      cognito.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,
      }
      

      【讨论】:

      • 我可以按照您的程序注册,但我无法使用此程序登录。是否需要进行任何修改才能登录?如果您可以在此处添加它,将会非常有帮助。提前致谢。
      【解决方案10】:

      上述问题陈述的快速解决方法是删除现有的“App Client”并使用未选中的Generate client secret

      创建一个新的

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

      【讨论】:

      • 可能是克服此错误的最简单方法..
      【解决方案11】:

      在 Java 中,您可以使用以下代码:

      private String getSecretHash(String email, String appClientId, String appSecretKey) throws Exception {
          byte[] data = (email + appClientId).getBytes("UTF-8");
          byte[] key = appSecretKey.getBytes("UTF-8");
      
          return Base64.encodeAsString(HmacSHA256(data, key));
      }
      
      static byte[] HmacSHA256(byte[] data, byte[] key) throws Exception {
          String algorithm = "HmacSHA256";
          Mac mac = Mac.getInstance(algorithm);
          mac.init(new SecretKeySpec(key, algorithm));
          return mac.doFinal(data);
      }
      

      【讨论】:

      • 除了输出到屏幕之外,您在 SDK 中的哪些地方使用了这个秘密哈希?
      • 任何人都可以指向任何在线 AWS 文档,其中解释了针对客户端密钥进行身份验证吗? base64/sha256 签名编码是令人信服的解决方案——但除非它们明确符合说明如何针对客户端密钥进行身份验证的 AWS 文档,否则毫无价值。
      【解决方案12】:

      这是我用来生成秘密哈希的示例 php 代码

      <?php
          $userId = "aaa";
          $clientId = "bbb";
          $clientSecret = "ccc";
          $s = hash_hmac('sha256', $userId.$clientId, $clientSecret, true);
          echo base64_encode($s);
      ?>
      

      在这种情况下,结果是:

      DdSuILDJ2V84zfOChcn6TfgmlfnHsUYq0J6c01QV43I=
      

      【讨论】:

      • 这个哈希值在哪里
      • 如果不明显,则必须在请求正文的“AuthParameters”节点下提供密钥,密钥为“SECRET_HASH”,而不是像其他参数一样的“SecretHash”。 'AuthParameters' =&gt; [ 'USERNAME' =&gt; $login, 'PASSWORD' =&gt; $password, 'SECRET_HASH' =&gt; $this-&gt;_createSecretHash($login), ],
      【解决方案13】:

      对于 JAVA 和 .NET,您需要在 auth 参数中传递名为 SECRET_HASH 的秘密。

      AdminInitiateAuthRequest request = new AdminInitiateAuthRequest
      {
        ClientId = this.authorizationSettings.AppClientId,
        AuthFlow = AuthFlowType.ADMIN_NO_SRP_AUTH,
        AuthParameters = new Dictionary<string, string>
        {
          {"USERNAME", username},
          {"PASSWORD", password},
          {
            "SECRET_HASH", EncryptionHelper.GetSecretHash(username, AppClientId, AppClientSecret)
          }
        },
        UserPoolId = this.authorizationSettings.UserPoolId
      };
      

      它应该可以工作。

      【讨论】:

        【解决方案14】:

        带有 Qt 框架的 C++

        QByteArray MyObject::secretHash(
             const QByteArray& email,
             const QByteArray& appClientId, 
             const QByteArray& appSecretKey)
        {
                    QMessageAuthenticationCode code(QCryptographicHash::Sha256);
                    code.setKey(appSecretKey);
                    code.addData(email);
                    code.addData(appClientId);
                    return code.result().toBase64();
        };
        

        【讨论】:

          【解决方案15】:

          这是我的 1 个命令,它有效(已确认 :))

          EMAIL="EMAIL@HERE.com" \
          CLIENT_ID="[CLIENT_ID]" \
          CLIENT_SECRET="[CLIENT_ID]" \
          && SECRET_HASH=$(echo -n "${EMAIL}${CLIENT_ID}" | openssl dgst -sha256 -hmac "${CLIENT_SECRET}" | xxd -r -p | openssl base64) \
          && aws cognito-idp ...  --secret-hash "${SECRET_HASH}"
          

          【讨论】:

          • openssl 位对我造成了一些无关的输出,但这有效:SECRET_HASH=$(echo -n "${EMAIL}${CLIENT_ID}" | openssl dgst -sha256 -binary -hmac "${CLIENT_SECRET}" | openssl base64)
          【解决方案16】:

          此解决方案于 2021 年 3 月生效:

          如果您正在使用同时生成“client_secret”和“client_id”的客户端,而不是计算 SECRET_HASH 并将其提供给AWS docs 中指定的函数,传递“client_secret”

          注意:我试图从刷新令牌生成新令牌。

          let result = await cognitoIdentityServiceProvidor
            .initiateAuth({
              AuthFlow: "REFRESH_TOKEN",
              ClientId: clientId,
              AuthParameters: {
                REFRESH_TOKEN: refresh_token,
                SECRET_HASH: clientSecret,
              },
            })
            .promise();
          

          这很荒谬,但它有效!

          【讨论】:

          • 如果在用户池应用程序客户端配置上勾选了“为管理员 API 启用用户名密码身份验证以进行身份​​验证 (ALLOW_ADMIN_USER_PASSWORD_AUTH)” 框,这是否会被视为安全风险,并且 @987654323 @ 方法用于面向客户端的代码(例如 React )?
          【解决方案17】:

          可能有一个更紧凑的版本,但这适用于 Ruby,特别是在 Ruby on Rails 中,不需要任何东西:

          key = ENV['COGNITO_SECRET_HASH']
          data = username + ENV['COGNITO_CLIENT_ID']
          digest = OpenSSL::Digest.new('sha256')
          
          hmac = Base64.strict_encode64(OpenSSL::HMAC.digest(digest, key, data))
          

          【讨论】:

            【解决方案18】:

            认知认证

            错误:应用客户端未配置秘密但已收到秘密哈希

            提供 secretKey 为 nil 对我有用。提供的凭据包括:-

            • CognitoIdentityUserPoolRegion(区域)
            • CognitoIdentityUserPoolId (userPoolId)
            • CognitoIdentityUserPoolAppClientId (ClientId)
            • AWSCognitoUserPoolsSignInProviderKey (AccessKeyId)

              // setup service configuration
              let serviceConfiguration = AWSServiceConfiguration(region: CognitoIdentityUserPoolRegion, credentialsProvider: nil)
              
              // create pool configuration
              let poolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: CognitoIdentityUserPoolAppClientId,
                                                                              clientSecret: nil,
                                                                              poolId: CognitoIdentityUserPoolId)
              
              // initialize user pool client
              AWSCognitoIdentityUserPool.register(with: serviceConfiguration, userPoolConfiguration: poolConfiguration, forKey: AWSCognitoUserPoolsSignInProviderKey)
              

            以上所有内容都适用于以下链接的代码示例。

            AWS 示例代码:https://github.com/awslabs/aws-sdk-ios-samples/tree/master/CognitoYourUserPools-Sample/Swift

            如果这对你不起作用,请告诉我。

            【讨论】:

            • 这是一个死链接
            【解决方案19】:

            下面似乎现在可以与 .NET 一起使用,对于使用 Time Heur 的 .NET 的 Alexa Skills SDK 的 asp.net 页面

            注入依赖

                    private readonly CognitoUserManager<CognitoUser> _userManager;
             public RegisterModel(
                        UserManager<CognitoUser> userManager,
                        )
             _userManager = userManager as CognitoUserManager<CognitoUser> as CognitoUserManager<CognitoUser>;
            

            然后分配一个哈希

                 var user = _pool.GetUser(Input.UserName);
            _userManager.PasswordHasher.HashPassword(user,Input.Password);
             var result = await _userManager.CreateAsync(user, Input.Password);
            

            【讨论】:

              【解决方案20】:

              NodeJS 解决方案:

              • 计算身份验证操作的秘密哈希:

                import * as crypto from 'crypto';
                
                const secretHash = crypto
                    .createHmac('SHA256', clientSecret)
                    .update(email + clientId)
                    .digest('base64');
                
              • 为刷新令牌操作计算秘密哈希:

                import * as crypto from 'crypto';
                
                const secretHash = crypto
                    .createHmac('SHA256', clientSecret)
                    .update(sub + clientId)
                    .digest('base64');
                

              参数对象如下所示:

                const authenticateParams = {
                  ClientId: clientId,
                  UserPoolId: poolId,
                  AuthFlow: CognitoAuthFlow.ADMIN_NO_SRP_AUTH,
                  AuthParameters: {
                    PASSWORD: password,
                    USERNAME: email,
                    SECRET_HASH: secretHash,
                  },
                };
              
                const refreshTokenParams = {
                  ClientId: clientId,
                  UserPoolId: poolId,
                  AuthFlow: CognitoAuthFlow.REFRESH_TOKEN_AUTH,
                  AuthParameters: {
                    REFRESH_TOKEN: refreshToken,
                    SECRET_HASH: secretHash,
                  },
                };
              

              用法:

              import * as CognitoIdentityProvider from 'aws-sdk/clients/cognitoidentityserviceprovider';
              
              const provider = new CognitoIdentityProvider({ region });
              provider.adminInitiateAuth(params).promise(); // authenticateParams or refreshTokenParams, return a promise object.
              

              【讨论】:

                猜你喜欢
                • 2017-09-25
                • 2020-12-11
                • 2019-06-23
                • 2019-02-28
                • 2019-03-03
                • 2018-05-05
                • 2017-08-04
                • 2019-02-13
                • 2019-04-21
                相关资源
                最近更新 更多