在 Android 上使用 Apple 進行身份驗證

通過使用 Firebase SDK 執行端到端 OAuth 2.0 登錄流程,您可以讓您的用戶使用他們的 Apple ID 通過 Firebase 進行身份驗證。

在你開始之前

要使用 Apple 登錄用戶,首先在 Apple 的開發者網站上配置使用 Apple 登錄,然後啟用 Apple 作為您的 Firebase 項目的登錄提供商。

加入蘋果開發者計劃

Sign In with Apple 只能由Apple Developer Program的成員配置。

配置使用 Apple 登錄

Apple Developer網站上,執行以下操作:

  1. 將您的網站關聯到您的應用程序,如為 Web 配置登錄 Apple的第一部分中所述。出現提示時,將以下 URL 註冊為返回 URL:

    https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler

    您可以在Firebase 控制台設置頁面上獲取您的 Firebase 項目 ID。

    完成後,記下您的新服務 ID,您將在下一節中用到它。

  2. 創建一個 Sign In with Apple private key 。在下一節中,您將需要新的私鑰和密鑰 ID。
  3. 如果您使用 Firebase 身份驗證的任何向用戶發送電子郵件的功能,包括電子郵件鏈接登錄、電子郵件地址驗證、帳戶更改撤銷等,請配置 Apple 私人電子郵件中繼服務並註冊noreply@ YOUR_FIREBASE_PROJECT_ID .firebaseapp.com (或您自定義的電子郵件模板域),以便 Apple 可以將 Firebase 身份驗證發送的電子郵件轉發到匿名的 Apple 電子郵件地址。

啟用 Apple 作為登錄提供商

  1. 將 Firebase 添加到您的 Android 項目。當您在 Firebase 控制台中設置您的應用時,請務必註冊您應用的 SHA-1 簽名。
  2. Firebase 控制台中,打開Auth部分。在登錄方法選項卡上,啟用Apple提供商。指定您在上一節中創建的服務 ID。此外,在OAuth 代碼流配置部分,指定您的 Apple Team ID 以及您在上一節中創建的私鑰和密鑰 ID。

符合 Apple 匿名數據要求

Sign In with Apple 為用戶提供了在登錄時匿名化他們的數據的選項,包括他們的電子郵件地址。選擇此選項的用戶擁有域為privaterelay.appleid.com的電子郵件地址。當您在您的應用程序中使用 Sign In with Apple 時,您必須遵守 Apple 關於這些匿名 Apple ID 的任何適用的開發者政策或條款。

這包括在將任何直接識別個人信息與匿名 Apple ID 相關聯之前獲得任何必要的用戶同意。使用 Firebase 身份驗證時,這可能包括以下操作:

  • 將電子郵件地址鏈接到匿名的 Apple ID,反之亦然。
  • 將電話號碼鏈接到匿名的 Apple ID,反之亦然
  • 將非匿名社交憑證(Facebook、Google 等)鏈接到匿名 Apple ID,反之亦然。

上面的列表並不詳盡。請參閱您開發者帳戶的“會員資格”部分中的 Apple 開發者計劃許可協議,以確保您的應用程序符合 Apple 的要求。

使用 Firebase SDK 處理登錄流程

在 Android 上,使用 Apple 帳戶通過 Firebase 對用戶進行身份驗證的最簡單方法是使用 Firebase Android SDK 處理整個登錄流程。

