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

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

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

开始之前

  1. 将 Firebase 添加到您的 iOS 项目
  2. 在您的 Podfile 中添加以下 pod:
    pod 'Firebase/Auth'
    
  3. 如果您尚未将自己的应用与您的 Firebase 项目关联,请在 Firebase 控制台中进行关联。

安全问题

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

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

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

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

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

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

启用应用验证

要使用电话号码身份验证,Firebase 必须要能验证电话号码登录请求是否来自您的应用。Firebase 身份验证可通过以下两种方法完成此操作:

  • 静默 APNs 通知:用户首次通过电话号码在设备上登录时,Firebase 身份验证会通过一条静默推送通知向设备发送令牌。如果您的应用成功收到 Firebase 的通知,就可以继续进行电话号码登录。

    对于 iOS 8.0 及更高版本,静默通知不需要用户明确同意,因此,即使用户拒绝在应用中接收 APNs 通知也不受影响。如此一来,在实现 Firebase 电话号码身份验证时,应用不需要请求用户授予权限即可接收推送通知。

  • reCAPTCHA 验证:如果无法发送或接收静默推送通知(例如,如果用户为应用停用了后台刷新,或者当您在 iOS 模拟器上测试应用时),Firebase 身份验证会使用 reCAPTCHA 验证来完成电话登录流程。reCAPTCHA 质询通常可以在不需要用户解决任何问题的情况下完成。

如果正确配置了静默推送通知,只有极少数的用户会经历 reCAPTCHA 流程。尽管如此,您应该确保无论静默推送通知是否可用,电话号码登录功能都可正常工作。

开始接收静默通知

要启用 APNs 通知以用于 Firebase 身份验证,请执行以下操作:

  1. 在 Xcode 中为您的项目启用推送通知
  2. 将您的 APNs 身份验证密钥上传到 Firebase。如果您还没有 APNs 身份验证密钥,请参阅配置 FCM APNs

    1. 在 Firebase 控制台中,在您的项目内依次选择齿轮图标、项目设置以及云消息传递标签。

    2. iOS 应用配置下的 APNs 身份验证密钥中,点击上传按钮。

    3. 转到您保存密钥的位置,选择该密钥,然后点击打开。添加该密钥的密钥 ID(可在 Apple Developer Member CenterCertificates, Identifiers & Profiles 中找到),然后点击上传

    如果您已有 APNs 证书,则可以改为上传证书。

设置 reCAPTCHA 验证

要让 Firebase SDK 能使用 reCAPTCHA 验证,请执行以下操作:

  1. 向您的 Xcode 项目添加自定义网址架构:
    1. 打开您的项目配置:在左侧的树状视图中双击项目名称。从 TARGETS 部分中选择您的应用,然后选择 Info 标签,并展开 URL Types 部分。
    2. 点击 + 按钮,并为您的倒序客户端 ID 添加一个网址架构。要找到该值,请打开 GoogleService-Info.plist 配置文件,然后查找 REVERSED_CLIENT_ID 键。复制该键的值,并将其粘贴到配置页面上的 URL Schemes 框中。将其他字段留空。

      完成上述操作后,您的配置应显示如下(但其中的值应替换为您的应用特有的值):

  2. 可选:如果要自定义应用在向用户显示 reCAPTCHA 时呈现 SFSafariViewControllerUIWebView 的方式,请创建一个符合 FIRAuthUIDelegate 协议的自定义类,并将其传递给 verifyPhoneNumber:UIDelegate:completion:

向用户的电话发送验证码

