在 Android 上使用电话号码进行 Firebase 身份验证

借助 Firebase 身份验证,您可以通过向用户电话发送短信的方式协助用户登录。用户使用短信中包含的一次性验证码登录。

要向应用中添加电话号码登录方式,最简单的方法就是使用 FirebaseUI。它包括一个普适性登录微件,可为电话号码登录方式、基于密码的登录方式和联合登录方式实现登录流程。本文档介绍了如何使用 Firebase SDK 实现电话号码登录流程。

开始之前

  1. 将 Firebase 添加到您的 Android 项目
  2. 将 Firebase 身份验证的依赖项添加至您的应用级 build.gradle 文件:
    implementation 'com.google.firebase:firebase-auth:16.0.2'
  3. 如果您尚未将您的应用与 Firebase 项目相关联,请在 Firebase 控制台中加以关联。
  4. 如果您尚未在 Firebase 控制台中设置您的应用的 SHA-1 哈希,请执行此操作。如需详细了解如何查找您的应用的 SHA-1 哈希,请参阅对客户端进行身份验证

另请注意,电话号码登录需要一台真机设备,在模拟器上无法实现。

安全问题

与其他可用的方法相比,仅使用电话号码进行身份验证的方式虽然便捷,但安全性较低,因为同一电话号码很容易流转给不同用户使用。此外,在具有多份用户个人资料的设备上,任何一位可以收到短信的用户都能使用该设备的电话号码登录帐号。

如果您在应用中使用基于电话号码的登录,则应同时提供更安全的登录方法,并将使用电话号码登录的安全隐患告知用户。

为您的 Firebase 项目启用电话号码登录

要让用户能够通过短信登录,您必须先为 Firebase 项目启用电话号码登录功能,操作步骤如下:

  1. Firebase 控制台中,打开 Authentication(身份验证)部分。
  2. 登录方法页面上,启用电话号码登录方法。

Firebase 的电话号码登录请求的配额非常充足,大多数应用都不会受到配额问题的影响。但是,如果您需要通过电话号码身份验证让数量非常多的用户登录,则可能需要升级您的定价方案。请参阅定价页面。

向用户的电话发送验证码

要启动电话号码登录流程,请向用户显示一个提示他们输入其电话号码的界面。虽然相关的法律要求可能不尽相同,但为了让用户心中有数,最佳做法是将以下事宜告知用户:如果用户使用电话登录方式,则可能会收到一条验证短信,并需按标准费率支付短信费用。

然后,将用户的电话号码传递给 PhoneAuthProvider.verifyPhoneNumber 方法,以请求 Firebase 验证该电话号码。例如:

PhoneAuthProvider.getInstance().verifyPhoneNumber(
        phoneNumber,        // Phone number to verify
        60,                 // Timeout duration
        TimeUnit.SECONDS,   // Unit of timeout
        this,               // Activity (for callback binding)
        mCallbacks);        // OnVerificationStateChangedCallbacks

verifyPhoneNumber 方法是可重入的:如果您多次调用它(例如在某个 Activity 的 onStart 方法中),那么除非原始请求已超时,否则 verifyPhoneNumber 方法将不会发送第二条短信。

如果您的应用在用户登录成功之前关闭了(例如,当用户正在使用其短信应用时),则可以利用此行为继续完成电话号码登录流程。在您调用 verifyPhoneNumber 后,设置一个表示正在进行验证的标记。然后,在您的 Activity 的 onSaveInstanceState 方法中保存该标记,并在 onRestoreInstanceState 中恢复该标记。最后,在该 Activity 的 onStart 方法中,检查是否已在进行验证,如果是,请再次调用 verifyPhoneNumber。当验证完成或失败时,请务必清除该标记(请参阅验证回调函数)。

要轻松处理屏幕旋转以及涉及 Activity 重新启动的其他情况,请将您的 Activity 传递给 verifyPhoneNumber 方法。当 Activity 停止时,系统将自动取消与回调函数的关联,因此您可以在回调方法中自由编写界面转换代码。

Firebase 发送的短信还可通过指定身份验证语言(通过您的身份验证实例中的 setLanguageCode 方法来指定)来进行本地化。