要使用 Firebase Android SDK 處理登錄流程,請執行以下步驟:

  1. 使用其 Builder 和提供者 ID apple.com構造OAuthProvider的實例:

    Kotlin+KTX

    val provider = OAuthProvider.newBuilder("apple.com")
    

    Java

    OAuthProvider.Builder provider = OAuthProvider.newBuilder("apple.com");
    
  2. 可選:指定超出您希望從身份驗證提供程序請求的默認範圍的其他 OAuth 2.0 範圍。

    Kotlin+KTX

    provider.setScopes(arrayOf("email", "name"))
    

    Java

    List<String> scopes =
        new ArrayList<String>() {
          {
            add("email");
            add("name");
          }
        };
    provider.setScopes(scopes);
    

    默認情況下,啟用每個電子郵件地址一個帳戶時,Firebase 會請求電子郵件和名稱範圍。如果您將此設置更改為Multiple accounts per email address ,除非您指定,否則 Firebase 不會向 Apple 請求任何範圍。

  3. 可選:如果要以英語以外的語言顯示 Apple 的登錄屏幕,請設置locale參數。有關支持的語言環境,請參閱使用 Apple 文檔登錄

    Kotlin+KTX

    // Localize the Apple authentication screen in French.
    provider.addCustomParameter("locale", "fr")
    

    Java

    // Localize the Apple authentication screen in French.
    provider.addCustomParameter("locale", "fr");
    
  4. 使用 OAuth 提供程序對象通過 Firebase 進行身份驗證。請注意,與其他FirebaseAuth操作不同,這將通過打開自定義 Chrome 選項卡來控制您的 UI。因此,不要在您附加的OnSuccessListenerOnFailureListener中引用您的 Activity,因為它們會在操作啟動 UI 時立即分離。

    您應該首先檢查您是否已經收到回复。使用此方法登錄會將您的 Activity 置於後台,這意味著它可以在登錄流程期間由系統回收。如果發生這種情況,為了確保您不會讓用戶重試,您應該檢查結果是否已經存在。

    要檢查是否有待定結果,請調用getPendingAuthResult()

    Kotlin+KTX

    val pending = auth.pendingAuthResult
    if (pending != null) {
        pending.addOnSuccessListener { authResult ->
            Log.d(TAG, "checkPending:onSuccess:$authResult")
            // Get the user profile with authResult.getUser() and
            // authResult.getAdditionalUserInfo(), and the ID
            // token from Apple with authResult.getCredential().
        }.addOnFailureListener { e ->
            Log.w(TAG, "checkPending:onFailure", e)
        }
    } else {
        Log.d(TAG, "pending: null")
    }
    

    Java

    mAuth = FirebaseAuth.getInstance();
    Task<AuthResult> pending = mAuth.getPendingAuthResult();
    if (pending != null) {
        pending.addOnSuccessListener(new OnSuccessListener<AuthResult>() {
            @Override
            public void onSuccess(AuthResult authResult) {
                Log.d(TAG, "checkPending:onSuccess:" + authResult);
                // Get the user profile with authResult.getUser() and
                // authResult.getAdditionalUserInfo(), and the ID
                // token from Apple with authResult.getCredential().
            }
        }).addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                Log.w(TAG, "checkPending:onFailure", e);
            }
        });
    } else {
        Log.d(TAG, "pending: null");
    }
    

    如果沒有未決結果,請通過調用startActivityForSignInWithProvider()啟動登錄流程:

    Kotlin+KTX

    auth.startActivityForSignInWithProvider(this, provider.build())
            .addOnSuccessListener { authResult ->
                // Sign-in successful!
                Log.d(TAG, "activitySignIn:onSuccess:${authResult.user}")
                val user = authResult.user
                // ...
            }
            .addOnFailureListener { e ->
                Log.w(TAG, "activitySignIn:onFailure", e)
            }
    

    Java

    mAuth.startActivityForSignInWithProvider(this, provider.build())
            .addOnSuccessListener(
                    new OnSuccessListener<AuthResult>() {
                        @Override
                        public void onSuccess(AuthResult authResult) {
                            // Sign-in successful!
                            Log.d(TAG, "activitySignIn:onSuccess:" + authResult.getUser());
                            FirebaseUser user = authResult.getUser();
                            // ...
                        }
                    })
            .addOnFailureListener(
                    new OnFailureListener() {
                        @Override
                        public void onFailure(@NonNull Exception e) {
                            Log.w(TAG, "activitySignIn:onFailure", e);
                        }
                    });
    

    與 Firebase Auth 支持的其他提供商不同,Apple 不提供照片 URL。

    此外,當用戶選擇不與應用程序共享他們的電子郵件時,Apple 會為該用戶提供一個唯一的電子郵件地址(格式xyz@privaterelay.appleid.com ),並將其與您的應用程序共享。如果您配置了私人電子郵件中繼服務,Apple 會將發送到匿名地址的電子郵件轉發到用戶的真實電子郵件地址。

    Apple 僅在用戶首次登錄時與應用共享顯示名稱等用戶信息。通常,Firebase 會在用戶首次登錄 Apple 時存儲顯示名稱,您可以使用getCurrentUser().getDisplayName()獲取該信息。但是,如果您之前使用 Apple 在不使用 Firebase 的情況下讓用戶登錄應用程序,Apple 將不會向 Firebase 提供用戶的顯示名稱。

