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

我想制作一个允许用户名login的系统。 这个过程需要以下几点:

  1. 用户必须使用电子邮件/密码进行注册
  2. 用户可以设置一个唯一的用户名
  3. 用户可以使用电子邮件或用户名login
  4. 用户可以通过电子邮件或用户名恢复他们的密码
  5. 该function必须在启用了持久性的数据库上工作

这个问题以前已经回答过,但是它禁用了用户使用密码恢复的function。 他们也没有处理大小写敏感问题,一个人可以注册为“史酷比”,另一个人可以注册为“史酷比”。

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


  1. 创build电子邮件/密码的Firebase注册stream程。

这是用户login体验的中坚力量。 这可以从这里提供的股票Firebase API文档完全实现


  1. 提示用户input他们的用户名

用户名input应该在注册时完成,我build议在注册stream程中增加一个字段。 我还build议在用户login时检查用户是否有用户名。如果他们不这样做,那么显示一个SetUsername接口,提示他们设置一个用户名,然后进一步进入用户界面。 由于几个原因,用户可能没有用户名; 可能因粗鲁或保留而被撤销,或者在注册时需要用户名之前注册。

确保您使用的是使用Firebase交易的启用持久性的Firebase版本。 事务是必要的,否则你的应用程序可以对用户名表中的数据做出假设,即使用户名可能早在几秒前就已经为用户设置了。

我也build议强制用户名几乎是字母数字(我允许一些无害的标点符号)。 在Swift中,我可以用下面的代码实现这个function:

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. 保存用户数据

下一个重要的步骤就是知道如何保存用户的数据,以便将来可以访问。 以下是我存储我的用户数据的截图: 用户细节层次结构 有几件事要注意:

  • 用户名被存储两次,一次在usernames名中,另一次在details/[uid]/username 。 我build议这样做,因为它允许你用户名区分大小写(见下一点),它还允许你知道确切的数据库引用来检查用户名( usernames/scooby ),而不必查询或检查find匹配用户名的details (只有在需要区分大小写时才会变得更加复杂)
  • usernames引用以小写forms存储。 当我检查这个引用中的值,或者当我保存到这个引用时,我确保我只保存小写的数据。 这意味着如果任何人想要检查用户名“scoobY”是否存在,它将失败,因为在小写字母它是现有用户“史酷比”相同的用户名。
  • details/[uid]/username段包含大写字母。 这允许在用户偏好的情况下显示用户名,而不是实施小写字母或大写字母,用户可以将其名称指定为“NASA Fan”而不是转换成“NASA Fan”,同时也防止注册用户名“NASA FAN”(或任何其他情况下的迭代)
  • 电子邮件正在存储在用户的详细信息。 这可能看起来很奇怪,因为您可以通过Firebase.auth().currentUser.email?检索当前用户的电子邮件Firebase.auth().currentUser.email? 。 这是必要的原因是因为我们需要在以用户身份login之前引用电子邮件。

  1. 使用电子邮件或用户名login

要使其无缝工作,您需要在login时join一些检查。

由于我不允许用户名中的@字符,我可以假设包含@的login请求是一个电子邮件请求。 使用Firebase的FIRAuth.auth().signInWithEmail(email, password, completion)方法可以正常处理这些请求。

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

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

当您执行此检索时,您应该考虑是否启用了持久性,以及是否有可能撤销用户名。 如果一个用户名可以被撤销,并且你已经启用了持久化,那么你需要确保你在一个Transaction块中检索用户名的值,以确保你没有得到一个caching的值。

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

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

一旦此请求成功,您就可以使用刚才检索的电子邮件string执行标准Firebase电子邮件login。

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

有几点需要注意高级function: – 如果允许用户使用FIRUserProfileChangeRequest更新电子邮件,请确保在auth和details[uid]email字段中都更新它,否则将打破用户名loginfunction – 通过使用成功和失败块,可以显着减less处理检索方法中所有不同失败情况所需的代码。 以下是我的获取电子邮件方法的示例:

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

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

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