auth.setLanguageCode("fr");
// To apply the default app language instead of explicitly setting it.
// auth.useAppLanguage();

在调用 PhoneAuthProvider.verifyPhoneNumber 时,您还必须提供一个 OnVerificationStateChangedCallbacks 的实例,该实例包含了用于处理请求结果的回调函数的实现代码。例如:

mCallbacks = new PhoneAuthProvider.OnVerificationStateChangedCallbacks() {

    @Override
    public void onVerificationCompleted(PhoneAuthCredential credential) {
        // This callback will be invoked in two situations:
        // 1 - Instant verification. In some cases the phone number can be instantly
        //     verified without needing to send or enter a verification code.
        // 2 - Auto-retrieval. On some devices Google Play services can automatically
        //     detect the incoming verification SMS and perform verification without
        //     user action.
        Log.d(TAG, "onVerificationCompleted:" + credential);

        signInWithPhoneAuthCredential(credential);
    }

    @Override
    public void onVerificationFailed(FirebaseException e) {
        // This callback is invoked in an invalid request for verification is made,
        // for instance if the the phone number format is not valid.
        Log.w(TAG, "onVerificationFailed", e);

        if (e instanceof FirebaseAuthInvalidCredentialsException) {
            // Invalid request
            // ...
        } else if (e instanceof FirebaseTooManyRequestsException) {
            // The SMS quota for the project has been exceeded
            // ...
        }

        // Show a message and update the UI
        // ...
    }

    @Override
    public void onCodeSent(String verificationId,
                           PhoneAuthProvider.ForceResendingToken token) {
        // The SMS verification code has been sent to the provided phone number, we
        // now need to ask the user to enter the code and then construct a credential
        // by combining the code with a verification ID.
        Log.d(TAG, "onCodeSent:" + verificationId);

        // Save verification ID and resending token so we can use them later
        mVerificationId = verificationId;
        mResendToken = token;

        // ...
    }
};

验证回调函数

在大多数应用中,您需要实现 onVerificationCompletedonVerificationFailedonCodeSent 回调函数。此外可能还需要实现 onCodeAutoRetrievalTimeOut,具体取决于您的应用的要求。

onVerificationCompleted(PhoneAuthCredential)

在下列两种情况中可调用此方法:

  • 即时验证:在某些情况下可以即时验证电话号码,而无需发送或输入验证码。
  • 自动检索:在某些设备上,Google Play 服务可以自动检测收到的验证短信并进行验证,而无需用户执行任何操作。(某些运营商可能不支持这项功能。)
在上述任何一种情况下,系统成功验证用户的电话号码之后,您就可以使用传递给该回调函数的 PhoneAuthCredential 对象让用户登录

onVerificationFailed(FirebaseException)

调用此方法的目的是响应无效的验证请求(例如所指定的电话号码或验证码无效的请求)。

onCodeSent(String verificationId, PhoneAuthProvider.ForceResendingToken)

可选。通过短信将验证码发送到所提供的电话号码后,可调用此方法。

当调用此方法时,大多数应用会显示一个界面,提示用户输入短信中的验证码。(同时,系统可能正在后台进行自动验证。)当用户输入验证码后,您便可使用该验证码和之前传递给该方法的验证 ID 来创建 PhoneAuthCredential 对象,接下来就可以使用该对象让用户登录。但是,有些应用可能会等到 onCodeAutoRetrievalTimeOut 被调用之后才会显示验证码界面(不推荐此做法)。

onCodeAutoRetrievalTimeOut(String verificationId)

可选。如果没有先触发 onVerificationCompleted,在指定给 verifyPhoneNumber 的超时时长过去后,可调用此方法。在没有 SIM 卡的设备上,由于不能进行短信自动检索,因此系统会立即调用此方法。

有些应用在自动验证期会阻止用户输入,直到自动验证期超时后,才会显示一个界面,并在该界面中提示用户输入短信中的验证码(不推荐此做法)。

创建 PhoneAuthCredential 对象

