我可以让Firebase使用用户名登录过程吗?

时间:2022-02-02 23:13:59

I want to make a system which allows for username login. This process requires the following:

我想建立一个允许用户名登录的系统。此过程需要以下内容:

  1. User must register with an email/password
  2. 用户必须使用电子邮件/密码注册
  3. The user can set a unique username
  4. 用户可以设置唯一的用户名
  5. The user can sign in with either email or username
  6. 用户可以使用电子邮件或用户名登录
  7. The user can recover their password via email or username
  8. 用户可以通过电子邮件或用户名恢复其密码
  9. The functionality must work on a persistence enabled database
  10. 该功能必须适用于启用持久性的数据库

This question has been answered previously, but it disabled the functionality for the user to use password recovery. They also didn't address case-sensitivity, one person could register as "scooby" and another as "Scooby".

此问题之前已得到解答,但它禁用了用户使用密码恢复的功能。他们也没有解决案件敏感问题,一个人可以注册为“scooby”,另一个人注册为“Scooby”。

1 个解决方案

#1


8  

After multiple iterations of development I've come up with the following design to address this. I will post my code snippets in Swift, but they will typically be translatable directly into Android with ease.

经过多次开发迭代后,我想出了以下设计来解决这个问题。我将在Swift中发布我的代码片段,但它们通常可以轻松地直接翻译到Android中。


  1. Create a Firebase registration process for email/password.
  2. 为电子邮件/密码创建Firebase注册过程。

This is required as the backbone of the user's sign-in experience. This can be implemented completely from the stock Firebase API documentation provided here

这是用户登录体验的支柱。这可以完全从此处提供的库存Firebase API文档中实现


  1. Prompt the user to enter their username
  2. 提示用户输入其用户名

The username entry should be completed at registration, I'd recommend an additional field in the registration flow. I also recommend checking if the user has a username whenever they log in. If they don't, then display a SetUsername interface that prompts them to set a username before progressing further into the UI. A user might not have a username for a few reasons; it could be revoked for being rude or reserved, or they might have signed up prior to the username being required at registration.

用户名条目应在注册时完成,我建议在注册流程中添加一个字段。我还建议用户在登录时检查用户是否有用户名。如果用户没有,则显示一个SetUsername界面,提示他们在进一步进入用户界面之前设置用户名。用户可能由于某些原因而没有用户名;它可能因粗鲁或保留而被撤销,或者他们可能在注册时需要用户名之前注册。

Make sure that if you're using a persistence-enabled Firebase build that you use Firebase Transactions. The transactions are necessary, otherwise your app can make assumptions about the data in the username table, even though a username might have been set for a user only seconds earlier.

如果您使用的是启用了持久性的Firebase版本,请确保使用Firebase事务处理。事务是必要的,否则您的应用程序可以对用户名表中的数据进行假设,即使用户可能仅在几秒前为用户设置了用户名。

I would also advise enforcing the username to be nearly alphanumeric (I allow for some harmless punctuation). In Swift I can achieve this with the following code:

我还建议强制使用用户名几乎是字母数字(我允许一些无害的标点符号)。在Swift中,我可以使用以下代码实现此目的:

static var invalidCharacters:NSCharacterSet {
    let chars = NSMutableCharacterSet.alphanumericCharacterSet()

    // I add _ - and . to the valid characters.
    chars.addCharactersInString("_-.")

    return chars.invertedSet
}

if username.rangeOfCharacterFromSet(invalidCharacters) != nil {
    // The username is valid
}

  1. Saving the user data
  2. 保存用户数据

The next important step is knowing how to save the user's data in a way that we can access it in the future. Below is a screenshot of the way I store my user data: 我可以让Firebase使用用户名登录过程吗? A few things to note:

