无法验证亚马逊认知用户池中客户端的秘密散列

我被困在“亚马逊认知身份用户池”的过程中。

我尝试了所有可能的代码来认证用户的认证用户池。但我总是得到错误说 “错误: 无法验证客户端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);
});
124351 次浏览

目前看来,AWS 认知并不能完美地处理客户机机密。它将在不久的将来工作,但目前它仍然是一个测试版本。

对我来说,对于一个没有客户端机密的应用程序来说,它工作得很好,但是对于一个有客户端机密的应用程序来说就失败了。

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

这是我用来生成秘密散列的示例 php 代码

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

在这种情况下,结果是:

DdSuILDJ2V84zfOChcn6TfgmlfnHsUYq0J6c01QV43I=

对于那些有兴趣使用 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 函数调用该函数

注意: 这个答案很大程度上基于乔治 · 坎贝尔在下面的链接中的回答: 在 python 中使用 string + secret key 计算 SHA 散列

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

Javascript SDK 不支持客户端机密的应用程序。

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

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

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

因为每个人都发布了自己的语言,这里有一个节点(它可以在浏览器中使用 browserify-crypto,如果你使用 webpack 或者 Browserify,它会自动使用) :

const crypto = require('crypto');


...


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

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

使用 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();
};

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

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

应该会有用的。

基于 SecreHash 的 NodeJS 解决方案

AWS 从 SDK 中删除秘密密钥看起来很愚蠢,因为它不会在 NodeJS 中公开。

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

白兰地

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

Get-inceptor. js (Forked and edit for NodeJS from Fork of https://github.com/werk85/fetch-intercept/blob/develop/src/index.js)

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

这可能晚了几年,但只要不选择“生成客户端机密”选项,它将为您的网络客户端工作。

generate app client option

可能还有一个更紧凑的版本,但是这个版本适用于 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))

Amazon 在他们的文档中用 Java 应用程序代码提到了 计算 secret 散列值 for 亚马逊认知

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$#\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即可。

new app create

认知认证

错误: 应用程序客户端没有配置为机密,但是收到了机密散列

提供 secret Key 作为空对我很有用。提供的证书包括:-

  • CognitoIdentityUserPoolArea (region)
  • CognitoIdentityUserPoolId (userPoolId)
  • CognitoIdentityUserPoolAppClientId (ClientId)
  • SignInProviderKey (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)
    

All above things work with below linked code sample.

AWS Sample code : https://github.com/awslabs/aws-sdk-ios-samples/tree/master/CognitoYourUserPools-Sample/Swift

Let me know if that doesn't work for you.

下面是我的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}"

这个解决方案将在2021年3月生效:

如果您使用的客户机同时生成了“ client _ SECRET”和“ client _ id”,而不是像 AWS 文件传递“ client _ secret”中指定的那样计算 SECRET T _ HASH 并将其提供给函数。

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

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

这很荒谬,但是很有效!

对于上面提到的问题陈述的一个快速修复方法是删除现有的“ AppClient”并创建一个带有未选中的 生成客户机密的新客户端

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

AWS Cognito

下面似乎与。NET,对于 asp.NET 页面,使用 Alexa Skills SDK for。时间小时网

注射依赖

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

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.

用于 javascript 的 crypto包已不再使用,因此使用 crypto-js:

import CryptoJS from 'crypto-js';
import Base64 from 'crypto-js/enc-base64';


const secretHash = Base64.stringify(CryptoJS.HmacSHA256(username + clientId, clientSecret));

记住以前运行 npm install @types/crypto-js crypto-js

我看到一个。NET 一个建议在这里,但这里的变化,工作为我,因为我无法找到访问“加密帮助程序。返回文章页面

private string GetHMAC(string text, string key)
{
// TODO: null checks or whatever you want on your inputs...
using (var hmacsha256 = new HMACSHA256(Encoding.UTF8.GetBytes(key)))
{
var hash = hmacsha256.ComputeHash(Encoding.UTF8.GetBytes(text));
return Convert.ToBase64String(hash);
}
}

你可以调用这个类似于注册请求的东西,如下:

SignUpRequest signUpRequest = new SignUpRequest
{
ClientId = "<your_client_app_id>",
Password = "<the-password-your-user-wanted>",
Username = "<the-username-your-user-wanted",
};


// TODO: add whatever else you need to on your sign up request (like email, phone number etc...)


// and the magic line right here:
signUpRequest.SecretHash = GetHMAC(
signUpRequest.Username + "<your_client_app_id>",
"<your_client_app_secret>");


SignUpResponse response = await _provider.SignUpAsync(signUpRequest);

对我来说,这就像一个魅力。我最初是把客户端应用程序的机密直接分配给这个“ Secretariat Hash”属性,但从扫描这里的其余答案,我意识到我真的需要哈希一些数据使用该键作为输入到哈希。