在用户输入 Firebase 发送至用户电话的验证码后,使用该验证码和之前传递给 onCodeSentonCodeAutoRetrievalTimeOut 回调函数的验证 ID 创建一个 PhoneAuthCredential 对象。(当 onVerificationCompleted 被调用时,您将直接获得一个 PhoneAuthCredential 对象,在这种情况下您可以跳过这一步骤。)

要创建 PhoneAuthCredential 对象,请调用 PhoneAuthProvider.getCredential

PhoneAuthCredential credential = PhoneAuthProvider.getCredential(verificationId, code);

让用户登录

在您获取了 PhoneAuthCredential 对象(无论是在 onVerificationCompleted 回调函数中还是通过调用 PhoneAuthProvider.getCredential 获取)后,将 PhoneAuthCredential 对象传递给 FirebaseAuth.signInWithCredential 以完成登录流程:

private void signInWithPhoneAuthCredential(PhoneAuthCredential credential) {
    mAuth.signInWithCredential(credential)
            .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
                @Override
                public void onComplete(@NonNull Task<AuthResult> task) {
                    if (task.isSuccessful()) {
                        // Sign in success, update UI with the signed-in user's information
                        Log.d(TAG, "signInWithCredential:success");

                        FirebaseUser user = task.getResult().getUser();
                        // ...
                    } else {
                        // Sign in failed, display a message and update the UI
                        Log.w(TAG, "signInWithCredential:failure", task.getException());
                        if (task.getException() instanceof FirebaseAuthInvalidCredentialsException) {
                            // The verification code entered was invalid
                        }
                    }
                }
            });
}

通过已列入白名单的电话号码进行测试

您可以通过 Firebase 控制台将电话号码列入白名单以便于开发。将电话号码列入白名单有以下好处:

  • 可测试电话号码身份验证,而不占用使用量配额。
  • 可测试电话号码身份验证,而无需实际发送短信。
  • 可使用同一电话号码运行连续测试,而不会受到短信发送数量的限制。如果审核人员恰好使用相同的电话号码进行测试,这样做会使在 App Store 审核过程中遭拒的风险降至最低。
  • 无需其他操作即可在开发环境中轻松进行测试,例如可以在 iOS 模拟器或 Android 模拟器中进行开发而无需借助 Google Play 服务。
  • 可编写集成测试,而不会被通常在生产环境中应用于真实电话号码的安全检查阻止。

要列入白名单的电话号码必须符合以下要求:

  1. 确保您使用了不存在的虚构号码。Firebase 身份验证不允许您将真实用户使用的现有电话号码列入白名单。您可以使用以 555 为前缀的数字作为美国测试电话号码,例如:+1 650-555-3434
  2. 电话号码必须采用正确的格式,以符合长度要求和其他限制。这些电话号码仍将与真实用户的电话号码一样经过相同的验证。
  3. 您最多可以添加 10 个电话号码用于开发。
  4. 使用难以猜到的测试电话号码/验证码,并经常更换。

将电话号码和验证码列入白名单

  1. Firebase 控制台中,打开 Authentication(身份验证)部分。
  2. 登录方法标签中,启用电话登录提供方(如果您尚未启用)。
  3. 打开用于测试的电话号码折叠菜单。
  4. 提供您想要测试的电话号码,例如 +1 650-555-3434。
  5. 为该特定号码提供 6 位验证码,例如 654321。
  6. 添加该号码。如有需要,您可以删除电话号码及其验证码,只需将鼠标悬停在相应的行上并点击垃圾桶图标即可。

手动测试

您可以直接在应用中开始使用已列入白名单的电话号码。这样一来,您就可以在开发阶段执行手动测试,而不会遇到配额问题或受到限制。您也可以直接通过 iOS 模拟器或 Android 模拟器进行测试,而无需安装 Google Play 服务。

当您提供已列入白名单的电话号码并发送验证码时,系统实际上不会发送短信。相反,您需要提供之前配置的验证码才能完成登录。

完成登录后,系统会使用该电话号码创建一位 Firebase 用户。该用户的行为和属性与真实电话号码用户相同,并且可通过同样的方式使用实时数据库/Cloud Firestore 和其他服务。在此过程中生成的 ID 令牌与真实电话号码用户的令牌具有相同的签名。

您还可以通过自定义声明针对此类用户设置测试角色,以将其作为虚拟用户区分开来(如果您想进一步限制访问权限)。

