如果您已使用 Identity Platform 升級到 Firebase 身份驗證,則可以將 SMS 多重身份驗證添加到您的 Android 應用。
多因素身份驗證可提高應用程序的安全性。雖然攻擊者經常破壞密碼和社交帳戶,但攔截短信更加困難。
在你開始之前
啟用至少一個支持多重身份驗證的提供程序。除電話驗證、匿名驗證和 Apple Game Center 外,每個提供商都支持 MFA。
確保您的應用正在驗證用戶電子郵件。 MFA 需要電子郵件驗證。這可以防止惡意行為者使用他們不擁有的電子郵件註冊服務,然後通過添加第二個因素鎖定真正的所有者。
在 Firebase 控制台中註冊您應用的 SHA-1 哈希(您的更改將自動轉移到 Google Cloud Firebase)。
按照Authenticating your client中的步驟獲取應用的 SHA-1 哈希。
打開Firebase 控制台。
導航到項目設置。
在您的應用下,單擊 Android 圖標。
按照指導步驟添加您的 SHA-1 哈希。
啟用多重身份驗證
打開 Firebase 控制台的身份驗證 > 登錄方法頁面。
在高級部分,啟用SMS Multi-factor Authentication 。
您還應該輸入用於測試應用程序的電話號碼。雖然可選,但強烈建議註冊測試電話號碼以避免在開發過程中受到限制。
如果您尚未授權應用的域,請將其添加到 Firebase 控制台的“身份驗證”>“設置”頁面上的允許列表中。
選擇註冊模式
您可以選擇您的應用程序是否需要多重身份驗證,以及註冊用戶的方式和時間。一些常見的模式包括:
註冊用戶的第二個因素作為註冊的一部分。如果您的應用需要對所有用戶進行多重身份驗證,請使用此方法。
提供可跳過的選項以在註冊期間註冊第二個因素。想要鼓勵但不要求多因素身份驗證的應用程序可能更喜歡這種方法。
提供從用戶帳戶或配置文件管理頁面添加第二個因素的能力,而不是註冊屏幕。這最大限度地減少了註冊過程中的摩擦,同時仍然為安全敏感的用戶提供多因素身份驗證。
當用戶想要訪問具有更高安全要求的功能時,需要逐步添加第二個因素。
註冊第二個因素
為用戶註冊一個新的次要因素:
重新驗證用戶。
要求用戶輸入他們的電話號碼。
為用戶獲取多因素會話:
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(); } } });
構建一個
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; // ... } };
使用用戶的電話號碼、多因素會話和您的回調初始化一個
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)
的調用。向用戶手機發送驗證信息:
Kotlin+KTX
PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions)
Java
PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions);
雖然不是必需的,但最好提前通知用戶他們將收到 SMS 消息,並且適用標準費率。
發送 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);
使用
PhoneAuthCredential
初始化MultiFactorAssertion
對象:Kotlin+KTX
val multiFactorAssertion = PhoneMultiFactorGenerator.getAssertion(credential)
Java
MultiFactorAssertion multiFactorAssertion = PhoneMultiFactorGenerator.getAssertion(credential);
完成報名。或者,您可以指定第二個因素的顯示名稱。這對於具有多個第二因素的用戶很有用,因為電話號碼在身份驗證流程中被屏蔽(例如,+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 驗證登錄用戶:
使用他們的第一個因素登錄用戶,然後捕獲
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()
後捕獲錯誤。如果用戶註冊了多個次要因素,請詢問他們使用哪一個:
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. }
使用提示和多因素會話初始化
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();
向用戶手機發送驗證信息:
Kotlin+KTX
// Send SMS verification code PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions)
Java
// Send SMS verification code PhoneAuthProvider.verifyPhoneNumber(phoneAuthOptions);
發送 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);
使用
PhoneAuthCredential
初始化MultiFactorAssertion
對象:Kotlin+KTX
val multiFactorAssertion = PhoneMultiFactorGenerator.getAssertion(credential)
Java
MultiFactorAssertion multiFactorAssertion = PhoneMultiFactorGenerator.getAssertion(credential);
調用
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.
}
}
});
恭喜!您使用多重身份驗證成功登錄了用戶。
下一步是什麼
- 使用 Admin SDK 以編程方式管理多因素用戶。