如何结合Firebase实时数据库校验与OTP验证用户?含权限及代码问题
解决方案:先验证CUG号码存在再触发OTP认证
首先,咱们先梳理下你现有代码的核心问题:
- 在
onCreate时就获取了mobno = e1.getText().toString(),这时候用户还没输入手机号,所以这个值是空的,导致数据库查询的是一个空节点,逻辑完全走偏了 - 把按钮点击事件的注册放在了数据库回调里,这会导致只有数据库回调执行后,按钮点击才会生效,而且如果用户多次输入号码点击,逻辑会混乱
另外,针对你的数据库规则疑问:不需要先完成OTP认证再检查号码,我们可以通过设置精细的数据库规则,让未认证用户只能检查特定手机号是否存在,同时保证数据库整体安全。
第一步:设置安全的Firebase Realtime Database规则
修改你的数据库规则,允许未认证用户读取Employees下的单个手机号节点(仅用于检查存在性),但限制写入权限给对应的认证用户:
{ "rules": { "Employees": { "$phoneNumber": { // 允许所有用户读取该节点(仅用于检查号码是否存在) ".read": true, // 仅允许已认证且手机号匹配的用户写入 ".write": "auth != null && auth.token.phone_number == $phoneNumber" } } } }
这样既满足了未登录时检查号码的需求,又不会暴露整个数据库的内容。
第二步:修正代码逻辑
核心思路是:用户输入手机号 → 点击发送OTP → 先查询数据库确认号码存在 → 存在则发起OTP验证,不存在则提示错误
以下是修正后的完整代码:
public class PhoneLogin extends AppCompatActivity { private static final String TAG = "PhoneLogin"; private boolean mVerificationInProgress = false; private String mVerificationId; private PhoneAuthProvider.ForceResendingToken mResendToken; private PhoneAuthProvider.OnVerificationStateChangedCallbacks mCallbacks; private FirebaseAuth mAuth; private DatabaseReference rootRef; TextView t1, t2; ImageView i1; EditText e1, e2; Button b1, b2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_phone_login); // 初始化视图 e1 = findViewById(R.id.Phonenoedittext); b1 = findViewById(R.id.PhoneVerify); t1 = findViewById(R.id.textView2Phone); i1 = findViewById(R.id.imageView2Phone); e2 = findViewById(R.id.OTPeditText); b2 = findViewById(R.id.OTPVERIFY); t2 = findViewById(R.id.textViewVerified); // 初始化Firebase实例 mAuth = FirebaseAuth.getInstance(); rootRef = FirebaseDatabase.getInstance().getReference(); // 初始化OTP回调 initPhoneAuthCallbacks(); // 发送OTP按钮点击事件 b1.setOnClickListener(v -> { String phoneNumber = e1.getText().toString().trim(); if (phoneNumber.isEmpty()) { Toast.makeText(PhoneLogin.this, "请输入手机号", Toast.LENGTH_SHORT).show(); return; } // 先检查数据库中是否存在该号码 checkPhoneNumberInDatabase(phoneNumber); }); // 验证OTP按钮点击事件 b2.setOnClickListener(v -> { String otpCode = e2.getText().toString().trim(); if (otpCode.isEmpty()) { Toast.makeText(PhoneLogin.this, "请输入验证码", Toast.LENGTH_SHORT).show(); return; } if (mVerificationId == null) { Toast.makeText(PhoneLogin.this, "请先获取验证码", Toast.LENGTH_SHORT).show(); return; } PhoneAuthCredential credential = PhoneAuthProvider.getCredential(mVerificationId, otpCode); signInWithPhoneAuthCredential(credential); }); } // 初始化PhoneAuth回调 private void initPhoneAuthCallbacks() { mCallbacks = new PhoneAuthProvider.OnVerificationStateChangedCallbacks() { @Override public void onVerificationCompleted(PhoneAuthCredential credential) { mVerificationInProgress = false; Toast.makeText(PhoneLogin.this, "验证自动完成", Toast.LENGTH_SHORT).show(); signInWithPhoneAuthCredential(credential); } @Override public void onVerificationFailed(FirebaseException e) { mVerificationInProgress = false; Toast.makeText(PhoneLogin.this, "验证失败", Toast.LENGTH_SHORT).show(); if (e instanceof FirebaseAuthInvalidCredentialsException) { Toast.makeText(PhoneLogin.this, "无效的手机号格式", Toast.LENGTH_SHORT).show(); } else if (e instanceof FirebaseTooManyRequestsException) { Toast.makeText(PhoneLogin.this, "请求过于频繁,请稍后再试", Toast.LENGTH_SHORT).show(); } } @Override public void onCodeSent(String verificationId, PhoneAuthProvider.ForceResendingToken token) { mVerificationId = verificationId; mResendToken = token; Toast.makeText(PhoneLogin.this, "验证码已发送", Toast.LENGTH_SHORT).show(); // 切换到OTP输入界面 toggleToOtpInputUI(); } }; } // 检查手机号是否在数据库中 private void checkPhoneNumberInDatabase(String phoneNumber) { DatabaseReference phoneRef = rootRef.child("Employees").child(phoneNumber); phoneRef.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot dataSnapshot) { if (dataSnapshot.exists()) { // 号码存在,发起OTP验证 startPhoneNumberVerification(phoneNumber); } else { Toast.makeText(PhoneLogin.this, "该CUG号码不存在", Toast.LENGTH_SHORT).show(); } } @Override public void onCancelled(@NonNull DatabaseError databaseError) { Toast.makeText(PhoneLogin.this, "数据库查询失败:" + databaseError.getMessage(), Toast.LENGTH_SHORT).show(); } }); } // 发起手机号验证 private void startPhoneNumberVerification(String phoneNumber) { if (mVerificationInProgress) { Toast.makeText(this, "验证已在进行中", Toast.LENGTH_SHORT).show(); return; } mVerificationInProgress = true; PhoneAuthProvider.getInstance().verifyPhoneNumber( phoneNumber, 60, TimeUnit.SECONDS, this, mCallbacks ); } // 切换到OTP输入UI private void toggleToOtpInputUI() { e1.setVisibility(View.GONE); b1.setVisibility(View.GONE); t1.setVisibility(View.GONE); i1.setVisibility(View.GONE); t2.setVisibility(View.VISIBLE); e2.setVisibility(View.VISIBLE); b2.setVisibility(View.VISIBLE); } private void signInWithPhoneAuthCredential(PhoneAuthCredential credential) { mAuth.signInWithCredential(credential) .addOnCompleteListener(this, task -> { if (task.isSuccessful()) { Toast.makeText(PhoneLogin.this, "验证成功", Toast.LENGTH_SHORT).show(); startActivity(new Intent(PhoneLogin.this, NavigationDrawer.class)); finish(); } else { Toast.makeText(PhoneLogin.this, "验证失败", Toast.LENGTH_SHORT).show(); if (task.getException() instanceof FirebaseAuthInvalidCredentialsException) { Toast.makeText(PhoneLogin.this, "无效的验证码", Toast.LENGTH_SHORT).show(); } } }); } }
代码关键修改点:
- 把视图初始化、Firebase实例初始化、按钮点击事件注册都放在
onCreate中,逻辑更清晰 - 单独提取
initPhoneAuthCallbacks方法,统一管理OTP回调逻辑 - 点击发送OTP时,先获取用户输入的手机号,再调用
checkPhoneNumberInDatabase查询数据库 - 只有数据库确认号码存在后,才调用
startPhoneNumberVerification发起OTP验证 - 增加了输入为空的校验,提升用户体验
- 提取
toggleToOtpInputUI方法,统一处理UI切换逻辑
这样修改后,就能实现仅当CUG号码存在于数据库时,才触发OTP认证的需求,同时保证了数据库的安全性。
内容的提问来源于stack exchange,提问作者Dpka
相关产品推荐
相关产品推荐

