Firebase is back at Google I/O on May 10! Register now

將多重身份驗證添加到您的 Android 應用程序

透過集合功能整理內容 你可以依據偏好儲存及分類內容。

如果您已使用 Identity Platform 升級到 Firebase 身份驗證,則可以將 SMS 多重身份驗證添加到您的 Android 應用。

多因素身份驗證可提高應用程序的安全性。雖然攻擊者經常破壞密碼和社交帳戶,但攔截短信更加困難。

在你開始之前

  1. 啟用至少一個支持多重身份驗證的提供程序。電話驗證、匿名驗證和 Apple Game Center 外,每個提供商都支持 MFA。

  2. 確保您的應用正在驗證用戶電子郵件。 MFA 需要電子郵件驗證。這可以防止惡意行為者使用他們不擁有的電子郵件註冊服務,然後通過添加第二個因素鎖定真正的所有者。

  3. 在 Firebase 控制台中註冊您應用的 SHA-1 哈希(您的更改將自動轉移到 Google Cloud Firebase)。

    1. 按照Authenticating your client中的步驟獲取應用的 SHA-1 哈希。

    2. 打開Firebase 控制台

    3. 導航到項目設置

    4. 您的應用下,單擊 Android 圖標。

    5. 按照指導步驟添加您的 SHA-1 哈希。

啟用多重身份驗證

  1. 打開 Firebase 控制台的身份驗證 > 登錄方法頁面。

  2. 高級部分,啟用SMS Multi-factor Authentication

    您還應該輸入用於測試應用程序的電話號碼。雖然可選,但強烈建議註冊測試電話號碼以避免在開發過程中受到限制。

  3. 如果您尚未授權應用的域,請將其添加到 Firebase 控制台的“身份驗證”>“設置”頁面上的允許列表中。

選擇註冊模式

您可以選擇您的應用程序是否需要多重身份驗證,以及註冊用戶的方式和時間。一些常見的模式包括:

  • 註冊用戶的第二個因素作為註冊的一部分。如果您的應用需要對所有用戶進行多重身份驗證,請使用此方法。

  • 提供可跳過的選項以在註冊期間註冊第二個因素。想要鼓勵但不要求多因素身份驗證的應用程序可能更喜歡這種方法。

  • 提供從用戶帳戶或配置文件管理頁面添加第二個因素的能力,而不是註冊屏幕。這最大限度地減少了註冊過程中的摩擦,同時仍然為安全敏感的用戶提供多因素身份驗證。

  • 當用戶想要訪問具有更高安全要求的功能時,需要逐步添加第二個因素。

註冊第二個因素

