在 Android 應用程式中新增多重驗證機制

如果您已升級至 Firebase Authentication with Identity Platform,可以為 Android 應用程式新增簡訊多重驗證機制。

多重驗證可以提升應用程式的安全性。攻擊者通常會竊取密碼及盜用社群媒體帳戶,但攔截簡訊的難度較高。

事前準備

  1. 至少啟用一個支援多重驗證的供應商。除了電話驗證、匿名驗證和 Apple Game Center 以外,所有供應商都支援 MFA。

  2. 請確認您的應用程式會驗證使用者的電子郵件地址。多重驗證功能需要通過電子郵件驗證。這麼做可防止惡意人士使用不屬於自己的電子郵件地址註冊服務,然後透過新增第二個因素將真正的擁有者鎖在門外。

  3. 在 Firebase 主控台註冊應用程式的 SHA-1 雜湊 (變更會自動轉移至 Google Cloud Firebase)。

    1. 請按照「驗證用戶端」一文中的步驟,取得應用程式的 SHA-1 雜湊值。

    2. 開啟 Firebase 控制台

    3. 前往「專案設定」

    4. 在「你的應用程式」下方,按一下 Android 圖示。

    5. 按照指示步驟新增 SHA-1 雜湊。

啟用多重驗證

  1. 開啟 Firebase 主控台的「Authentication > Sign-in method頁面。

  2. 在「進階」部分中,啟用「簡訊多重驗證」

    您也應輸入要用來測試應用程式的電話號碼。雖然這不是必要步驟,我們仍強烈建議您註冊測試電話號碼,以免在開發期間發生節流情形。

  3. 如果您尚未授權應用程式的網域,請在 Firebase 控制台的「驗證 > 設定」頁面中,將該網域新增至許可清單。

選擇註冊模式

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

  • 在註冊過程中註冊使用者的第二重驗證。如果您的應用程式要求所有使用者都必須進行多重驗證,請使用這個方法。

  • 提供可略過的選項,讓使用者在註冊時註冊第二重驗證。如要鼓勵使用者採用多重驗證,但不強制要求使用者採用,則可能會偏好這種做法。

  • 提供從使用者帳戶或個人資料管理頁面新增第二因素的功能,而非在註冊畫面中提供。這樣一來,註冊程序的摩擦力就會降到最低,同時仍可讓重視安全性的使用者使用多重驗證功能。

  • 當使用者想要存取需要更高安全性要求的功能時,要求逐步新增第二個因素。

註冊次要驗證方式

如要為使用者註冊新的次要驗證方法,請按照下列步驟操作:

  1. 重新驗證使用者。

  2. 請使用者輸入電話號碼。

  3. 為使用者取得多重驗證工作階段:

    Kotlin

    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

    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

    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

    PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions) 

    Java

    PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions); 

    雖然這不是強制規定,但建議您事先告知使用者,他們將收到簡訊,並且會收取標準費率。

  7. 傳送簡訊碼後,請要求使用者驗證這組碼:

    Kotlin

    // 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

    val multiFactorAssertion   = PhoneMultiFactorGenerator.getAssertion(credential) 

    Java

    MultiFactorAssertion multiFactorAssertion   = PhoneMultiFactorGenerator.getAssertion(credential); 
  9. 完成註冊。您可以選擇為第二個因素指定顯示名稱。這對擁有多個第二因素的使用者很有幫助,因為電話號碼會在驗證流程中遮蓋 (例如 +1******1234)。

    Kotlin

    // 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

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) {         // ...       }       }); 

恭喜!您已為使用者成功註冊第二個驗證因素。

使用第二種驗證方法登入

如要使用簡訊雙重驗證登入使用者,請按照下列步驟操作:

  1. 請使用者以第一個因素登入,然後擷取 FirebaseAuthMultiFactorException 例外狀況。這個錯誤包含解析器,可用於取得使用者註冊的第二因素。它還包含一個基礎工作階段,證明使用者已成功透過第一因素驗證。

    舉例來說,如果使用者的第一個因素是電子郵件和密碼:

    Kotlin

    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

    // 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 if (multiFactorResolver.hints[selectedIndex].factorId     === TotpMultiFactorGenerator.FACTOR_ID) {     // User selected a TOTP second factor. } else {     // Unsupported second factor. } 

    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()                .equals( PhoneMultiFactorGenerator.FACTOR_ID ) ) { // User selected a phone second factor. MultiFactorInfo selectedHint =   multiFactorResolver.getHints().get(selectedIndex); } else if ( resolver               .getHints()               .get(selectedIndex)               .getFactorId()               .equals(TotpMultiFactorGenerator.FACTOR_ID ) ) {   // User selected a TOTP second factor. } else { // Unsupported second factor. } 
  3. 使用提示和多因素工作階段初始化 PhoneAuthOptions 物件。這些值包含在附加至 FirebaseAuthMultiFactorException 的解析器中。

    Kotlin

    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

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

    Java

    // Send SMS verification code PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions); 
  5. 傳送簡訊碼後,請要求使用者驗證這組碼:

    Kotlin

    // 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

    val multiFactorAssertion = PhoneMultiFactorGenerator.getAssertion(credential) 

    Java

    MultiFactorAssertion multiFactorAssertion     = PhoneMultiFactorGenerator.getAssertion(credential); 
  7. 請呼叫 resolver.resolveSignIn() 完成次要驗證。接著,您可以存取原始登入結果,其中包含標準提供者專屬資料和驗證憑證:

    Kotlin

    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

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.         }       }       }); 

恭喜!您已成功使用多重驗證登入使用者。

後續步驟