集成测试

除了手动测试外,Firebase 身份验证还提供 API 以帮助编写用于进行电话身份验证测试的集成测试。这些 API 通过停用 reCAPTCHA 要求(在网页应用中)和静默推送通知(在 iOS 应用中)来停用应用验证。因此,您可以在这些流程中进行自动测试,并且实现起来更加容易。此外,借助这些 API,您还可以在 Android 上测试即时验证流程。

在 Android 上,您可以轻松使用已列入白名单的电话号码,而无需进行其他 API 调用。使用已列入白名单的号码调用 verifyPhoneNumber 会触发 onCodeSent 回调函数,在这种情况下,您将需要提供对应的验证码。这样,您将可以在 Android 模拟器中进行测试。

// Testing can be done directly with the whitelisted numbers.
private String mVerificationId = null;
String phoneNum = "+16505554567";
String testVerificationCode = "123456";
// Whenever verification is triggered with the whitelisted number,
// provided it is not set for auto-retrieval, onCodeSent will be triggered.
PhoneAuthProvider.getInstance().verifyPhoneNumber(
    phoneNum, 30 /*timeout*/, TimeUnit.SECONDS,
    new OnVerificationStateChangedCallbacks() {
        @Override
        public void onCodeSent(String verificationId) {
            mVerificationId = verificationId;
            // The corresponding whitelisted code above should be used to complete sign-in.
            MyActivity.this.enableUserManuallyInputCode();
        }

        @Override
        void onVerificationCompleted(AuthCredential credential) {
            signInWithCredential(credential);
        }
        void onVerificationFailed(FirebaseAuthException) {
        }
  });

此外,您可以在 Android 中测试自动检索流程,只需通过调用 setAutoRetrievedSmsCodeForPhoneNumber 来设置列入白名单的号码及其对应的验证码来进行自动检索即可。

verifyPhoneNumber 被调用时,它会直接通过 PhoneAuthCredential 触发 onVerificationCompleted。这仅适用于已列入白名单的电话号码。

将您的应用发布到 Google Play 商店时,请确保此验证已停用,并且在您的应用中没有硬编码的已列入白名单的电话号码。

// The test phone number and code should be whitelisted in the console.
String phoneNumber = "+16505554567";
String smsCode = "123456";

FirebaseAuth firebaseAuth = FirebaseAuth.getInstance();
FirebaseAuthSettings firebaseAuthSettings = firebaseAuth.getFirebaseAuthSettings();

// Configure faking the auto-retrieval with the whitelisted numbers.
firebaseAuthSettings.setAutoRetrievedSmsCodeForPhoneNumber(phoneNumber, smsCode);

PhoneAuthProvider phoneAuthProvider = PhoneAuthProvider.getInstance();
phoneAuthProvider.verifyPhoneNumber(
  phoneNumber,
  60L,
  TimeUnit.SECONDS,
  this, /* activity */
  new OnVerificationStateChangedCallbacks() {
    @Override
    public void onVerificationCompleted(PhoneAuthCredential credential) {
      // Instant verification is applied and a credential is directly returned.
    }

    ... /* other callbacks */
  }

后续步骤

在用户首次登录后,系统会创建一个新的用户帐号,并将其与该用户登录时使用的凭据(即用户名和密码、电话号码或者身份验证提供方信息)相关联。此新帐号存储在您的 Firebase 项目中,无论用户采用何种方式登录,您项目中的每个应用都可以使用此帐号来识别用户。

  • 在您的应用中,您可以从 FirebaseUser 对象获取用户的个人资料基本信息。请参阅管理用户

  • 在您的 Firebase 实时数据库和 Cloud Storage 安全规则中,您可以从 auth 变量获取已登录用户的唯一身份用户 ID,然后用此 ID 来控制用户可以访问哪些数据。

您可以允许用户通过多种身份验证提供方服务登录您的应用,只需将多个身份验证提供方凭据关联至现有用户帐号即可。

要让用户退出登录,可调用 signOut

FirebaseAuth.getInstance().signOut();

发送以下问题的反馈:

此网页
需要帮助?请访问我们的支持页面