為用戶註冊一個新的次要因素:

  1. 重新驗證用戶。

  2. 要求用戶輸入他們的電話號碼。

  3. 為用戶獲取多因素會話:

    Kotlin+KTX

    user.multiFactor.session.addOnCompleteListener { task ->
        if (task.isSuccessful) {
            val multiFactorSession: MultiFactorSession? = task.result
        }
    }
    

    Java

    user.getMultiFactor().getSession()
      .addOnCompleteListener(
          new OnCompleteListener<MultiFactorSession>() {
          @Override
          public void onComplete(@NonNull Task<MultiFactorSession> task) {
            if (task.isSuccessful()) {
              MultiFactorSession multiFactorSession = task.getResult();
            }
          }
          });
    
  4. 構建一個OnVerificationStateChangedCallbacks對象來處理驗證過程中的不同事件:

    Kotlin+KTX

    val callbacks = object : OnVerificationStateChangedCallbacks() {
        override fun onVerificationCompleted(credential: PhoneAuthCredential) {
            // 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. You can disable this feature by calling
            //    PhoneAuthOptions.builder#requireSmsValidation(true) when building
            //    the options to pass to PhoneAuthProvider#verifyPhoneNumber().
            // 2) Auto-retrieval. On some devices, Google Play services can
            //    automatically detect the incoming verification SMS and perform
            //    verification without user action.
            this@MainActivity.credential = credential
        }
    
        override fun onVerificationFailed(e: FirebaseException) {
            // This callback is invoked in response to invalid requests for
            // verification, like an incorrect phone number.
            if (e is FirebaseAuthInvalidCredentialsException) {
                // Invalid request
                // ...
            } else if (e is FirebaseTooManyRequestsException) {
                // The SMS quota for the project has been exceeded
                // ...
            }
            // Show a message and update the UI
            // ...
        }
    
        override fun onCodeSent(
            verificationId: String, forceResendingToken: ForceResendingToken
        ) {
            // 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.
            // Save the verification ID and resending token for later use.
            this@MainActivity.verificationId = verificationId
            this@MainActivity.forceResendingToken = forceResendingToken
            // ...
        }
    }
    

    Java

    OnVerificationStateChangedCallbacks callbacks =
    new 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. You can disable this feature by calling
        //    PhoneAuthOptions.builder#requireSmsValidation(true) when building
        //    the options to pass to PhoneAuthProvider#verifyPhoneNumber().
        // 2) Auto-retrieval. On some devices, Google Play services can
        //    automatically detect the incoming verification SMS and perform
        //    verification without user action.
        this.credential = credential;
      }
      @Override
      public void onVerificationFailed(FirebaseException e) {
        // This callback is invoked in response to invalid requests for
        // verification, like an incorrect phone number.
        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.
        // Save the verification ID and resending token for later use.
        this.verificationId = verificationId;
        this.forceResendingToken = token;
        // ...
      }
    };
    
  5. 使用用戶的電話號碼、多因素會話和您的回調初始化一個PhoneInfoOptions對象:

    Kotlin+KTX

    val phoneAuthOptions = PhoneAuthOptions.newBuilder()
        .setPhoneNumber(phoneNumber)
        .setTimeout(30L, TimeUnit.SECONDS)
        .setMultiFactorSession(MultiFactorSession)
        .setCallbacks(callbacks)
        .build()
    

    Java

    PhoneAuthOptions phoneAuthOptions =
      PhoneAuthOptions.newBuilder()
          .setPhoneNumber(phoneNumber)
          .setTimeout(30L, TimeUnit.SECONDS)
          .setMultiFactorSession(multiFactorSession)
          .setCallbacks(callbacks)
          .build();
    

    默認情況下,啟用即時驗證。要禁用它,請添加對requireSmsValidation(true)的調用。

  6. 向用戶手機發送驗證信息:

    Kotlin+KTX

    PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions)
    

    Java

    PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions);
    

    雖然不是必需的,但最好提前通知用戶他們將收到 SMS 消息,並且適用標準費率。

  7. 發送 SMS 代碼後,要求用戶驗證代碼:

    Kotlin+KTX

    // Ask user for the verification code.
    val credential = PhoneAuthProvider.getCredential(verificationId, verificationCode)
    

    Java

    // Ask user for the verification code.
    PhoneAuthCredential credential
      = PhoneAuthProvider.getCredential(verificationId, verificationCode);
    
  8. 使用PhoneAuthCredential初始化MultiFactorAssertion對象:

    Kotlin+KTX

    val multiFactorAssertion
      = PhoneMultiFactorGenerator.getAssertion(credential)
    

    Java

    MultiFactorAssertion multiFactorAssertion
      = PhoneMultiFactorGenerator.getAssertion(credential);
    
  9. 完成報名。或者,您可以指定第二個因素的顯示名稱。這對於具有多個第二因素的用戶很有用,因為電話號碼在身份驗證流程中被屏蔽(例如,+1******1234)。

    Kotlin+KTX

    // Complete enrollment. This will update the underlying tokens
    // and trigger ID token change listener.
    FirebaseAuth.getInstance()
        .currentUser
        ?.multiFactor
        ?.enroll(multiFactorAssertion, "My personal phone number")
        ?.addOnCompleteListener {
            // ...
        }
    

    Java

    // Complete enrollment. This will update the underlying tokens
    // and trigger ID token change listener.
    FirebaseAuth.getInstance()
      .getCurrentUser()
      .getMultiFactor()
      .enroll(multiFactorAssertion, "My personal phone number")
      .addOnCompleteListener(
          new OnCompleteListener<Void>() {
          @Override
          public void onComplete(@NonNull Task<Void> task) {
            // ...
          }
          });
    

下面的代碼顯示了註冊第二個因素的完整示例:

Kotlin+KTX

val multiFactorAssertion = PhoneMultiFactorGenerator.getAssertion(credential)
user.multiFactor.session
    .addOnCompleteListener { task ->
        if (task.isSuccessful) {
            val multiFactorSession = task.result
            val phoneAuthOptions = PhoneAuthOptions.newBuilder()
                .setPhoneNumber(phoneNumber)
                .setTimeout(30L, TimeUnit.SECONDS)
                .setMultiFactorSession(multiFactorSession)
                .setCallbacks(callbacks)
                .build()
            // Send SMS verification code.
            PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions)
        }
    }

