在Android Java项目中基于Firebase实现登录时的SMS 2FA后续流程
Firebase SMS多因素认证(2FA)OTP验证页面实现方案
以下是完整的实现步骤,涵盖OTP页面创建、权限配置、跳转逻辑和OTP验证流程:
1. 创建OTP输入Activity
1.1 布局文件(res/layout/activity_otp_verify.xml)
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp" android:gravity="center"> <EditText android:id="@+id/et_otp" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="请输入6位验证码" android:inputType="number" android:maxLength="6" android:textSize="18sp" /> <Button android:id="@+id/btn_verify_otp" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="验证验证码" android:layout_marginTop="16dp" /> <Button android:id="@+id/btn_resend_otp" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="重新发送验证码" android:layout_marginTop="8dp" /> </LinearLayout>
1.2 Java代码(OtpVerifyActivity.java)
import android.Manifest; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; import android.text.TextUtils; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.FirebaseException; import com.google.firebase.auth.FirebaseUser; import com.google.firebase.auth.MultiFactorAssertion; import com.google.firebase.auth.MultiFactorResolver; import com.google.firebase.auth.PhoneAuthCredential; import com.google.firebase.auth.PhoneAuthOptions; import com.google.firebase.auth.PhoneAuthProvider; import com.google.firebase.auth.PhoneMultiFactorGenerator; import com.google.firebase.auth.PhoneMultiFactorInfo; import java.util.concurrent.TimeUnit; public class OtpVerifyActivity extends AppCompatActivity { private static final int SMS_PERMISSION_REQUEST_CODE = 100; private EditText etOtp; private Button btnVerifyOtp, btnResendOtp; private MultiFactorResolver multiFactorResolver; private PhoneMultiFactorInfo selectedPhoneHint; private String verificationId; private PhoneAuthProvider.ForceResendingToken resendingToken; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_otp_verify); // 检查并申请SMS权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.SEND_SMS}, SMS_PERMISSION_REQUEST_CODE); } else { initViewsAndLogic(); } } private void initViewsAndLogic() { // 初始化视图 etOtp = findViewById(R.id.et_otp); btnVerifyOtp = findViewById(R.id.btn_verify_otp); btnResendOtp = findViewById(R.id.btn_resend_otp); // 获取从登录页传递的参数 multiFactorResolver = getIntent().getParcelableExtra("MULTI_FACTOR_RESOLVER"); selectedPhoneHint = (PhoneMultiFactorInfo) getIntent().getParcelableExtra("SELECTED_PHONE_HINT"); // 初始发送OTP sendSmsOtp(); // 验证按钮点击事件 btnVerifyOtp.setOnClickListener(v -> { String otp = etOtp.getText().toString().trim(); if (TextUtils.isEmpty(otp) || otp.length() != 6) { Toast.makeText(OtpVerifyActivity.this, "请输入有效的6位验证码", Toast.LENGTH_SHORT).show(); return; } verifyOtp(otp); }); // 重新发送按钮点击事件 btnResendOtp.setOnClickListener(v -> { if (resendingToken != null) { resendSmsOtp(); } else { sendSmsOtp(); } }); } // 发送SMS OTP private void sendSmsOtp() { PhoneAuthProvider.getInstance().verifyPhoneNumber( PhoneAuthOptions.newBuilder(FirebaseAuth.getInstance()) .setPhoneNumber(selectedPhoneHint.getPhoneNumber()) .setTimeout(60L, TimeUnit.SECONDS) .setActivity(this) .setCallbacks(new PhoneAuthProvider.OnVerificationStateChangedCallbacks() { @Override public void onVerificationCompleted(PhoneAuthCredential credential) { // 自动验证完成,直接使用凭证登录 verifyOtp(credential.getSmsCode()); } @Override public void onVerificationFailed(FirebaseException e) { Toast.makeText(OtpVerifyActivity.this, "验证码发送失败:" + e.getMessage(), Toast.LENGTH_SHORT).show(); } @Override public void onCodeSent(String s, PhoneAuthProvider.ForceResendingToken token) { super.onCodeSent(s, token); verificationId = s; resendingToken = token; Toast.makeText(OtpVerifyActivity.this, "验证码已发送至您的手机", Toast.LENGTH_SHORT).show(); } }) .build() ); } // 重新发送SMS OTP private void resendSmsOtp() { PhoneAuthProvider.getInstance().verifyPhoneNumber( PhoneAuthOptions.newBuilder(FirebaseAuth.getInstance()) .setPhoneNumber(selectedPhoneHint.getPhoneNumber()) .setTimeout(60L, TimeUnit.SECONDS) .setActivity(this) .setCallbacks(new PhoneAuthProvider.OnVerificationStateChangedCallbacks() { @Override public void onVerificationCompleted(PhoneAuthCredential credential) { verifyOtp(credential.getSmsCode()); } @Override public void onVerificationFailed(FirebaseException e) { Toast.makeText(OtpVerifyActivity.this, "验证码重发失败:" + e.getMessage(), Toast.LENGTH_SHORT).show(); } @Override public void onCodeSent(String s, PhoneAuthProvider.ForceResendingToken token) { super.onCodeSent(s, token); verificationId = s; resendingToken = token; Toast.makeText(OtpVerifyActivity.this, "验证码已重新发送", Toast.LENGTH_SHORT).show(); } }) .setForceResendingToken(resendingToken) .build() ); } // 验证OTP并完成登录 private void verifyOtp(String otp) { if (verificationId == null) { Toast.makeText(this, "请先获取验证码", Toast.LENGTH_SHORT).show(); return; } PhoneAuthCredential credential = PhoneAuthProvider.getCredential(verificationId, otp); MultiFactorAssertion assertion = PhoneMultiFactorGenerator.getAssertion(credential); multiFactorResolver.resolveSignIn(assertion) .addOnCompleteListener(task -> { if (task.isSuccessful()) { // MFA验证成功,登录完成 FirebaseUser user = task.getResult().getUser(); Toast.makeText(OtpVerifyActivity.this, "登录成功", Toast.LENGTH_SHORT).show(); // 跳转到主页面 startActivity(new Intent(OtpVerifyActivity.this, MainActivity.class)); finish(); } else { Toast.makeText(OtpVerifyActivity.this, "验证码验证失败:" + task.getException().getMessage(), Toast.LENGTH_SHORT).show(); } }); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == SMS_PERMISSION_REQUEST_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { initViewsAndLogic(); } else { Toast.makeText(this, "需要授予发送SMS权限才能完成验证", Toast.LENGTH_SHORT).show(); finish(); } } } }
2. 配置权限与Activity注册
在AndroidManifest.xml中添加以下内容:
<!-- 发送SMS权限 --> <uses-permission android:name="android.permission.SEND_SMS" /> <!-- 可选:接收/读取SMS权限,用于自动填充验证码 --> <uses-permission android:name="android.permission.RECEIVE_SMS" /> <uses-permission android:name="android.permission.READ_SMS" /> <!-- 注册OTP验证Activity --> <activity android:name=".OtpVerifyActivity" />
3. 修改登录页跳转逻辑
在登录页的signInWithEmailAndPassword回调中,处理FirebaseAuthMultiFactorException并跳转到OTP页面:
FirebaseAuth.getInstance() .signInWithEmailAndPassword(email, password) .addOnCompleteListener(new OnCompleteListener<AuthResult>() { @Override public void onComplete(@NonNull Task<AuthResult> task) { if (task.isSuccessful()) { // 用户未启用第二因素认证,登录成功 startActivity(new Intent(LoginActivity.this, MainActivity.class)); finish(); return; } if (task.getException() instanceof FirebaseAuthMultiFactorException) { FirebaseAuthMultiFactorException e = (FirebaseAuthMultiFactorException) task.getException(); MultiFactorResolver multiFactorResolver = e.getResolver(); // 若存在多个认证方式,可弹窗让用户选择,这里默认取第一个SMS方式 for (MultiFactorInfo hint : multiFactorResolver.getHints()) { if (hint instanceof PhoneMultiFactorInfo) { PhoneMultiFactorInfo phoneHint = (PhoneMultiFactorInfo) hint; Intent intent = new Intent(LoginActivity.this, OtpVerifyActivity.class); intent.putExtra("MULTI_FACTOR_RESOLVER", multiFactorResolver); intent.putExtra("SELECTED_PHONE_HINT", phoneHint); startActivity(intent); finish(); return; } } Toast.makeText(LoginActivity.this, "未找到可用的SMS认证方式", Toast.LENGTH_SHORT).show(); } else { // 其他登录失败情况 Toast.makeText(LoginActivity.this, "登录失败:" + task.getException().getMessage(), Toast.LENGTH_SHORT).show(); } } });
关键说明
- 权限处理:Android 6.0及以上需动态申请
SEND_SMS权限,否则无法发送验证码。 - 自动验证:
onVerificationCompleted回调会在系统自动获取到验证码时触发,无需用户手动输入。 - 多方式选择:若用户绑定了多个第二因素认证方式,可添加弹窗让用户选择具体使用的方式,而非默认取第一个。
内容的提问来源于stack exchange,提问作者Bhumesh Thakur
相关产品推荐
相关产品推荐

