在 Apple 平台上使用電話號碼驗證 Firebase

您可以使用 Firebase 驗證功能傳送簡訊讓使用者登入 傳送到使用者的手機上使用者透過 簡訊。

如要在應用程式中新增電話號碼登入程序,最簡單的方法是使用 FirebaseUI, 包含用於實作手機登入流程的置入登入小工具 以及密碼和聯合登入。這份文件 說明如何使用 Firebase SDK 導入電話號碼登入流程。

事前準備

  1. 如果您尚未將應用程式連結至 Firebase 專案,請前往 Firebase 控制台
  2. 使用 Swift Package Manager 安裝及管理 Firebase 依附元件。

    1. 在 Xcode 中保持開啟應用程式專案,然後依序選擇 [檔案] >新增套件
    2. 在系統提示時,新增 Firebase Apple 平台 SDK 存放區:
    3.   https://github.com/firebase/firebase-ios-sdk.git
    4. 選擇 Firebase 驗證資料庫。
    5. 在目標建構設定的「Other Linker Flags」部分中新增 -ObjC 標記。
    6. 完成後,Xcode 會自動開始解析並下載 複製到背景依附元件

安全疑慮

僅使用電話號碼進行驗證,安全性較低 其他可用的方法,因為擁有電話號碼 可以在使用者之間輕鬆轉移此外,當多位使用者共用裝置時 設定檔,任何可接收簡訊的使用者,皆可透過 裝置的電話號碼。

如果您在應用程式中使用電話號碼登入功能,則應提供這項功能 提供更安全的登入方式,並告知使用者這項安全措施 也不必為了使用電話號碼登入而做出取捨

為 Firebase 專案啟用電話號碼登入功能

您必須先啟用電話號碼登入功能,才能透過簡訊登入使用者 方法:

  1. Firebase 控制台開啟「驗證」專區。
  2. 在「Sign-in Method」(登入方式) 頁面中啟用電話號碼 登入方式。

Firebase 的電話號碼登入要求配額夠高,適用於大多數應用程式 不會受到影響但如果需要登入大量使用者 電話驗證,你可能需要升級定價方案。詳情請見 請參閱定價頁面。

啟用應用程式驗證功能

如要使用電話號碼驗證功能,Firebase 必須能夠驗證 電話號碼登入要求來自您的應用程式。與 GCP 互動的方式 Firebase 驗證功能會完成這項動作:

  • 靜音 APN 通知:您透過該使用者 首次在裝置上輸入電話號碼,Firebase 驗證功能會將 透過無訊息通知傳送到裝置。如果您的應用程式 已成功收到 Firebase 的通知,電話號碼 登入資訊就能繼續操作

    如果是 iOS 8.0 以上版本,則靜音通知不需要明確設定 就算使用者拒絕接收同意聲明,也不會受到影響 APN 應用程式中的通知。因此,應用程式不需要求使用者提出要求 權限以便在實作 Firebase 手機時接收推播通知 驗證數字

  • reCAPTCHA 驗證:如果應用程式收發郵件 無法傳送靜音推播通知 停用應用程式的背景重新整理功能,或是在 iOS 模擬工具,Firebase 驗證採用 reCAPTCHA 驗證機制來完成 手機登入流程系統經常可以完成 reCAPTCHA 驗證問題 使用者不必解決任何問題

正確設定靜音推播通知後,只有極小的 百分比的使用者將看到 reCAPTCHA 流程。儘管如此 確保用於登入電話號碼的登入功能 (無論是否為靜音推送都能正確運作) 有通知。

開始接收靜音通知