// Ask user for the verification code.
val credential = PhoneAuthProvider.getCredential(verificationId, verificationCode)

val multiFactorAssertion = PhoneMultiFactorGenerator.getAssertion(credential)

// Complete enrollment.
FirebaseAuth.getInstance()
    .currentUser
    ?.multiFactor
    ?.enroll(multiFactorAssertion, "My personal phone number")
    ?.addOnCompleteListener {
        // ...
    }

Java

MultiFactorAssertion multiFactorAssertion = PhoneMultiFactorGenerator.getAssertion(credential);
user.getMultiFactor().getSession()
  .addOnCompleteListener(
      new OnCompleteListener<MultiFactorSession>() {
      @Override
      public void onComplete(@NonNull Task<MultiFactorSession> task) {
        if (task.isSuccessful()) {
          MultiFactorSession multiFactorSession = task.getResult();
          PhoneAuthOptions phoneAuthOptions =
            PhoneAuthOptions.newBuilder()
                .setPhoneNumber(phoneNumber)
                .setTimeout(30L, TimeUnit.SECONDS)
                .setMultiFactorSession(multiFactorSession)
                .setCallbacks(callbacks)
                .build();
          // Send SMS verification code.
          PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions);
        }
      }
      });

// Ask user for the verification code.
PhoneAuthCredential credential =
  PhoneAuthProvider.getCredential(verificationId, verificationCode);

MultiFactorAssertion multiFactorAssertion = PhoneMultiFactorGenerator.getAssertion(credential);
// Complete enrollment.
FirebaseAuth.getInstance()
  .getCurrentUser()
  .getMultiFactor()
  .enroll(multiFactorAssertion, "My personal phone number")
  .addOnCompleteListener(
      new OnCompleteListener<Void>() {
      @Override
      public void onComplete(@NonNull Task<Void> task) {
        // ...
      }
      });

恭喜!您成功為用戶註冊了第二個身份驗證因素。

使用第二個因素讓用戶登錄

