我是 Dragan 的同事,经过大量尝试,我们在团队中找到了一个切实可行的解决方案。只是注意到我们实际上可以访问高级 AWS 和 Microsoft 支持,但他们无法帮助我们。 AWS Cognito 团队意识到了这个问题,但似乎没有优先级 - 因为将近一年没有任何修复。
流程说明
我们在前端使用他们的 javascript 库 msal 对微软进行身份验证(不涉及 Cognito)。我们收到一个 JWT 令牌并使用这个令牌在用户池中创建一个普通的 Cognito 用户。电子邮件是从 microsoft 令牌中读取的,密码是使用安全随机(尽可能长)自动生成的。此外,我们将 microsoft 令牌作为自定义用户属性发送。在 PreSignUp Lambda 中,如果 microsoft 令牌有效,我们会自动激活用户,因此不会向用户发送密码验证电子邮件。回到前端,我们将放大自定义身份验证挑战登录与我们缓存在前端的电子邮件一起使用。现在我们通过 DefineAuthChallenge,然后是 CreateAuthChallenge。 CreateAuthChallenge 不做任何事情,因为 microsoft 令牌是我们的挑战,不需要创建。回到前端,我们调用包含 sessionKey 和 microsoft token 的 CustomChallenge。我们现在在 VerifyChallenge Lambda 中,我们使用开源 JWT 库验证 microsoft 令牌本身。流程通过我们只允许一次尝试的 DefineAuthChallenge 返回。最后,用户从 Cognito 收到 Cognito 令牌。
以下 sn-ps 是 Lambda 的完整代码 sn-ps。我不得不从我们的项目中删除一些特定的东西,所以希望这样做不会破坏任何东西。所有文件都是index.js,Lambda 不需要其他文件。您肯定可以外包一些我们尚未完成的重复代码。此处不包含 FE 代码。
PreSignUp Lambda
const jwksClient = require('jwks-rsa');
const jwt = require('jsonwebtoken');
const client = jwksClient({
jwksUri: 'https://login.microsoftonline.com/common/discovery/v2.0/keys'
});
const options = {
algorithms: ['RS256']
};
function getKey(header, callback) {
client.getSigningKey(header.kid, function (err, key) {
const signingKey = key.publicKey || key.rsaPublicKey;
callback(null, signingKey);
});
}
const verifyMicrosoftToken = async (jwt, token, key) => {
if (!token) return {};
return new Promise((resolve, reject) =>
jwt.verify(token, key, options, (err, decoded) => err ? reject({}) :
resolve(decoded))
);
};
exports.handler = async (event) => {
const email = event.request.userAttributes.email.toLowerCase();
//verify microsoft and auto enable user
if (event.request.userAttributes['custom:msalIdtoken']) {
const token = await verifyMicrosoftToken(
jwt, event.request.userAttributes['custom:msalIdtoken'], getKey
);
const emailFromToken = token.email !== undefined ? token.email : token.preferred_username;
if (token && emailFromToken.toLowerCase() === email) {
event.response.autoConfirmUser = true;
event.response.autoVerifyEmail = true;
}
}
return event;
};
DefineAuthChallenge Lambda
exports.handler = (event, context, callback) => {
if (event.request.session &&
event.request.session.length > 0 &&
event.request.session.slice(-1)[0].challengeName === 'CUSTOM_CHALLENGE' &&
event.request.session.slice(-1)[0].challengeResult === true){
console.log("Session: ", event.request.session);
event.response.issueTokens = true;
event.response.failAuthentication = false;
} else {
event.response.failAuthentication = false;
event.response.issueTokens = false;
event.response.challengeName = 'CUSTOM_CHALLENGE';
}
// Return to Amazon Cognito
callback(null, event);
};
CreateChallenge Lambda
exports.handler = (event, context, callback) => {
if (event.request.challengeName === 'CUSTOM_CHALLENGE') {
event.response.publicChallengeParameters = {};
event.response.publicChallengeParameters.dummy = 'dummy';
event.response.privateChallengeParameters = {};
event.response.privateChallengeParameters.dummy = 'dummy';
event.response.challengeMetadata = 'MICROSOFT_JWT_CHALLENGE';
}
callback(null, event);
};
VerifyAuthChallenge Lambda
const AWS = require('aws-sdk');
const jwksClient = require('jwks-rsa');
const jwt = require('jsonwebtoken');
const client = jwksClient({
jwksUri: 'https://login.microsoftonline.com/common/discovery/v2.0/keys'
});
const options = {
algorithms: ['RS256']
};
function getKey(header, callback){
client.getSigningKey(header.kid, function(err, key) {
const signingKey = key.publicKey || key.rsaPublicKey;
callback(null, signingKey);
});
}
exports.handler = (event, context, callback) => {
if(event.request.challengeAnswer){
jwt.verify(event.request.challengeAnswer, getKey, options, function(err, decoded) {
if(decoded){
const email = decoded.email !== undefined ? decoded.email : decoded.preferred_username;
if (email.toLowerCase() === event.request.userAttributes['email'].toLowerCase()) {
event.response.answerCorrect = true;
// it is necessary to add this group to user so in BE we can resolve microsoft provider
const cognitoIdentityServiceProvider = new AWS.CognitoIdentityServiceProvider();
var params = {
GroupName: "CUSTOM_MICROSOFT_AUTH",
UserPoolId: event.userPoolId,
Username: event.userName
};
cognitoIdentityServiceProvider.adminAddUserToGroup(params, function (err) {
if (err) {
console.log("Group cannot be added to the user: " + event.userName, err);
}
callback(null, event);
});
}
}
if(err){
console.log(err);
}
});
}else{
event.response.answerCorrect = false;
callback(null, event);
}
};
这是我们使用的一些链接
后记
如果您在此代码中发现任何与安全相关的问题,请私下与我联系,我们公司将根据严重程度给予一些赞赏($)。