Google 致力于为黑人社区推动种族平等。查看具体举措

在 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();