要使用雙因素 SMS 驗證登錄用戶:

  1. 使用他們的第一個因素登錄用戶,然後捕獲FirebaseAuthMultiFactorException異常。此錯誤包含一個解析程序,您可以使用它來獲取用戶註冊的第二個因素。它還包含一個基礎會話,證明用戶成功通過他們的第一個因素進行身份驗證。

    例如,如果用戶的第一個因素是電子郵件和密碼:

    Kotlin+KTX

    FirebaseAuth.getInstance()
        .signInWithEmailAndPassword(email, password)
        .addOnCompleteListener(
            OnCompleteListener { task ->
                if (task.isSuccessful) {
                    // User is not enrolled with a second factor and is successfully
                    // signed in.
                    // ...
                    return@OnCompleteListener
                }
                if (task.exception is FirebaseAuthMultiFactorException) {
                    // The user is a multi-factor user. Second factor challenge is
                    // required.
                    val multiFactorResolver =
                        (task.exception as FirebaseAuthMultiFactorException).resolver
                    // ...
                } else {
                    // Handle other errors, such as wrong password.
                }
            })
    

    Java

    FirebaseAuth.getInstance()
      .signInWithEmailAndPassword(email, password)
      .addOnCompleteListener(
          new OnCompleteListener<AuthResult>() {
          @Override
          public void onComplete(@NonNull Task<AuthResult> task) {
            if (task.isSuccessful()) {
              // User is not enrolled with a second factor and is successfully
              // signed in.
              // ...
              return;
            }
            if (task.getException() instanceof FirebaseAuthMultiFactorException) {
              // The user is a multi-factor user. Second factor challenge is
              // required.
              MultiFactorResolver multiFactorResolver = task.getException().getResolver();
              // ...
            } else {
              // Handle other errors such as wrong password.
            }
          }
          });
    

    如果用戶的第一個因素是聯合提供程序,例如 OAuth,則在調用startActivityForSignInWithProvider()後捕獲錯誤。

  2. 如果用戶註冊了多個次要因素,請詢問他們使用哪一個:

    Kotlin+KTX

    // Ask user which second factor to use.
    // You can get the list of enrolled second factors using
    //   multiFactorResolver.hints
    
    // Check the selected factor:
    if (multiFactorResolver.hints[selectedIndex].factorId
        === PhoneMultiFactorGenerator.FACTOR_ID
    ) {
        // User selected a phone second factor.
        val selectedHint = 
            multiFactorResolver.hints[selectedIndex] as PhoneMultiFactorInfo
    } else {
        // Unsupported second factor.
        // Note that only phone second factors are currently supported.
    }
    

    Java

    // Ask user which second factor to use.
    // You can get the masked phone number using
    // resolver.getHints().get(selectedIndex).getPhoneNumber()
    // You can get the display name using
    // resolver.getHints().get(selectedIndex).getDisplayName()
    if (resolver.getHints().get(selectedIndex).getFactorId()
      == PhoneMultiFactorGenerator.FACTOR_ID) {
    // User selected a phone second factor.
    MultiFactorInfo selectedHint =
      multiFactorResolver.getHints().get(selectedIndex);
    } else {
    // Unsupported second factor.
    // Note that only phone second factors are currently supported.
    }
    
  3. 使用提示和多因素會話初始化PhoneAuthOptions對象。這些值包含在附加到FirebaseAuthMultiFactorException的解析器中。

    Kotlin+KTX

    val phoneAuthOptions = PhoneAuthOptions.newBuilder()
        .setMultiFactorHint(selectedHint)
        .setTimeout(30L, TimeUnit.SECONDS)
        .setMultiFactorSession(multiFactorResolver.session)
        .setCallbacks(callbacks) // Optionally disable instant verification.
        // .requireSmsValidation(true)
        .build()
    

    Java

    PhoneAuthOptions phoneAuthOptions =
      PhoneAuthOptions.newBuilder()
          .setMultiFactorHint(selectedHint)
          .setTimeout(30L, TimeUnit.SECONDS)
          .setMultiFactorSession(multiFactorResolver.getSession())
          .setCallbacks(callbacks)
          // Optionally disable instant verification.
          // .requireSmsValidation(true)
          .build();
    
  4. 向用戶手機發送驗證信息:

    Kotlin+KTX

    // Send SMS verification code
    PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions)
    

    Java

    // Send SMS verification code
    PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions);
    
  5. 發送 SMS 代碼後,要求用戶驗證代碼:

    Kotlin+KTX

    // Ask user for the verification code. Then, pass it to getCredential:
    val credential =
        PhoneAuthProvider.getCredential(verificationId, verificationCode)
    

    Java

    // Ask user for the verification code. Then, pass it to getCredential:
    PhoneAuthCredential credential
        = PhoneAuthProvider.getCredential(verificationId, verificationCode);
    
  6. 使用PhoneAuthCredential初始化MultiFactorAssertion對象:

    Kotlin+KTX

    val multiFactorAssertion = PhoneMultiFactorGenerator.getAssertion(credential)
    

    Java

    MultiFactorAssertion multiFactorAssertion
        = PhoneMultiFactorGenerator.getAssertion(credential);
    
  7. 調用resolver.resolveSignIn()完成二次認證。然後,您可以訪問原始登錄結果,其中包括標準的提供商特定數據和身份驗證憑據:

    Kotlin+KTX

    multiFactorResolver
        .resolveSignIn(multiFactorAssertion)
        .addOnCompleteListener { task ->
            if (task.isSuccessful) {
                val authResult = task.result
                // AuthResult will also contain the user, additionalUserInfo,
                // and an optional credential (null for email/password)
                // associated with the first factor sign-in.
    
                // For example, if the user signed in with Google as a first
                // factor, authResult.getAdditionalUserInfo() will contain data
                // related to Google provider that the user signed in with;
                // authResult.getCredential() will contain the Google OAuth
                //   credential;
                // authResult.getCredential().getAccessToken() will contain the
                //   Google OAuth access token;
                // authResult.getCredential().getIdToken() contains the Google
                //   OAuth ID token.
            }
        }
    

    Java

    multiFactorResolver
      .resolveSignIn(multiFactorAssertion)
      .addOnCompleteListener(
          new OnCompleteListener<AuthResult>() {
          @Override
          public void onComplete(@NonNull Task<AuthResult> task) {
            if (task.isSuccessful()) {
              AuthResult authResult = task.getResult();
              // AuthResult will also contain the user, additionalUserInfo,
              // and an optional credential (null for email/password)
              // associated with the first factor sign-in.
              // For example, if the user signed in with Google as a first
              // factor, authResult.getAdditionalUserInfo() will contain data
              // related to Google provider that the user signed in with.
              // authResult.getCredential() will contain the Google OAuth
              // credential.
              // authResult.getCredential().getAccessToken() will contain the
              // Google OAuth access token.
              // authResult.getCredential().getIdToken() contains the Google
              // OAuth ID token.
            }
          }
          });
    

