2022년 10월 18일에 오프라인과 온라인으로 진행될 Firebase Summit에 참여하세요. Firebase로 앱을 빠르게 개발하고 안심하고 앱을 출시하며 손쉽게 확장하는 방법을 알아보세요. 지금 등록하기

Android 앱에 다단계 인증 추가

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.

Identity Platform을 사용한 Firebase 인증으로 업그레이드한 경우 Android 앱에 SMS 다단계 인증을 추가할 수 있습니다.

다단계 인증은 앱의 보안을 강화합니다. 공격자는 종종 암호와 소셜 계정을 손상시키지만 문자 메시지를 가로채는 것은 더 어렵습니다.

시작하기 전에

  1. 다단계 인증을 지원하는 하나 이상의 공급자를 활성화합니다. 전화 인증, 익명 인증 및 Apple Game Center를 제외한 모든 제공업체는 MFA를 지원합니다.

  2. 앱이 사용자 이메일을 확인하고 있는지 확인하십시오. MFA는 이메일 확인이 필요합니다. 이렇게 하면 악의적인 행위자가 자신이 소유하지 않은 이메일로 서비스에 등록한 다음 두 번째 요소를 추가하여 실제 소유자를 잠그는 것을 방지할 수 있습니다.

  3. Firebase 콘솔에 앱의 SHA-1 해시를 등록합니다(변경 사항이 자동으로 Google Cloud Firebase로 전달됨).

    1. 클라이언트 인증 의 단계에 따라 앱의 SHA-1 해시를 가져옵니다.

    2. Firebase 콘솔 을 엽니다.

    3. 프로젝트 설정 으로 이동합니다.

    4. 내 앱 아래에서 Android 아이콘을 클릭합니다.

    5. 안내된 단계에 따라 SHA-1 해시를 추가합니다.

다단계 인증 활성화

  1. Firebase 콘솔의 인증 > 로그인 방법 페이지를 엽니다.

  2. 고급 섹션에서 SMS 다단계 인증 을 활성화합니다.

    앱을 테스트할 전화번호도 입력해야 합니다. 선택 사항이지만 개발 중 스로틀링을 방지하려면 테스트 전화 번호를 등록하는 것이 좋습니다.

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

축하합니다! 사용자에 대한 두 번째 인증 요소를 성공적으로 등록했습니다.

두 번째 요소로 사용자 로그인

2단계 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() 를 호출한 후 오류를 catch합니다.

  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. 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를 사용하여 프로그래밍 방식으로 다단계 사용자 를 관리합니다.