如何啟用與 Firebase 驗證搭配使用的 APN 通知:

  1. 在 Xcode 中, 為專案啟用推播通知
  2. 將 APN 驗證金鑰上傳至 Firebase。 如果您還沒有 APN 驗證金鑰,請務必前往 Apple Developer Member Center

    1. 在 Firebase 控制台的專案中,選取 齒輪圖示,選取 「Project Settings」,然後選取 「雲端通訊」分頁。

    2. 在「iOS 應用程式設定」下方的「APNs 驗證金鑰」中, 按一下「上傳」圖示 按鈕。

    3. 瀏覽至儲存金鑰的位置並選取金鑰,然後按一下 開啟。加入金鑰的金鑰 ID (位於 Apple Developer Member Center 時,按一下 按一下「上傳」

    如果您已有 APN 憑證,則可上傳憑證 。

設定 reCAPTCHA 驗證

如要啟用 Firebase SDK 進行 reCAPTCHA 驗證,請按照下列步驟操作:

  1. 在 Xcode 專案中新增自訂網址配置:
    1. 開啟專案設定:按兩下 左側樹狀檢視中從「目標」部分中選取應用程式,然後 選取「資訊」分頁標籤,然後展開「網址類型」部分。
    2. 點選「+」按鈕,然後將經過編碼的應用程式 ID 新增為網址 配置。如要找到經過編碼的應用程式 ID,請前往 一般 Firebase 控制台的「Settings」(設定) 頁面,在 iOS 區段中 應用程式。將其他欄位留白。

      設定完成後,設定看起來應該會與 後面 (但採用您應用程式的專屬值):

      Xcode 自訂網址配置設定介面的螢幕擷取畫面
  2. 選用:如要自訂應用程式的呈現方式 SFSafariViewController (向使用者顯示 reCAPTCHA) 時建立自訂 符合 AuthUIDelegate 通訊協定的類別,並傳遞至 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 方法會重新傳送:如果您多次呼叫此方法,例如 在檢視畫面的 onAppear 方法中,verifyPhoneNumber 方法不會 除非原始要求逾時,否則會發送第二封簡訊。

    呼叫 verifyPhoneNumber(_:uiDelegate:completion:) 時, Firebase 會將無訊息推播通知傳送至您的應用程式,或 向使用者提出 reCAPTCHA 驗證問題。應用程式收到 通知或使用者完成 reCAPTCHA 驗證問題,Firebase 傳送內含驗證碼的 SMS 訊息到 指定電話號碼,並將驗證 ID 傳送至 函式。您需要同時擁有驗證碼和驗證 以便登入使用者。

    您也能指定 Firebase 傳送的簡訊, 透過驗證上的 languageCode 屬性驗證語言 執行個體。

    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:) 驗證成功後,您可以提示使用者在輸入驗證碼時輸入驗證碼 透過簡訊接收。

透過驗證碼登入使用者

