Identity Platform을 사용한 Firebase 인증으로 업그레이드한 경우 Android 앱에 SMS 다단계 인증을 추가할 수 있습니다.
다단계 인증은 앱의 보안을 강화합니다. 공격자는 종종 암호와 소셜 계정을 손상시키지만 문자 메시지를 가로채는 것은 더 어렵습니다.
시작하기 전에
다단계 인증을 지원하는 하나 이상의 공급자를 활성화합니다. 전화 인증, 익명 인증 및 Apple Game Center를 제외한 모든 제공업체는 MFA를 지원합니다.
앱이 사용자 이메일을 확인하고 있는지 확인하십시오. MFA는 이메일 확인이 필요합니다. 이렇게 하면 악의적인 행위자가 자신이 소유하지 않은 이메일로 서비스에 등록한 다음 두 번째 요소를 추가하여 실제 소유자를 잠그는 것을 방지할 수 있습니다.
Firebase 콘솔에 앱의 SHA-1 해시를 등록합니다(변경 사항이 자동으로 Google Cloud Firebase로 전달됨).
클라이언트 인증 의 단계에 따라 앱의 SHA-1 해시를 가져옵니다.
Firebase 콘솔 을 엽니다.
프로젝트 설정 으로 이동합니다.
내 앱 아래에서 Android 아이콘을 클릭합니다.
안내된 단계에 따라 SHA-1 해시를 추가합니다.
다단계 인증 활성화
Firebase 콘솔의 인증 > 로그인 방법 페이지를 엽니다.
고급 섹션에서 SMS 다단계 인증 을 활성화합니다.
앱을 테스트할 전화번호도 입력해야 합니다. 선택 사항이지만 개발 중 스로틀링을 방지하려면 테스트 전화 번호를 등록하는 것이 좋습니다.
앱의 도메인을 아직 승인하지 않은 경우 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) {
// ...
}
});
축하합니다! 사용자에 대한 두 번째 인증 요소를 성공적으로 등록했습니다.
두 번째 요소로 사용자 로그인
2단계 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()
를 호출한 후 오류를 catch합니다.사용자가 여러 개의 보조 요소를 등록한 경우 사용할 보조 요소를 묻습니다.
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);
2차 인증을 완료하려면
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를 사용하여 프로그래밍 방식으로 다단계 사용자 를 관리합니다.