下一个重要步骤是了解如何以我们将来可以访问它的方式保存用户数据。下面是我存储用户数据的方式的屏幕截图:有几点需要注意:

  • The usernames are stored twice, once in usernames and again in details/[uid]/username. I recommend this as it allows you to be case sensitive with usernames (see the next point) and it also allows you to know the exact database reference to check a username (usernames/scooby) rather than having to query or check through the children of details to find a username that matches (which would only become more complicated when you have to factor in case-sensitivity)
  • 用户名存储两次,一次存储在用户名中,再次存储在详细信息/ [uid] / username中。我推荐这个,因为它允许你使用用户名区分大小写(参见下一点),它还允许你知道确切的数据库引用来检查用户名(用户名/ scooby),而不必查询或检查子项找到匹配的用户名的详细信息(当您必须考虑区分大小写时,这将变得更加复杂)
  • the usernames reference is stored in lowercase. When I check the values in this reference, or when I save to this reference, I ensure that I only save data in lowercase. This means if anyone wants to check if the username 'scoobY' exists, it will fail because in lowercase it's the same username as the existing user "Scooby".
  • 用户名引用以小写形式存储。当我检查此引用中的值时,或者当我保存到此引用时,我确保仅以小写形式保存数据。这意味着如果有人想检查用户名'scoobY'是否存在,它将失败,因为小写字母与现有用户“Scooby”的用户名相同。
  • The details/[uid]/username field contains capitals. This allows for the username to display in the case of preference for the user, rather than enforcing a lowercase or Capitalised word, the user can specify their name as "NASA Fan" and not be converted over to "Nasa Fan", while also preventing anyone else from registering the username "NASA FAN" (or any other case iterations)
  • 详细信息/ [uid] / username字段包含大写字母。这允许在用户偏好的情况下显示用户名,而不是强制使用小写字母或大写单词,用户可以将其名称指定为“NASA Fan”而不是转换为“Nasa Fan”,同时还可以防止其他任何人注册用户名“NASA FAN”(或任何其他案例迭代)
  • The emails are being stored in the user details. This might seem peculiar because you can retrieve the current user's email via Firebase.auth().currentUser.email?. The reason this is necessary is because we need references to the emails prior to logging in as the user.
  • 电子邮件存储在用户详细信息中。这可能看起来很奇怪,因为您可以通过Firebase.auth()。currentUser.email?检索当前用户的电子邮件。这是必要的原因是因为我们需要在以用户身份登录之前引用电子邮件。

  1. Logging in with email or username
  2. 使用电子邮件或用户名登录

For this to work seamlessly, you need to incorporate a few checks at login.

为了使其无缝工作,您需要在登录时加入一些检查。

Since I've disallowed the @ character in usernames, I can assume that a login request containing an @ is an email request. These requests get processed as normal, using Firebase's FIRAuth.auth().signInWithEmail(email, password, completion) method.

由于我在用户名中禁止使用@字符,因此我可以假设包含@的登录请求是电子邮件请求。使用Firebase的FIRAuth.auth()。signInWithEmail(电子邮件,密码,完成)方法正常处理这些请求。

For all other requests, we will assume it's a username request. Note: The cast to lowercase.

对于所有其他请求,我们将假设它是用户名请求。注意:强制转换为小写。

let ref = FIRDatabase.database().reference()
let usernameRef = ref.child("users/usernames/\(username.lowercaseString)")

When you perform this retrieval, you should consider if you have persistence-enabled, and if there's a possibility that a username could be revoked. If a username could be revoked and you have persistence-enabled, you will want to ensure you retrieve the username value within a Transaction block, to make sure you don't get a cached value back.

执行此检索时,应考虑是否启用了持久性,以及是否可能撤消用户名。如果可以撤消用户名并且启用了持久性,则需要确保在Transaction块中检索用户名值,以确保不会获得缓存值。

When this retrieval succeeds, you get the value from username[username], which is the user's uid. With this value, you can now perform a retrieval on the user's email value:

当此检索成功时,您将从用户名[username]获取值,该用户名是用户的uid。使用此值,您现在可以对用户的电子邮件值执行检索:

