如何同步更新Firebase Authentication与实时数据库用户邮箱
Firebase Auth 与 Realtime Database 邮箱同步更新方案
原有代码核心问题
- 异步逻辑顺序错误:Realtime Database 更新逻辑写在 Auth 操作的异步回调外,不会等待 Auth 接口返回就直接执行数据库写入,属于典型的竞态问题,哪怕 Auth 更新失败,数据库还是会被修改
- 登录凭证硬编码:
signInWithEmailAndPassword("currentEmail","currentPassword")写死了固定字符串,不是当前用户的真实登录凭证,登录接口大概率直接失败,且你没有做错误日志打印,完全感知不到 Auth 侧的请求失败 - 空指针与语法问题:直接用
!!强转currentUser,用户未登录时会直接崩溃;原代码点击事件的闭合括号缺失,存在语法错误 - 存储规则错误:用可修改的
name作为用户数据节点的 key,用户改名后会丢失原有数据关联;且把明文密码存在 Realtime Database 中存在严重数据泄露风险 - 错误处理缺失:Auth 更新的失败分支没有任何提示或回滚逻辑,出现问题无法定位
正确实现逻辑
要保证两边数据强一致,必须严格按串行流程执行异步操作:
- 先做输入合法性校验,拦截空值、非法邮箱格式
- 校验当前用户登录状态,未登录直接终止流程
- 优先执行 Firebase Auth 侧的邮箱、密码更新操作
- 只有 Auth 侧所有操作返回成功后,再执行 Realtime Database 的字段更新
- 任意一步失败立刻终止流程,给用户明确错误提示,不执行后续写入
- 所有操作全部成功后,再清空输入框、弹出成功提示
修正后可运行代码
private lateinit var binding: ActivityUpdateBinding private lateinit var dbRef: DatabaseReference private lateinit var firebaseAuth: FirebaseAuth override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityUpdateBinding.inflate(layoutInflater) setContentView(binding.root) // 全局初始化一次即可,不需要在更新方法里重复初始化 firebaseAuth = FirebaseAuth.getInstance() dbRef = FirebaseDatabase.getInstance().getReference("User") binding.btnUpdate.setOnClickListener { val name = binding.etName.text.toString().trim() val password = binding.etPassword.text.toString().trim() val email = binding.etEmail.text.toString().trim() if (name.isEmpty() || password.isEmpty() || email.isEmpty()) { Toast.makeText(this, "请填写完整信息", Toast.LENGTH_SHORT).show() return@setOnClickListener } updateData(name, password, email) } } private fun updateData(name: String, password: String, email: String) { val currentUser = firebaseAuth.currentUser if (currentUser == null) { Toast.makeText(this, "用户未登录,请先登录", Toast.LENGTH_SHORT).show() return } // 先更新Auth侧邮箱 currentUser.updateEmail(email) .addOnCompleteListener { emailUpdateTask -> if (emailUpdateTask.isSuccessful) { // 邮箱更新成功后再更新密码 currentUser.updatePassword(password) .addOnCompleteListener { pwdUpdateTask -> if (pwdUpdateTask.isSuccessful) { // Auth侧全部更新完成,再写数据库,用uid作为用户唯一key val userUpdateMap = mapOf( "name" to name, "email" to email // 禁止在数据库存明文密码,密码由Firebase Auth安全托管 ) dbRef.child(currentUser.uid).updateChildren(userUpdateMap) .addOnSuccessListener { // 全流程成功再清输入、弹提示 binding.etName.text.clear() binding.etPassword.text.clear() binding.etEmail.text.clear() Toast.makeText(this, "信息更新成功", Toast.LENGTH_SHORT).show() } .addOnFailureListener { e -> // 数据库更新失败可根据业务需求回滚Auth侧修改,保证数据一致 Toast.makeText(this, "数据库更新失败:${e.message}", Toast.LENGTH_SHORT).show() } } else { Toast.makeText(this, "密码更新失败:${pwdUpdateTask.exception?.message}", Toast.LENGTH_SHORT).show() } } } else { val err = emailUpdateTask.exception val errMsg = err?.message ?: "邮箱更新失败" // 登录状态过期需要重新认证 if (errMsg.contains("requires-recent-login")) { Toast.makeText(this, "登录状态已过期,请重新登录后再修改", Toast.LENGTH_SHORT).show() // 此处可弹出密码输入框,调用reauthenticate方法完成认证后重试更新 } else { Toast.makeText(this, "邮箱更新失败:$errMsg", Toast.LENGTH_SHORT).show() } } } }
关键注意事项
- 邮箱、密码属于用户敏感信息,Firebase 要求用户最近1小时内完成过登录才能修改,如果返回
requires-recent-login错误,需要先调用currentUser.reauthenticate(credential)传入用户当前密码完成重新认证,再重试更新 - 所有用户业务数据必须用 Firebase Auth 返回的
uid作为节点 key,不要用邮箱、姓名等可修改字段作为 key,避免数据关联错乱 - 绝对不要在自有数据库中存储用户明文密码,Firebase Auth 已经做了加盐哈希存储,自行存储会导致严重的安全合规问题
- 如果对数据一致性要求极高,可以加服务端对账逻辑,定期比对 Auth 和 Realtime Database 中的用户邮箱字段,修正不一致数据
内容的提问来源于stack exchange,提问作者hacker
相关产品推荐
相关产品推荐

