微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

阻止用户自行注册联合身份提供商 (FIP),但允许在管理员添加的情况下使用 FIP 登录

如何解决阻止用户自行注册联合身份提供商 (FIP),但允许在管理员添加的情况下使用 FIP 登录

我已在 Amazon Cognito 中为我的 Web 应用程序设置了一个用户池。该应用程序不是公开的,只允许特定用户登录。亚马逊控制台中该用户池的策略只允许管理员创建新用户

我已经通过 Facebook 和 Google 实现了登录。 Cognito 确实允许用户使用这些联合身份提供者登录应用程序,这很棒。不过,似乎任何拥有 Facebook 或 Google 帐户的人现在都可以自行注册

因此,一方面,人们无法使用常规 Cognito 凭据创建自己的用户,但另一方面,如果他们使用联合身份提供商,则可以在 Cognito 中创建新用户

是否可以通过 Facebook 或 Google 将我的应用程序的登录限制为仅限用户池中已存在的用户这样,管理员仍然可以控制谁可以访问应用。我想使用联合身份提供商共享的电子邮件来检查他们是否可以登录

应用程序是使用 CloudFront 设置的。我编写了一个 Lambda,它拦截源请求以检查 cookie 中的令牌并根据访问令牌的有效性授权访问。

我想避免编写额外的代码来阻止用户注册 Facebook 或 Google,但如果没有其他方法,我将更新 Lambda。

解决方法

所以,这是我最终编写的预注册 Lambda 触发器。我花时间使用 async/await 而不是 Promises。它工作得很好,除了有一个记录在案的错误,即 Cognito 强制第一次使用外部身份提供者的用户注册,然后再次登录(因此他们会看到两次身份验证页面),然后才能访问应用程序。我有一个关于如何解决这个问题的想法,但与此同时,下面的 Lambda 做了我想要的。此外,事实证明来自 Login With Amazon 的 ID 没有使用正确的大小写,所以我不得不手动重新格式化该 ID,这很不幸。让我觉得 Cognito 触发器的实现有点问题。

const PROVIDER_MAP = new Map([
    ['facebook','Facebook'],['google','Google'],['loginwithamazon','LoginWithAmazon'],['signinwithapple','SignInWithApple']
]);

async function getFirstCognitoUserWithSameEmail(event) {
    const { region,userPoolId,request } = event;

    const AWS = require('aws-sdk');
    const cognito = new AWS.CognitoIdentityServiceProvider({
        region
    });

    const parameters = {
        UserPoolId: userPoolId,AttributesToGet: ['sub','email'],// We don't really need these attributes
        Filter: `email = "${request.userAttributes.email}"` // Unfortunately,only one filter can be applied at once
    };

    const listUserQuery = await cognito.listUsers(parameters).promise();

    if (!listUserQuery || !listUserQuery.Users) {
        return { error: 'Could not get list of users.' };
    }

    const { Users: users } = listUserQuery;

    const cognitoUsers = users.filter(
        user => user.UserStatus !== 'EXTERNAL_PROVIDER' && user.Enabled
    );

    if (cognitoUsers.length === 0) {
        console.log('No existing enabled Cognito user with same email address found.');
        return {
            error: 'User is not allowed to sign up.'
        };
    }

    if (cognitoUsers.length > 1) {
        cognitoUsers.sort((a,b) =>
            a.UserCreateDate > b.UserCreateDate ? 1 : -1
        );
    }

    console.log(
        `Found ${cognitoUsers.length} enabled Cognito user(s) with same email address.`
    );

    return { user: cognitoUsers[0],error: null };
}

// Only external users get linked with Cognito users by design
async function linkExternalUserToCognitoUser(event,existingUsername) {
    const { userName,region,userPoolId } = event;

    const [
        externalIdentityProviderName,externalIdentityUserId
    ] = userName.split('_');

    if (!externalIdentityProviderName || !externalIdentityUserId) {
        console.error(
            'Invalid identity provider name or external user ID. Should look like facebook_123456789.'
        );
        return { error: 'Invalid external user data.' };
    }

    const providerName = PROVIDER_MAP.get(externalIdentityProviderName);

    let userId = externalIdentityUserId;
    if (providerName === PROVIDER_MAP.get('loginwithamazon')) {
        // Amazon IDs look like amzn1.account.ABC123DEF456
        const [part1,part2,amazonId] = userId.split('.');
        const upperCaseAmazonId = amazonId.toUpperCase();
        userId = `${part1}.${part2}.${upperCaseAmazonId}`;
    }

    const AWS = require('aws-sdk');
    const cognito = new AWS.CognitoIdentityServiceProvider({
        region
    });

    console.log(`Linking ${userName} (ID: ${userId}).`);

    const parameters = {
        // Existing user in the user pool to be linked to the external identity provider user account.
        DestinationUser: {
            ProviderAttributeValue: existingUsername,ProviderName: 'Cognito'
        },// An external identity provider account for a user who does not currently exist yet in the user pool.
        SourceUser: {
            ProviderAttributeName: 'Cognito_Subject',ProviderAttributeValue: userId,ProviderName: providerName // Facebook,Google,Login with Amazon,Sign in with Apple
        },UserPoolId: userPoolId
    };

    // See https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminLinkProviderForUser.html
    await cognito.adminLinkProviderForUser(parameters).promise();

    console.log('Successfully linked external identity to user.');

    // TODO: Update the user created for the external identity and update the "email verified" flag to true. This should take care of the bug where users have to sign in twice when they sign up with an identity provider for the first time to access the website.
    // Bug is documented here: https://forums.aws.amazon.com/thread.jspa?threadID=267154&start=25&tstart=0

    return { error: null };
}