重新驗證和帳戶鏈接

相同的模式可以與startActivityForReauthenticateWithProvider()一起使用,您可以使用它來為需要最近登錄的敏感操作檢索新的憑證:

Kotlin+KTX

// The user is already signed-in.
val firebaseUser = auth.getCurrentUser()

firebaseUser
    .startActivityForReauthenticateWithProvider(/* activity= */ this, provider.build())
    .addOnSuccessListener( authResult -> {
        // User is re-authenticated with fresh tokens and
        // should be able to perform sensitive operations
        // like account deletion and email or password
        // update.
    })
    .addOnFailureListener( e -> {
        // Handle failure.
    })

Java

// The user is already signed-in.
FirebaseUser firebaseUser = mAuth.getCurrentUser();

firebaseUser
    .startActivityForReauthenticateWithProvider(/* activity= */ this, provider.build())
    .addOnSuccessListener(
        new OnSuccessListener<AuthResult>() {
          @Override
          public void onSuccess(AuthResult authResult) {
            // User is re-authenticated with fresh tokens and
            // should be able to perform sensitive operations
            // like account deletion and email or password
            // update.
          }
        })
    .addOnFailureListener(
        new OnFailureListener() {
          @Override
          public void onFailure(@NonNull Exception e) {
            // Handle failure.
          }
        });

而且,您可以使用linkWithCredential()將不同的身份提供者鏈接到現有帳戶。

請注意,Apple 要求您在將用戶的 Apple 帳戶鏈接到其他數據之前獲得用戶的明確同意。

例如,要將 Facebook 帳戶鏈接到當前的 Firebase 帳戶,請使用您通過將用戶登錄到 Facebook 獲得的訪問令牌:

Kotlin+KTX

// Initialize a Facebook credential with a Facebook access token.
val credential = FacebookAuthProvider.getCredential(token.getToken())

// Assuming the current user is an Apple user linking a Facebook provider.
mAuth.getCurrentUser().linkWithCredential(credential)
    .addOnCompleteListener(this, task -> {
        if (task.isSuccessful()) {
          // Facebook credential is linked to the current Apple user.
          // The user can now sign in to the same account
          // with either Apple or Facebook.
        }
      });

Java

// Initialize a Facebook credential with a Facebook access token.
AuthCredential credential = FacebookAuthProvider.getCredential(token.getToken());

// Assuming the current user is an Apple user linking a Facebook provider.
mAuth.getCurrentUser().linkWithCredential(credential)
    .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
      @Override
      public void onComplete(@NonNull Task<AuthResult> task) {
        if (task.isSuccessful()) {
          // Facebook credential is linked to the current Apple user.
          // The user can now sign in to the same account
          // with either Apple or Facebook.
        }
      }
    });

高級:手動處理登錄流程

