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