使用者提供應用程式傳送的驗證碼後 訊息,請建立 FIRPhoneAuthCredential 物件,並將該物件 至 signInWithCredential:completion:

  1. 向使用者取得驗證碼。
  2. 透過驗證建立 FIRPhoneAuthCredential 物件 和驗證 ID

    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().signIn(with: credential) { authResult, error in
        if let error = error {
          let authError = error as NSError
          if isMFAEnabled, authError.code == AuthErrorCode.secondFactorRequired.rawValue {
            // The user is a multi-factor user. Second factor challenge is required.
            let resolver = authError
              .userInfo[AuthErrorUserInfoMultiFactorResolverKey] as! MultiFactorResolver
            var displayNameString = ""
            for tmpFactorInfo in resolver.hints {
              displayNameString += tmpFactorInfo.displayName ?? ""
              displayNameString += " "
            }
            self.showTextInputPrompt(
              withMessage: "Select factor to sign in\n\(displayNameString)",
              completionBlock: { userPressedOK, displayName in
                var selectedHint: PhoneMultiFactorInfo?
                for tmpFactorInfo in resolver.hints {
                  if displayName == tmpFactorInfo.displayName {
                    selectedHint = tmpFactorInfo as? PhoneMultiFactorInfo
                  }
                }
                PhoneAuthProvider.provider()
                  .verifyPhoneNumber(with: selectedHint!, uiDelegate: nil,
                                     multiFactorSession: resolver
                                       .session) { verificationID, error in
                    if error != nil {
                      print(
                        "Multi factor start sign in failed. Error: \(error.debugDescription)"
                      )
                    } else {
                      self.showTextInputPrompt(
                        withMessage: "Verification code for \(selectedHint?.displayName ?? "")",
                        completionBlock: { userPressedOK, verificationCode in
                          let credential: PhoneAuthCredential? = PhoneAuthProvider.provider()
                            .credential(withVerificationID: verificationID!,
                                        verificationCode: verificationCode!)
                          let assertion: MultiFactorAssertion? = PhoneMultiFactorGenerator
                            .assertion(with: credential!)
                          resolver.resolveSignIn(with: assertion!) { authResult, error in
                            if error != nil {
                              print(
                                "Multi factor finanlize sign in failed. Error: \(error.debugDescription)"
                              )
                            } else {
                              self.navigationController?.popViewController(animated: true)
                            }
                          }
                        }
                      )
                    }
                  }
              }
            )
          } else {
            self.showMessagePrompt(error.localizedDescription)
            return
          }
          // ...
          return
        }
        // User is signed in
        // ...
    }

    Objective-C

    [[FIRAuth auth] signInWithCredential:credential
                              completion:^(FIRAuthDataResult * _Nullable authResult,
                                           NSError * _Nullable error) {
        if (isMFAEnabled && error && error.code == FIRAuthErrorCodeSecondFactorRequired) {
          FIRMultiFactorResolver *resolver = error.userInfo[FIRAuthErrorUserInfoMultiFactorResolverKey];
          NSMutableString *displayNameString = [NSMutableString string];
          for (FIRMultiFactorInfo *tmpFactorInfo in resolver.hints) {
            [displayNameString appendString:tmpFactorInfo.displayName];
            [displayNameString appendString:@" "];
          }
          [self showTextInputPromptWithMessage:[NSString stringWithFormat:@"Select factor to sign in\n%@", displayNameString]
                               completionBlock:^(BOOL userPressedOK, NSString *_Nullable displayName) {
           FIRPhoneMultiFactorInfo* selectedHint;
           for (FIRMultiFactorInfo *tmpFactorInfo in resolver.hints) {
             if ([displayName isEqualToString:tmpFactorInfo.displayName]) {
               selectedHint = (FIRPhoneMultiFactorInfo *)tmpFactorInfo;
             }
           }
           [FIRPhoneAuthProvider.provider
            verifyPhoneNumberWithMultiFactorInfo:selectedHint
            UIDelegate:nil
            multiFactorSession:resolver.session
            completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) {
              if (error) {
                [self showMessagePrompt:error.localizedDescription];
              } else {
                [self showTextInputPromptWithMessage:[NSString stringWithFormat:@"Verification code for %@", selectedHint.displayName]
                                     completionBlock:^(BOOL userPressedOK, NSString *_Nullable verificationCode) {
                 FIRPhoneAuthCredential *credential =
                     [[FIRPhoneAuthProvider provider] credentialWithVerificationID:verificationID
                                                                  verificationCode:verificationCode];
                 FIRMultiFactorAssertion *assertion = [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
                 [resolver resolveSignInWithAssertion:assertion completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) {
                   if (error) {
                     [self showMessagePrompt:error.localizedDescription];
                   } else {
                     NSLog(@"Multi factor finanlize sign in succeeded.");
                   }
                 }];
               }];
              }
            }];
         }];
        }
      else if (error) {
        // ...
        return;
      }
      // User successfully signed in. Get user data from the FIRUser object
      if (authResult == nil) { return; }
      FIRUser *user = authResult.user;
      // ...
    }];

使用虛構的電話號碼進行測試

您可以透過 Firebase 控制台設定用於開發的虛構電話號碼。使用虛構手機進行測試 數據具有以下優點:

  • 測試電話號碼驗證,而不耗用您的使用配額。
  • 在不傳送實際簡訊的情況下,測試電話號碼的驗證方式。
  • 以相同的電話號碼連續執行測試,而不會受到限制。這個 如果未經審查人員在應用程式商店審查流程,將審查遭拒的風險降到最低 使用相同的電話號碼進行測試
  • 在開發環境中輕鬆進行測試,不需額外的工作,例如 能夠在沒有 Google Play 服務的情況下,在 iOS 模擬器或 Android 模擬器中進行開發。
  • 編寫整合測試,但這些測試不會因一般套用的安全性檢查功能而遭到封鎖 實際電話號碼。

虛構電話號碼必須符合下列規定:

  1. 請務必使用虛構的電話號碼,但實際電話號碼尚未存在。 Firebase 驗證無法將真人使用者目前使用的電話號碼設為測試號碼。 其中一種做法是使用 555 組前置字元為美國的測試電話號碼,例如: 0933 000 000
  2. 電話號碼的格式必須正確, 限制。不過,他們仍須完成與實際使用者電話號碼相同的驗證。
  3. 您最多可以新增 10 組開發用電話號碼。
  4. 使用不易猜到和變更的測試電話號碼/驗證碼 經常更新

建立虛構的電話號碼和驗證碼

  1. Firebase 控制台,開啟 「Authentication」區段。
  2. 在「登入方式」分頁中啟用電話服務供應商 (如果您尚未啟用的話)。
  3. 開啟「測試用電話號碼」選單。
  4. 提供要測試的電話號碼,例如 +1 650-555-3434
  5. 提供該特定號碼的 6 位數驗證碼,例如「654321」
  6. 新增數字。如有需要,你可以刪除電話號碼,以及 請將滑鼠遊標懸停在對應資料列上,再按一下垃圾桶圖示。