要启动电话号码登录,请向用户显示一个要求其提供电话号码的界面,然后调用 verifyPhoneNumber:UIDelegate:completion: 以请求 Firebase 通过短信向用户电话发送身份验证代码:

  1. 获取用户的电话号码。

    虽然各地相关的法律要求可能不尽相同,但为了让用户心中有数,最佳做法是将以下事宜告知用户:如果用户使用电话登录方式,则可能会收到一条验证短信,并需按标准费率支付短信费用。

  2. 调用 verifyPhoneNumber:UIDelegate:completion:,并向其传递用户的电话号码。

    Swift

    PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber, uiDelegate: nil) { (verificationID, error) in
      if let error = error {
        self.showMessagePrompt(error.localizedDescription)
        return
      }
      // Sign in using the verificationID and the code sent to the user
      // ...
    }

    Objective-C

    [[FIRPhoneAuthProvider provider] verifyPhoneNumber:userInput
                                            UIDelegate:nil
                                            completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) {
      if (error) {
        [self showMessagePrompt:error.localizedDescription];
        return;
      }
      // Sign in using the verificationID and the code sent to the user
      // ...
    }];

    当您调用 verifyPhoneNumber:UIDelegate:completion: 时,Firebase 会向您的应用发送一条静默推送通知,或向用户发出 reCAPTCHA 质询。在您的应用收到通知或者用户完成 reCAPTCHA 质询后,Firebase 会向指定的电话号码发送一条包含身份验证代码的短信,并将验证 ID 传递给您的完成函数。您需要有验证码和验证 ID 才能让用户登录。

    您还可以通过身份验证实例中的 languageCode 属性指定身份验证语言,以对 Firebase 发送的短信进行本地化。

    Swift

     // Change language code to french.
     Auth.auth().languageCode = "fr";
    

    Objective-C

     // Change language code to french.
     [FIRAuth auth].languageCode = @"fr";
    
  3. 保存验证 ID,并在应用加载时将其还原。这样可确保即使您的应用在用户完成登录流程之前被终止(例如,用户切换到短信应用时),您仍然拥有有效的验证 ID。

    您可以用任何方式永久保留验证 ID。一个简单的方法是使用 NSUserDefaults 对象保存验证 ID:

    Swift

    UserDefaults.standard.set(verificationID, forKey: "authVerificationID")
    

    Objective-C

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject:verificationID forKey:@"authVerificationID"];
    

    然后,您可以还原保存的值:

    Swift

    let verificationID = UserDefaults.standard.string(forKey: "authVerificationID")
    

    Objective-C

    NSString *verificationID = [defaults stringForKey:@"authVerificationID"];
    

如果成功调用 verifyPhoneNumber:UIDelegate:completion:,那么您可以提示用户在收到验证码短信时输入验证码。

让用户使用验证码登录

用户将短信中的验证码提供给您的应用之后,通过从验证码和验证 ID 创建一个 FIRPhoneAuthCredential 对象并将此对象传递给 signInWithCredential:completion: 使用户登录。

  1. 从用户处获取验证码。
  2. 从验证码和验证 ID 创建 FIRPhoneAuthCredential 对象。

    Swift

    let credential = PhoneAuthProvider.provider().credential(
        withVerificationID: verificationID,
        verificationCode: verificationCode)

    Objective-C

    FIRAuthCredential *credential = [[FIRPhoneAuthProvider provider]
        credentialWithVerificationID:verificationID
                    verificationCode:userInput];
  3. 使用 FIRPhoneAuthCredential 对象使用户登录:

    Swift

    Auth.auth().signInAndRetrieveData(with: credential) { (authResult, error) in
      if let error = error {
        // ...
        return
      }
      // User is signed in
      // ...
    }

    Objective-C

    [[FIRAuth auth] signInAndRetrieveDataWithCredential:credential
                                             completion:^(FIRAuthDataResult * _Nullable authResult,
                                                          NSError * _Nullable error) {
      if (error) {
        // ...
        return;
      }
      // User successfully signed in. Get user data from the FIRUser object
      if (authResult == nil) { return; }
      FIRUser *user = authResult.user;
      // ...
    }];

使用已列入白名单的电话号码进行测试

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

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

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

  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 模拟器或没有 Google Play 服务的 Android 模拟器进行测试。

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

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

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

集成测试

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

对于 iOS 应用,您必须在调用 verifyPhoneNumber 之前将 appVerificationDisabledForTesting 设置设为 TRUE。这样做不需要任何 APNs 令牌,也不需要在后台发送静默推送通知,因此更便于您在模拟器中进行测试。此外,这也会停用 reCAPTCHA 后备流程。

请注意,停用应用验证后,使用未列入白名单的电话号码将无法完成登录。只有已列入白名单的电话号码才能与此 API 配合使用。

Swift

let phoneNumber = "+16505554567"

// This test verification code is specified for the given test phone number in the developer console.
let testVerificationCode = "123456"

Auth.auth().settings.isAppVerificationDisabledForTesting = TRUE
PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber, uiDelegate:nil) {
                                                            verificationID, error in
    if (error) {
      // Handles error
      self.handleError(error)
      return
    }
    let credential = PhoneAuthProvider.provider().credential(withVerificationID: verificationID ?? "",
                                                               verificationCode: testVerificationCode)
    Auth.auth().signInAndRetrieveData(with: credential) { authData, error in
      if (error) {
        // Handles error
        self.handleError(error)
        return
      }
      _user = authData.user
    }];
}];

Objective-C

NSString *phoneNumber = @"+16505554567";

// This test verification code is specified for the given test phone number in the developer console.
NSString *testVerificationCode = @"123456";