let ref = FIRDatabase.database().reference()
let usernameRef = ref.child("users/details/[uid]/email")

Once this request succeeds, you can then perform the standard Firebase email login with the email string you just retrieved.

此请求成功后,您可以使用刚刚检索到的电子邮件字符串执行标准Firebase电子邮件登录。

The exact same retrieval methods can be used to retrieve an email from a username to allow for password recovery.

完全相同的检索方法可用于从用户名检索电子邮件以允许密码恢复。

A few points to be wary of for advanced functionality: - If you allow the user to update their email using FIRUserProfileChangeRequest, make sure you update it both on the auth AND the details[uid]email field, otherwise you will break the username login functionality - You can significantly reduce the code required to handle all the different failure cases in the retrieval methods by using success and failure blocks. Here's an example of my get email method:

有几点要警惕高级功能: - 如果您允许用户使用FIRUserProfileChangeRequest更新他们的电子邮件,请确保您在auth和详细[uid]电子邮件字段中更新它,否则您将破坏用户名登录功能 - 通过使用成功和失败块,您可以显着减少处理检索方法中所有不同故障情况所需的代码。这是我的获取电子邮件方法的示例:

static func getEmail(username:String, success:(email:String) -> Void, failure:(error:String!) -> Void) {
    let usernameRef = FIRDatabase.database().reference().child("users/usernames/\(username.lowercaseString)")

    usernameRef.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
        if let userId = snapshot.value as? String {
            let emailRef = FIRDatabase.database().reference().child("users/details/\(userId)/email")

            emailRef.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
                if let email = snapshot.value as? String {
                    success(email: email)
                } else {
                    failure(error: "No email found for username '\(username)'.")
                }
            }) { (error) in
                failure(error: "Email could not be found.")
            }
        } else {
            failure(error: "No account found with username '\(username)'.")
        }
    }) { (error) in
        failure(error: "Username could not be found.")
    }
}

This success/failure block implementation allows the code I call in my ViewControllers to be much cleaner. Å login calls the following method:

这个成功/失败块实现允许我在ViewControllers中调用的代码更清晰。 Å登录调用以下方法:

if fieldText.containsString("@") {
    loginWithEmail(fieldText)
} else {
    // Attempt to get email for username.
    LoginHelper.getEmail(fieldText, success: { (email) in
        self.loginWithEmail(email)
    }, failure: { error in
        HUD.flash(.Error, delay: 0.5)
    })
}

#1


8  

After multiple iterations of development I've come up with the following design to address this. I will post my code snippets in Swift, but they will typically be translatable directly into Android with ease.

经过多次开发迭代后,我想出了以下设计来解决这个问题。我将在Swift中发布我的代码片段,但它们通常可以轻松地直接翻译到Android中。


  1. Create a Firebase registration process for email/password.
  2. 为电子邮件/密码创建Firebase注册过程。

This is required as the backbone of the user's sign-in experience. This can be implemented completely from the stock Firebase API documentation provided here

这是用户登录体验的支柱。这可以完全从此处提供的库存Firebase API文档中实现


  1. Prompt the user to enter their username
  2. 提示用户输入其用户名

The username entry should be completed at registration, I'd recommend an additional field in the registration flow. I also recommend checking if the user has a username whenever they log in. If they don't, then display a SetUsername interface that prompts them to set a username before progressing further into the UI. A user might not have a username for a few reasons; it could be revoked for being rude or reserved, or they might have signed up prior to the username being required at registration.

用户名条目应在注册时完成,我建议在注册流程中添加一个字段。我还建议用户在登录时检查用户是否有用户名。如果用户没有,则显示一个SetUsername界面,提示他们在进一步进入用户界面之前设置用户名。用户可能由于某些原因而没有用户名;它可能因粗鲁或保留而被撤销,或者他们可能在注册时需要用户名之前注册。