手動測試

您可以直接開始在應用程式中使用虛構的電話號碼。您可以藉此 執行手動測試,而不會發生配額問題或節流問題。 您也可以使用沒有 Google Play 服務的 iOS 模擬器或 Android 模擬器直接進行測試 已安裝。

如果您提供虛構的電話號碼並傳送驗證碼,則簡訊不會是 已傳送。您需要提供先前設定的驗證碼,才能完成簽署。 。

登入完成後,系統會使用該電話號碼建立 Firebase 使用者。 使用者和實際電話號碼使用者俱有相同的行為和屬性, 即時資料庫/Cloud Firestore 和其他服務的使用方式相同。建立期間建立的 ID 符記 這項程序與實際電話號碼使用者擁有相同的簽章。

另一種方式是透過自訂角色設定測試角色 聲明,以便區分他們為冒用使用者,以便進一步限制 資源存取權

整合測試

除了手動測試外,Firebase 驗證也提供 API 協助您撰寫整合測試 用於手機驗證測試這些 API 會停用 reCAPTCHA 來停用應用程式驗證功能 以及 iOS 的靜音推播通知這樣就能在自動化環境中執行自動化測試 也能輕鬆實作也用來測試 驗證流程

在 iOS 上,appVerificationDisabledForTesting 設定必須設為 在呼叫 verifyPhoneNumber 前為 TRUE。這項作業不需要 在背景傳送任何 APN 權杖或傳送無訊息推播通知, 輸入「Google 查詢」。這麼做也會停用 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 驗證會使用方法掃描功能,自動取得應用程式的 APN 權杖,用於處理 Firebase 傳送給 並自動攔截從 驗證期間的 reCAPTCHA 驗證頁面。

如果您不想使用清除功能,只要新增標記即可停用 FirebaseAppDelegateProxyEnabled 加入應用程式的 Info.plist 檔案, 設為 NO請注意,將此標記設為 NO 也會停用其他 Firebase 產品的滑動功能,包括 Firebase 雲端通訊。

停用滑動功能時,必須明確傳送 APN 裝置權杖 推播通知,以及 Firebase 驗證的自訂配置重新導向網址。

如果您要建構 SwiftUI 應用程式,還必須明確傳送 APNs 裝置權杖 推播通知,以及 Firebase 驗證的自訂配置重新導向網址。

如要取得 APN 裝置權杖,請實作 application(_:didRegisterForRemoteNotificationsWithDeviceToken:) 方法,然後將裝置權杖傳遞至 AuthsetAPNSToken(_:type:) 方法。

Swift

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

  // 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:): 方法後,請呼叫 AuthcanHandleNotification(_:) 方法。

Swift

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

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; it should be handled separately.
}

如要處理自訂配置重新導向網址,請將 application(_:open:options:) 方法,然後在這些方法中,將網址傳遞至 AuthcanHandleURL(_:) 方法。

Swift

func application(_ application: UIApplication, open url: URL,
    options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool {
  if Auth.auth().canHandle(url) {
    return true
  }
  // URL not auth related; it should be handled separately.
}

Objective-C

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

如果您使用 SwiftUI 或 UISceneDelegate 來處理重新導向網址,請採用 scene(_:openURLContexts:) 方法,然後在這些方法中,將網址傳遞至 AuthcanHandleURL(_:) 方法。

Swift

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
  for urlContext in URLContexts {
      let url = urlContext.url
      Auth.auth().canHandle(url)
  }
  // URL not auth related; it should be handled separately.
}

Objective-C

- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts {
  for (UIOpenURLContext *urlContext in URLContexts) {
    [FIRAuth.auth canHandleURL:urlContext.url];
    // URL not auth related; it should be handled separately.
  }
}

後續步驟

使用者首次登入後,系統會建立新的使用者帳戶 也就是使用者的名稱和密碼 號碼或驗證提供者資訊,也就是使用者登入時使用的網址。這項新功能 帳戶儲存為 Firebase 專案的一部分,可用來識別 使用者登入專案中的所有應用程式

  • 在您的應用程式中,您可以透過 User 物件。請參閱管理使用者

  • 在您的 Firebase 即時資料庫和 Cloud Storage 中 查看安全性規則即可 透過 auth 變數取得已登入使用者的不重複使用者 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;
}

我們也建議您新增錯誤處理程式碼,適用於完整的驗證範圍 發生錯誤。請參閱處理錯誤