下面的代碼顯示了一個多因素用戶登錄的完整示例:

Kotlin+KTX

FirebaseAuth.getInstance()
    .signInWithEmailAndPassword(email, password)
    .addOnCompleteListener { task ->
        if (task.isSuccessful) {
            // User is not enrolled with a second factor and is successfully
            // signed in.
            // ...
            return@addOnCompleteListener
        }
        if (task.exception is FirebaseAuthMultiFactorException) {
            val multiFactorResolver =
                (task.exception as FirebaseAuthMultiFactorException).resolver

            // Ask user which second factor to use. Then, get
            // the selected hint:
            val selectedHint =
                multiFactorResolver.hints[selectedIndex] as PhoneMultiFactorInfo

            // Send the SMS verification code.
            PhoneAuthProvider.verifyPhoneNumber(
                PhoneAuthOptions.newBuilder()
                    .setActivity(this)
                    .setMultiFactorSession(multiFactorResolver.session)
                    .setMultiFactorHint(selectedHint)
                    .setCallbacks(generateCallbacks())
                    .setTimeout(30L, TimeUnit.SECONDS)
                    .build()
            )

            // Ask user for the SMS verification code, then use it to get
            // a PhoneAuthCredential:
            val credential =
                PhoneAuthProvider.getCredential(verificationId, verificationCode)

            // Initialize a MultiFactorAssertion object with the
            // PhoneAuthCredential.
            val multiFactorAssertion: MultiFactorAssertion =
                PhoneMultiFactorGenerator.getAssertion(credential)

            // Complete sign-in.
            multiFactorResolver
                .resolveSignIn(multiFactorAssertion)
                .addOnCompleteListener { task ->
                    if (task.isSuccessful) {
                        // User successfully signed in with the
                        // second factor phone number.
                    }
                    // ...
                }
        } else {
            // Handle other errors such as wrong password.
        }
    }

Java

FirebaseAuth.getInstance()
  .signInWithEmailAndPassword(email, password)
  .addOnCompleteListener(
      new OnCompleteListener<AuthResult>() {
      @Override
      public void onComplete(@NonNull Task<AuthResult> task) {
        if (task.isSuccessful()) {
          // User is not enrolled with a second factor and is successfully
          // signed in.
          // ...
          return;
        }
        if (task.getException() instanceof FirebaseAuthMultiFactorException) {
          FirebaseAuthMultiFactorException e =
            (FirebaseAuthMultiFactorException) task.getException();

          MultiFactorResolver multiFactorResolver = e.getResolver();

          // Ask user which second factor to use.
          MultiFactorInfo selectedHint =
            multiFactorResolver.getHints().get(selectedIndex);

          // Send the SMS verification code.
          PhoneAuthProvider.verifyPhoneNumber(
            PhoneAuthOptions.newBuilder()
                .setActivity(this)
                .setMultiFactorSession(multiFactorResolver.getSession())
                .setMultiFactorHint(selectedHint)
                .setCallbacks(generateCallbacks())
                .setTimeout(30L, TimeUnit.SECONDS)
                .build());

          // Ask user for the SMS verification code.
          PhoneAuthCredential credential =
            PhoneAuthProvider.getCredential(verificationId, verificationCode);

          // Initialize a MultiFactorAssertion object with the
          // PhoneAuthCredential.
          MultiFactorAssertion multiFactorAssertion =
            PhoneMultiFactorGenerator.getAssertion(credential);

          // Complete sign-in.
          multiFactorResolver
            .resolveSignIn(multiFactorAssertion)
            .addOnCompleteListener(
                new OnCompleteListener<AuthResult>() {
                  @Override
                  public void onComplete(@NonNull Task<AuthResult> task) {
                  if (task.isSuccessful()) {
                    // User successfully signed in with the
                    // second factor phone number.
                  }
                  // ...
                  }
                });
        } else {
          // Handle other errors such as wrong password.
        }
      }
      });

恭喜!您使用多重身份驗證成功登錄了用戶。

下一步是什麼