Make sure that if you're using a persistence-enabled Firebase build that you use Firebase Transactions. The transactions are necessary, otherwise your app can make assumptions about the data in the username table, even though a username might have been set for a user only seconds earlier.

如果您使用的是启用了持久性的Firebase版本,请确保使用Firebase事务处理。事务是必要的,否则您的应用程序可以对用户名表中的数据进行假设,即使用户可能仅在几秒前为用户设置了用户名。

I would also advise enforcing the username to be nearly alphanumeric (I allow for some harmless punctuation). In Swift I can achieve this with the following code:

我还建议强制使用用户名几乎是字母数字(我允许一些无害的标点符号)。在Swift中,我可以使用以下代码实现此目的:

static var invalidCharacters:NSCharacterSet {
    let chars = NSMutableCharacterSet.alphanumericCharacterSet()

    // I add _ - and . to the valid characters.
    chars.addCharactersInString("_-.")

    return chars.invertedSet
}

if username.rangeOfCharacterFromSet(invalidCharacters) != nil {
    // The username is valid
}

  1. Saving the user data
  2. 保存用户数据

The next important step is knowing how to save the user's data in a way that we can access it in the future. Below is a screenshot of the way I store my user data: 我可以让Firebase使用用户名登录过程吗? A few things to note:

下一个重要步骤是了解如何以我们将来可以访问它的方式保存用户数据。下面是我存储用户数据的方式的屏幕截图:有几点需要注意:

  • The usernames are stored twice, once in usernames and again in details/[uid]/username. I recommend this as it allows you to be case sensitive with usernames (see the next point) and it also allows you to know the exact database reference to check a username (usernames/scooby) rather than having to query or check through the children of details to find a username that matches (which would only become more complicated when you have to factor in case-sensitivity)
  • 用户名存储两次,一次存储在用户名中,再次存储在详细信息/ [uid] / username中。我推荐这个,因为它允许你使用用户名区分大小写(参见下一点),它还允许你知道确切的数据库引用来检查用户名(用户名/ scooby),而不必查询或检查子项找到匹配的用户名的详细信息(当您必须考虑区分大小写时,这将变得更加复杂)
  • the usernames reference is stored in lowercase. When I check the values in this reference, or when I save to this reference, I ensure that I only save data in lowercase. This means if anyone wants to check if the username 'scoobY' exists, it will fail because in lowercase it's the same username as the existing user "Scooby".
  • 用户名引用以小写形式存储。当我检查此引用中的值时,或者当我保存到此引用时,我确保仅以小写形式保存数据。这意味着如果有人想检查用户名'scoobY'是否存在,它将失败,因为小写字母与现有用户“Scooby”的用户名相同。
  • The details/[uid]/username field contains capitals. This allows for the username to display in the case of preference for the user, rather than enforcing a lowercase or Capitalised word, the user can specify their name as "NASA Fan" and not be converted over to "Nasa Fan", while also preventing anyone else from registering the username "NASA FAN" (or any other case iterations)
  • 详细信息/ [uid] / username字段包含大写字母。这允许在用户偏好的情况下显示用户名,而不是强制使用小写字母或大写单词,用户可以将其名称指定为“NASA Fan”而不是转换为“Nasa Fan”,同时还可以防止其他任何人注册用户名“NASA FAN”(或任何其他案例迭代)
  • The emails are being stored in the user details. This might seem peculiar because you can retrieve the current user's email via Firebase.auth().currentUser.email?. The reason this is necessary is because we need references to the emails prior to logging in as the user.
  • 电子邮件存储在用户详细信息中。这可能看起来很奇怪,因为您可以通过Firebase.auth()。currentUser.email?检索当前用户的电子邮件。这是必要的原因是因为我们需要在以用户身份登录之前引用电子邮件。

  1. Logging in with email or username
  2. 使用电子邮件或用户名登录

For this to work seamlessly, you need to incorporate a few checks at login.

