无法在 Amazon Cognito 用户池中验证客户端的秘密哈希
Posted
技术标签:
【中文标题】无法在 Amazon Cognito 用户池中验证客户端的秘密哈希【英文标题】:Unable to verify secret hash for client in Amazon Cognito Userpools 【发布时间】: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/…
【参考方案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 Values 与 Amazon Cognito 的关系。在这里,此代码适用于 boto 3 Python SDK。
您可以在General settings
下的左侧菜单中找到您的App clients
。获取App client id
和App 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_id
和username
。但是我将正确的输出显示为根据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' => [ 'USERNAME' => $login, 'PASSWORD' => $password, 'SECRET_HASH' => $this->_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.
【讨论】:
以上是关于无法在 Amazon Cognito 用户池中验证客户端的秘密哈希的主要内容,如果未能解决你的问题,请参考以下文章
Amazon Cognito:验证用户后,是不是必须提示他们登录?
如何使用 Amazon Cognito 作为我的网站(非移动应用程序)的用户身份验证 [关闭]
检查用户 phone_number 是不是已存在于 aws cognito 用户池中
我们可以在 AWS Cognito 的单个用户池中拥有多个角色吗?
“没有当前用户”:是不是甚至可以通过 Amplify 使用身份验证类型 AMAZON_COGNITO_USER_POOLS 对 AWS AppSync 进行未经身份验证的调用?