module.exports = async (event,context,callback) => {
    // See event structure at https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html
    const { triggerSource } = event;

    switch (triggerSource) {
        default: {
            return callback(null,event);
        }
        case 'PreSignUp_ExternalProvider': {
            try {
                const {
                    user,error: getUserError
                } = await getFirstCognitoUserWithSameEmail(event);

                if (getUserError) {
                    console.error(getUserError);
                    return callback(getUserError,null);
                }

                const {
                    error: linkUserError
                } = await linkExternalUserToCognitoUser(event,user.Username);

                if (linkUserError) {
                    console.error(linkUserError);
                    return callback(linkUserError,null);
                }

                return callback(null,event);
            } catch (error) {
                const errorMessage =
                    'An error occurred while signing up user from an external identity provider.';
                console.error(errorMessage,error);

                return callback(errorMessage,null);
            }
        }
    }
};

,

有一种方法可以做到这一点,但您需要编写一些代码 - 没有现成的解决方案。

您需要编写一个 lambda 并将其连接到 Cognito 预注册触发器。 https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-sign-up.html 触发器具有三个不同的事件源; PreSignUp_SignUpPreSignUp_AdminCreateUserPreSignUp_ExternalProvider

您的 lambda 应该检查您是否有 PreSignUp_ExternalProvider 事件。对于这些事件,请使用 Cognito SDK 在现有池中查找用户。如果用户存在,则返回事件。如果用户不存在,则返回一个字符串(错误信息)。

我将在这里粘贴我自己的预注册触发器。它不会做您需要它做的事情,但您需要的所有主要组件都在那里。您基本上可以将其破解为执行您需要的操作。

const AWS = require("aws-sdk");
const cognito = new AWS.CognitoIdentityServiceProvider();

exports.handler = (event,callback) => {

  function checkForExistingUsers(event,linkToExistingUser) {

    console.log("Executing checkForExistingUsers");

    var params = {
      UserPoolId: event.userPoolId,Filter: "email = \"" + event.request.userAttributes.email + "\""
    };

    return new Promise((resolve,reject) =>
      cognito.listUsers(params,(err,result) => {
        if (err) {
          reject(err);
          return;
        }
        if (result && result.Users && result.Users[0] && result.Users[0].Username && linkToExistingUser) {
          console.log("Found existing users: ",result.Users);
          if (result.Users.length > 1){
            result.Users.sort((a,b) => (a.UserCreateDate > b.UserCreateDate) ? 1 : -1);
            console.log("Found more than one existing users. Ordered by createdDate: ",result.Users);
          }
          linkUser(result.Users[0].Username,event).then(result => {
              resolve(result);
            })
            .catch(error => {
              reject(err);
              return;
            });
        } else {
          resolve(result);
        }

      })
    );

  }

  function linkUser(sub,event) {
    console.log("Linking user accounts with target sub: " + sub + "and event: ",event);

    //By default,assume the existing account is a Cognito username/password
    var destinationProvider = "Cognito";
    var destinationSub = sub;
    //If the existing user is in fact an external user (Xero etc),override the the provider
    if (sub.includes("_")) {
      destinationProvider = sub.split("_")[0];
      destinationSub = sub.split("_")[1];
    }
    var params = {
      DestinationUser: {
        ProviderAttributeValue: destinationSub,ProviderName: destinationProvider
      },SourceUser: {
        ProviderAttributeName: 'Cognito_Subject',ProviderAttributeValue: event.userName.split("_")[1],ProviderName: event.userName.split("_")[0]
      },UserPoolId: event.userPoolId
    };
    console.log("Parameters for adminLinkProviderForUser: ",params);
    return new Promise((resolve,reject) =>
      cognito.adminLinkProviderForUser(params,result) => {
        if (err) {
          console.log("Error encountered whilst linking users: ",err);
          reject(err);
          return;
        }
        console.log("Successfully linked users.");
        resolve(result);
      })
    );
  }

  console.log(JSON.stringify(event));

  if (event.triggerSource == "PreSignUp_SignUp" || event.triggerSource == "PreSignUp_AdminCreateUser") {

    checkForExistingUsers(event,false).then(result => {
        if (result != null && result.Users != null && result.Users[0] != null) {
          console.log("Found at least one existing account with that email address: ",result);
          console.log("Rejecting sign-up");
          //prevent sign-up
          callback("An external provider account alreadys exists for that email address",null);
        } else {
          //proceed with sign-up
          callback(null,event);
        }
      })
      .catch(error => {
        console.log("Error checking for existing users: ",error);
        //proceed with sign-up
        callback(null,event);
      });

  }

  if (event.triggerSource == "PreSignUp_ExternalProvider") {

    checkForExistingUsers(event,true).then(result => {
        console.log("Completed looking up users and linking them: ",result);
        callback(null,event);
      })
      .catch(error => {
        console.log("Error checking for existing users: ",event);
      });

  }

};

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?