为了使其无缝工作,您需要在登录时加入一些检查。

Since I've disallowed the @ character in usernames, I can assume that a login request containing an @ is an email request. These requests get processed as normal, using Firebase's FIRAuth.auth().signInWithEmail(email, password, completion) method.

由于我在用户名中禁止使用@字符,因此我可以假设包含@的登录请求是电子邮件请求。使用Firebase的FIRAuth.auth()。signInWithEmail(电子邮件,密码,完成)方法正常处理这些请求。

For all other requests, we will assume it's a username request. Note: The cast to lowercase.

对于所有其他请求,我们将假设它是用户名请求。注意:强制转换为小写。

let ref = FIRDatabase.database().reference()
let usernameRef = ref.child("users/usernames/\(username.lowercaseString)")

When you perform this retrieval, you should consider if you have persistence-enabled, and if there's a possibility that a username could be revoked. If a username could be revoked and you have persistence-enabled, you will want to ensure you retrieve the username value within a Transaction block, to make sure you don't get a cached value back.

执行此检索时,应考虑是否启用了持久性,以及是否可能撤消用户名。如果可以撤消用户名并且启用了持久性,则需要确保在Transaction块中检索用户名值,以确保不会获得缓存值。

When this retrieval succeeds, you get the value from username[username], which is the user's uid. With this value, you can now perform a retrieval on the user's email value:

当此检索成功时,您将从用户名[username]获取值,该用户名是用户的uid。使用此值,您现在可以对用户的电子邮件值执行检索:

let ref = FIRDatabase.database().reference()
let usernameRef = ref.child("users/details/[uid]/email")

Once this request succeeds, you can then perform the standard Firebase email login with the email string you just retrieved.

此请求成功后,您可以使用刚刚检索到的电子邮件字符串执行标准Firebase电子邮件登录。

The exact same retrieval methods can be used to retrieve an email from a username to allow for password recovery.

完全相同的检索方法可用于从用户名检索电子邮件以允许密码恢复。

A few points to be wary of for advanced functionality: - If you allow the user to update their email using FIRUserProfileChangeRequest, make sure you update it both on the auth AND the details[uid]email field, otherwise you will break the username login functionality - You can significantly reduce the code required to handle all the different failure cases in the retrieval methods by using success and failure blocks. Here's an example of my get email method:

有几点要警惕高级功能: - 如果您允许用户使用FIRUserProfileChangeRequest更新他们的电子邮件,请确保您在auth和详细[uid]电子邮件字段中更新它,否则您将破坏用户名登录功能 - 通过使用成功和失败块,您可以显着减少处理检索方法中所有不同故障情况所需的代码。这是我的获取电子邮件方法的示例:

static func getEmail(username:String, success:(email:String) -> Void, failure:(error:String!) -> Void) {
    let usernameRef = FIRDatabase.database().reference().child("users/usernames/\(username.lowercaseString)")

    usernameRef.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
        if let userId = snapshot.value as? String {
            let emailRef = FIRDatabase.database().reference().child("users/details/\(userId)/email")

            emailRef.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
                if let email = snapshot.value as? String {
                    success(email: email)
                } else {
                    failure(error: "No email found for username '\(username)'.")
                }
            }) { (error) in
                failure(error: "Email could not be found.")
            }
        } else {
            failure(error: "No account found with username '\(username)'.")
        }
    }) { (error) in
        failure(error: "Username could not be found.")
    }
}

This success/failure block implementation allows the code I call in my ViewControllers to be much cleaner. Å login calls the following method:

这个成功/失败块实现允许我在ViewControllers中调用的代码更清晰。 Å登录调用以下方法:

if fieldText.containsString("@") {
    loginWithEmail(fieldText)
} else {
    // Attempt to get email for username.
    LoginHelper.getEmail(fieldText, success: { (email) in
        self.loginWithEmail(email)
    }, failure: { error in
        HUD.flash(.Error, delay: 0.5)
    })
}