[FIRAuth auth].settings.appVerificationDisabledForTesting = YES;
[[FIRPhoneAuthProvider provider] verifyPhoneNumber:phoneNumber
                                        completion:^(NSString *_Nullable verificationID,
                                                     NSError *_Nullable error) {
    if (error) {
      // Handles error
      [self handleError:error];
      return;
    }
    FIRAuthCredential *credential =
        [FIRPhoneAuthProvider credentialWithVerificationID:verificationID
                                          verificationCode:testVerificationCode];
    [FIRAuth auth] signInWithAndRetrieveDataWithCredential:credential
                                                completion:^(FIRUser *_Nullable user,
                                                             NSError *_Nullable error) {
      if (error) {
        // Handles error
        [self handleError:error];
        return;
      }
      _user = user;
    }];
}];

附录:使用电话号码登录而不调配

Firebase 身份验证使用方法调配 (method swizzling) 自动获取您的应用的 APNs 令牌,处理 Firebase 发送给您的应用的静默推送通知,并自动拦截在验证期间来自 reCAPTCHA 验证页面的自定义架构重定向。

如果您不想使用调配,可以通过向应用的 Info.plist 文件添加标记 FirebaseAppDelegateProxyEnabled 并将其设置为 NO 来停用调配。请注意,将此标记设置为 NO 也会为其他 Firebase 产品(包括 Firebase 云消息传递)停用调配。

如果停用调配,您必须显式传递 APNs 设备令牌、推送通知和自定义架构重定向网址给 Firebase 身份验证。

要获取 APNs 设备令牌,请实现 application:didRegisterForRemoteNotificationsWithDeviceToken: 方法,并在其中将设备令牌传递给 FIRAuthsetAPNSToken:type: 方法。

Swift

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
  // Pass device token to auth
  Auth.auth().setAPNSToken(deviceToken, type: AuthAPNSTokenTypeProd)

  // Further handling of the device token if needed by the app
  // ...
}

Objective-C

- (void)application:(UIApplication *)application
    didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
  // Pass device token to auth.
  [[FIRAuth auth] setAPNSToken:deviceToken type:FIRAuthAPNSTokenTypeProd];
  // Further handling of the device token if needed by the app.
}

要处理推送通知,请在 application:didReceiveRemoteNotification:fetchCompletionHandler: 方法中,通过调用 FIRAuthcanHandleNotification: 方法检查 Firebase 身份验证相关的通知。

Swift

func application(_ application: UIApplication,
    didReceiveRemoteNotification notification: [AnyHashable : Any],
    fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
  if Auth.auth().canHandleNotification(notification) {
    completionHandler(UIBackgroundFetchResultNoData)
    return
  }
  // This notification is not auth related, developer should handle it.
}

Objective-C

- (void)application:(UIApplication *)application
    didReceiveRemoteNotification:(NSDictionary *)notification
          fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
  // Pass notification to auth and check if they can handle it.
  if ([[FIRAuth auth] canHandleNotification:notification]) {
    completionHandler(UIBackgroundFetchResultNoData);
    return;
  }
  // This notification is not auth related, developer should handle it.
}

要处理自定义架构重定向网址,请为运行 iOS 8 及更早版本的设备实现 application:openURL:sourceApplication:annotation: 方法,为运行 iOS 9 及更高版本的设备实现 application:openURL:options: 方法,并在两个方法中传递网址给 FIRAuthcanHandleURL 方法。

Swift

// For iOS 9+
func application(_ application: UIApplication, open url: URL,
    options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool {
  if Auth.auth().canHandle(url) {
    return true
  }
  // URL not auth related, developer should handle it.
}

// For iOS 8-
func application(_ application: UIApplication,
                 open url: URL,
                 sourceApplication: String?,
                 annotation: Any) -> Bool {
  if Auth.auth().canHandle(url) {
    Return true
  }
  // URL not auth related, developer should handle it.
}

Objective-C

// For iOS 9+
- (BOOL)application:(UIApplication *)app
            openURL:(NSURL *)url
            options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
  if ([[FIRAuth auth] canHandleURL:url]) {
    return YES;
  }
  // URL not auth related, developer should handle it.
}

// For iOS 8-
- (BOOL)application:(UIApplication *)application
            openURL:(NSURL *)url
  sourceApplication:(NSString *)sourceApplication
         annotation:(id)annotation {
  if ([[FIRAuth auth] canHandleURL:url]) {
    return YES;
  }
  // URL not auth related, developer should handle it.
}

后续步骤

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

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

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

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

要让用户退出帐号,请调用 signOut:

Swift

    let firebaseAuth = Auth.auth()
do {
  try firebaseAuth.signOut()
} catch let signOutError as NSError {
  print ("Error signing out: %@", signOutError)
}
  

Objective-C

    NSError *signOutError;
BOOL status = [[FIRAuth auth] signOut:&signOutError];
if (!status) {
  NSLog(@"Error signing out: %@", signOutError);
  return;
}

您可能还需要添加适用于所有身份验证错误的错误处理代码。请参阅处理错误

发送以下问题的反馈:

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