You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何结合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();
                        }
                    }
                });
    }
}

代码关键修改点:

  1. 把视图初始化、Firebase实例初始化、按钮点击事件注册都放在onCreate中,逻辑更清晰
  2. 单独提取initPhoneAuthCallbacks方法,统一管理OTP回调逻辑
  3. 点击发送OTP时,先获取用户输入的手机号,再调用checkPhoneNumberInDatabase查询数据库
  4. 只有数据库确认号码存在后,才调用startPhoneNumberVerification发起OTP验证
  5. 增加了输入为空的校验,提升用户体验
  6. 提取toggleToOtpInputUI方法,统一处理UI切换逻辑

这样修改后,就能实现仅当CUG号码存在于数据库时,才触发OTP认证的需求,同时保证了数据库的安全性。

内容的提问来源于stack exchange,提问作者Dpka

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 10:26:10