您還可以使用 Apple 帳戶通過 Firebase 進行身份驗證,方法是使用 Apple Sign-In JS SDK、手動構建 OAuth 流程或使用AppAuth等 OAuth 庫來處理登錄流程。

  1. 對於每個登錄請求,生成一個隨機字符串(一個“nonce”),您將使用它來確保您獲得的 ID 令牌是專門為響應您的應用程序的身份驗證請求而授予的。此步驟對於防止重放攻擊很重要。

    您可以使用SecureRandom在 Android 上生成加密安全隨機數,如以下示例所示:

    Kotlin+KTX

    private fun generateNonce(length: Int): String {
        val generator = SecureRandom()
    
        val charsetDecoder = StandardCharsets.US_ASCII.newDecoder()
        charsetDecoder.onUnmappableCharacter(CodingErrorAction.IGNORE)
        charsetDecoder.onMalformedInput(CodingErrorAction.IGNORE)
    
        val bytes = ByteArray(length)
        val inBuffer = ByteBuffer.wrap(bytes)
        val outBuffer = CharBuffer.allocate(length)
        while (outBuffer.hasRemaining()) {
            generator.nextBytes(bytes)
            inBuffer.rewind()
            charsetDecoder.reset()
            charsetDecoder.decode(inBuffer, outBuffer, false)
        }
        outBuffer.flip()
        return outBuffer.toString()
    }
    

    Java

    private String generateNonce(int length) {
        SecureRandom generator = new SecureRandom();
    
        CharsetDecoder charsetDecoder = StandardCharsets.US_ASCII.newDecoder();
        charsetDecoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
        charsetDecoder.onMalformedInput(CodingErrorAction.IGNORE);
    
        byte[] bytes = new byte[length];
        ByteBuffer inBuffer = ByteBuffer.wrap(bytes);
        CharBuffer outBuffer = CharBuffer.allocate(length);
        while (outBuffer.hasRemaining()) {
            generator.nextBytes(bytes);
            inBuffer.rewind();
            charsetDecoder.reset();
            charsetDecoder.decode(inBuffer, outBuffer, false);
        }
        outBuffer.flip();
        return outBuffer.toString();
    }
    

    然後,獲取 nonce 的 SHA246 哈希值作為十六進製字符串:

    Kotlin+KTX

    private fun sha256(s: String): String {
        val md = MessageDigest.getInstance("SHA-256")
        val digest = md.digest(s.toByteArray())
        val hash = StringBuilder()
        for (c in digest) {
            hash.append(String.format("%02x", c))
        }
        return hash.toString()
    }
    

    Java

    private String sha256(String s) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] digest = md.digest(s.getBytes());
        StringBuilder hash = new StringBuilder();
        for (byte c: digest) {
            hash.append(String.format("%02x", c));
        }
        return hash.toString();
    }
    

    您將隨登錄請求發送 nonce 的 SHA256 哈希值,Apple 將在響應中原封不動地傳遞該哈希值。 Firebase 通過散列原始隨機數並將其與 Apple 傳遞的值進行比較來驗證響應。

  2. 使用您的 OAuth 庫或其他方法啟動 Apple 的登錄流程。請務必在您的請求中包含經過哈希處理的隨機數作為參數。

  3. 收到 Apple 的響應後,從響應中獲取 ID 令牌並使用它和未經哈希處理的隨機數來創建AuthCredential

    Kotlin+KTX

    val credential =  OAuthProvider.newCredentialBuilder("apple.com")
        .setIdTokenWithRawNonce(appleIdToken, rawUnhashedNonce)
        .build()
    

    Java

    AuthCredential credential =  OAuthProvider.newCredentialBuilder("apple.com")
        .setIdTokenWithRawNonce(appleIdToken, rawUnhashedNonce)
        .build();
    
  4. 使用 Firebase 憑據通過 Firebase 進行身份驗證:

    Kotlin+KTX

    auth.signInWithCredential(credential)
          .addOnCompleteListener(this) { task ->
              if (task.isSuccessful) {
                // User successfully signed in with Apple ID token.
                // ...
              }
          }
    

    Java

    mAuth.signInWithCredential(credential)
        .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
          @Override
          public void onComplete(@NonNull Task<AuthResult> task) {
            if (task.isSuccessful()) {
              // User successfully signed in with Apple ID token.
              // ...
            }
          }
        });
    

如果調用signInWithCredential成功,您可以使用getCurrentUser方法獲取用戶的帳戶數據。

下一步

用戶首次登錄後,將創建一個新的用戶帳戶並將其鏈接到用戶登錄所用的憑據,即用戶名和密碼、電話號碼或身份驗證提供商信息。這個新帳戶存儲為您的 Firebase 項目的一部分,可用於在項目中的每個應用程序中識別用戶,無論用戶如何登錄。

  • 在您的應用中,您可以從FirebaseUser對象獲取用戶的基本個人資料信息。請參閱管理用戶

  • 在您的 Firebase Realtime Database 和 Cloud Storage Security Rules中,您可以從auth變量中獲取登錄用戶的唯一用戶 ID,並使用它來控制用戶可以訪問的數據。

您可以允許用戶使用多個身份驗證提供程序登錄您的應用程序,方法是將身份驗證提供程序憑據鏈接到現有用戶帳戶。

要註銷用戶,請調用signOut

Kotlin+KTX

Firebase.auth.signOut()

Java

FirebaseAuth.getInstance().signOut();