获取我们在 Firebase 峰会上发布的所有信息,了解 Firebase 可如何帮助您加快应用开发速度并满怀信心地运行应用。了解详情

创建自定义令牌

Firebase 允许您使用安全的 JSON Web 令牌 (JWT) 对用户或设备进行身份验证,从而让您完全控制身份验证。您在服务器上生成这些令牌,将它们传回客户端设备,然后使用它们通过signInWithCustomToken()方法进行身份验证。

为此,您必须创建一个接受登录凭据(例如用户名和密码)的服务器端点,并且如果凭据有效,则返回自定义 JWT。然后,客户端设备可以使用从您的服务器返回的自定义 JWT 来通过 Firebase( iOS+AndroidWeb )进行身份验证。通过身份验证后,将在访问其他 Firebase 服务(例如 Firebase 实时数据库和云存储)时使用此身份。此外,JWT 的内容将在您的Realtime Database Rules中的auth对象和Cloud Storage Security Rules中的request.auth对象中可用。

您可以使用 Firebase Admin SDK 创建自定义令牌,或者如果您的服务器使用 Firebase 本身不支持的语言编写,则可以使用第三方 JWT 库。

在你开始之前

自定义令牌是已签名的 JWT,其中用于签名的私钥属于 Google 服务帐户。有多种方法可以指定 Firebase Admin SDK 用于签署自定义令牌的 Google 服务帐户:

  • 使用服务帐户 JSON 文件——此方法可用于任何环境,但需要您将服务帐户 JSON 文件与您的代码一起打包。必须特别注意确保服务帐户 JSON 文件不会暴露给外部各方。
  • 让 Admin SDK 发现服务帐户——此方法可用于 Google 管理的环境,例如 Google Cloud Functions 和 App Engine。您可能需要通过 Google Cloud Console 配置一些额外的权限。
  • 使用服务帐户 ID——在 Google 管理的环境中使用时,此方法将使用指定服务帐户的密钥签署令牌。但是,它使用远程网络服务,您可能必须通过 Google Cloud Console 为此服务帐户配置额外的权限。

使用服务帐户 JSON 文件

服务账户 JSON 文件包含服务账户对应的所有信息(包括 RSA 私钥)。它们可以从 Firebase 控制台下载。按照Admin SDK 设置说明获取有关如何使用服务帐户 JSON 文件初始化 Admin SDK 的更多信息。

这种初始化方法适用于范围广泛的 Admin SDK 部署。它还使 Admin SDK 能够在本地创建和签署自定义令牌,而无需进行任何远程 API 调用。这种方法的主要缺点是它需要您将服务帐户 JSON 文件与您的代码打包在一起。另请注意,服务帐户 JSON 文件中的私钥是敏感信息,必须特别注意保密。具体来说,不要将服务帐户 JSON 文件添加到公共版本控制中。

让 Admin SDK 发现服务帐户

如果您的代码部署在由 Google 管理的环境中,Admin SDK 可以尝试自动发现一种方法来签署自定义令牌:

  • 如果您的代码部署在适用于 Java、Python 或 Go 的 App Engine 标准环境中,Admin SDK 可以使用该环境中存在的App Identity 服务来签署自定义令牌。 App Identity 服务使用 Google App Engine 为您的应用程序配置的服务帐户对数据进行签名。

  • 如果您的代码部署在其他一些托管环境中(例如 Google Cloud Functions、Google Compute Engine),Firebase Admin SDK 可以从本地元数据服务器自动发现服务帐户 ID 字符串。然后将发现的服务帐户 ID 与 IAM 服务结合使用以远程签署令牌。

要使用这些签名方法,请使用 Google 应用程序默认凭据初始化 SDK,并且不要指定服务帐户 ID 字符串:

节点.js

initializeApp();

爪哇

FirebaseApp.initializeApp();

Python

default_app = firebase_admin.initialize_app()

app, err := firebase.NewApp(context.Background(), nil)
if err != nil {
	log.Fatalf("error initializing app: %v\n", err)
}

C#

FirebaseApp.Create();

要在本地测试相同的代码,请下载服务帐户 JSON 文件并将GOOGLE_APPLICATION_CREDENTIALS环境变量设置为指向它。

如果 Firebase Admin SDK 必须发现服务帐户 ID 字符串,它会在您的代码首次创建自定义令牌时执行此操作。结果被缓存并重新用于后续的令牌签名操作。自动发现的服务账户 ID 通常是 Google Cloud 提供的默认服务账户之一:

就像明确指定的服务帐户 ID 一样,自动发现的服务帐户 ID 必须具有iam.serviceAccounts.signBlob权限才能创建自定义令牌。您可能必须使用 Google Cloud Console 的IAM 和管理部分来授予默认服务帐户必要的权限。有关更多详细信息,请参阅下面的故障排除部分。

使用服务帐号 ID

为了保持应用程序各个部分之间的一致性,您可以指定一个服务帐户 ID,其密钥将用于在 Google 管理的环境中运行时签署令牌。这可以使 IAM 策略更简单、更安全,并避免必须在您的代码中包含服务帐户 JSON 文件。

可以在Google Cloud Console或下载的服务帐户 JSON 文件的client_email字段中找到服务帐户 ID。服务帐户 ID 是具有以下格式的电子邮件地址: <client-id>@<project-id>.iam.gserviceaccount.com 。它们在 Firebase 和 Google Cloud 项目中唯一标识服务帐户。

要使用单独的服务帐户 ID 创建自定义令牌,请按如下所示初始化 SDK:

节点.js

initializeApp({
  serviceAccountId: 'my-client-id@my-project-id.iam.gserviceaccount.com',
});

爪哇

FirebaseOptions options = FirebaseOptions.builder()
    .setCredentials(GoogleCredentials.getApplicationDefault())
    .setServiceAccountId("my-client-id@my-project-id.iam.gserviceaccount.com")
    .build();
FirebaseApp.initializeApp(options);

Python

options = {
    'serviceAccountId': 'my-client-id@my-project-id.iam.gserviceaccount.com',
}
firebase_admin.initialize_app(options=options)

conf := &firebase.Config{
	ServiceAccountID: "my-client-id@my-project-id.iam.gserviceaccount.com",
}
app, err := firebase.NewApp(context.Background(), conf)
if err != nil {
	log.Fatalf("error initializing app: %v\n", err)
}

C#

FirebaseApp.Create(new AppOptions()
{
    Credential = GoogleCredential.GetApplicationDefault(),
    ServiceAccountId = "my-client-id@my-project-id.iam.gserviceaccount.com",
});

服务帐户 ID 不是敏感信息,因此暴露它们无关紧要。但是,要使用指定的服务帐户签署自定义令牌,Firebase Admin SDK 必须调用远程服务。此外,您还必须确保 Admin SDK 用于进行此调用的服务帐户(通常是{project-name}@appspot.gserviceaccount.com )具有iam.serviceAccounts.signBlob权限。有关更多详细信息,请参阅下面的故障排除部分。

使用 Firebase Admin SDK 创建自定义令牌

Firebase Admin SDK 具有用于创建自定义令牌的内置方法。至少,您需要提供一个uid ,它可以是任何字符串,但应该唯一标识您正在验证的用户或设备。这些令牌在一小时后过期。

节点.js

const uid = 'some-uid';

getAuth()
  .createCustomToken(uid)
  .then((customToken) => {
    // Send token back to client
  })
  .catch((error) => {
    console.log('Error creating custom token:', error);
  });

爪哇

String uid = "some-uid";

String customToken = FirebaseAuth.getInstance().createCustomToken(uid);
// Send token back to client

Python

uid = 'some-uid'

custom_token = auth.create_custom_token(uid)

client, err := app.Auth(context.Background())
if err != nil {
	log.Fatalf("error getting Auth client: %v\n", err)
}

token, err := client.CustomToken(ctx, "some-uid")
if err != nil {
	log.Fatalf("error minting custom token: %v\n", err)
}

log.Printf("Got custom token: %v\n", token)

C#

var uid = "some-uid";

string customToken = await FirebaseAuth.DefaultInstance.CreateCustomTokenAsync(uid);
// Send token back to client

您还可以选择指定要包含在自定义令牌中的其他声明。例如,在下面,一个premiumAccount字段已添加到自定义令牌中,它将在您的安全规则中的auth / request.auth对象中可用:

节点.js

const userId = 'some-uid';
const additionalClaims = {
  premiumAccount: true,
};

getAuth()
  .createCustomToken(userId, additionalClaims)
  .then((customToken) => {
    // Send token back to client
  })
  .catch((error) => {
    console.log('Error creating custom token:', error);
  });

爪哇

String uid = "some-uid";
Map<String, Object> additionalClaims = new HashMap<String, Object>();
additionalClaims.put("premiumAccount", true);

String customToken = FirebaseAuth.getInstance()
    .createCustomToken(uid, additionalClaims);
// Send token back to client

Python

uid = 'some-uid'
additional_claims = {
    'premiumAccount': True
}

custom_token = auth.create_custom_token(uid, additional_claims)

client, err := app.Auth(context.Background())
if err != nil {
	log.Fatalf("error getting Auth client: %v\n", err)
}

claims := map[string]interface{}{
	"premiumAccount": true,
}

token, err := client.CustomTokenWithClaims(ctx, "some-uid", claims)
if err != nil {
	log.Fatalf("error minting custom token: %v\n", err)
}

log.Printf("Got custom token: %v\n", token)

C#

var uid = "some-uid";
var additionalClaims = new Dictionary<string, object>()
{
    { "premiumAccount", true },
};

string customToken = await FirebaseAuth.DefaultInstance
    .CreateCustomTokenAsync(uid, additionalClaims);
// Send token back to client

保留的自定义令牌名称

在客户端上使用自定义令牌登录

创建自定义令牌后,您应该将其发送到您的客户端应用程序。客户端应用程序通过调用signInWithCustomToken()使用自定义令牌进行身份验证:

iOS+

目标-C
[[FIRAuth auth] signInWithCustomToken:customToken
                           completion:^(FIRAuthDataResult * _Nullable authResult,
                                        NSError * _Nullable error) {
  // ...
}];
迅速
Auth.auth().signIn(withCustomToken: customToken ?? "") { user, error in
  // ...
}

安卓

mAuth.signInWithCustomToken(mCustomToken)
        .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
            @Override
            public void onComplete(@NonNull Task<AuthResult> task) {
                if (task.isSuccessful()) {
                    // Sign in success, update UI with the signed-in user's information
                    Log.d(TAG, "signInWithCustomToken:success");
                    FirebaseUser user = mAuth.getCurrentUser();
                    updateUI(user);
                } else {
                    // If sign in fails, display a message to the user.
                    Log.w(TAG, "signInWithCustomToken:failure", task.getException());
                    Toast.makeText(CustomAuthActivity.this, "Authentication failed.",
                            Toast.LENGTH_SHORT).show();
                    updateUI(null);
                }
            }
        });

统一

auth.SignInWithCustomTokenAsync(custom_token).ContinueWith(task => {
  if (task.IsCanceled) {
    Debug.LogError("SignInWithCustomTokenAsync was canceled.");
    return;
  }
  if (task.IsFaulted) {
    Debug.LogError("SignInWithCustomTokenAsync encountered an error: " + task.Exception);
    return;
  }

  Firebase.Auth.FirebaseUser newUser = task.Result;
  Debug.LogFormat("User signed in successfully: {0} ({1})",
      newUser.DisplayName, newUser.UserId);
});

C++

firebase::Future<firebase::auth::User*> result =
    auth->SignInWithCustomToken(custom_token);

Web version 8

firebase.auth().signInWithCustomToken(token)
  .then((userCredential) => {
    // Signed in
    var user = userCredential.user;
    // ...
  })
  .catch((error) => {
    var errorCode = error.code;
    var errorMessage = error.message;
    // ...
  });

Web version 9

import { getAuth, signInWithCustomToken } from "firebase/auth";

const auth = getAuth();
signInWithCustomToken(auth, token)
  .then((userCredential) => {
    // Signed in
    const user = userCredential.user;
    // ...
  })
  .catch((error) => {
    const errorCode = error.code;
    const errorMessage = error.message;
    // ...
  });

如果身份验证成功,您的用户现在将使用自定义令牌中包含的uid指定的帐户登录到您的客户端应用程序。如果该帐户以前不存在,将为该用户创建一个记录。

与其他登录方法(例如signInWithEmailAndPassword()signInWithCredential() )一样,实时数据库规则中的auth对象和Cloud Storage 安全规则中的request.auth对象将填充用户的uid 。在这种情况下, uid将是您在生成自定义令牌时指定的那个。

数据库规则

{
  "rules": {
    "adminContent": {
      ".read": "auth.uid === 'some-uid'"
    }
  }
}

存储规则

service firebase.storage {
  match /b/<your-firebase-storage-bucket>/o {
    match /adminContent/{filename} {
      allow read, write: if request.auth != null && request.auth.uid == "some-uid";
    }
  }
}

如果自定义令牌包含其他声明,则可以从规则中的auth.token (Firebase 实时数据库)或request.auth.token (云存储)对象中引用它们:

数据库规则

{
  "rules": {
    "premiumContent": {
      ".read": "auth.token.premiumAccount === true"
    }
  }
}

存储规则

service firebase.storage {
  match /b/<your-firebase-storage-bucket>/o {
    match /premiumContent/{filename} {
      allow read, write: if request.auth.token.premiumAccount == true;
    }
  }
}

使用第三方 JWT 库创建自定义令牌

如果您的后端使用的语言没有官方 Firebase Admin SDK,您仍然可以手动创建自定义令牌。首先,找到适用于您的语言的第三方 JWT 库。然后,使用该 JWT 库创建一个包含以下声明的 JWT:

自定义令牌声明
alg算法"RS256"
iss发行人您项目的服务帐号电子邮件地址
sub主题您项目的服务帐号电子邮件地址
aud观众"https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
iat发行时间当前时间,自 UNIX 纪元以来的秒数
exp到期时间自 UNIX 纪元以来令牌过期的时间(以秒为单位)。它最多可以比iat晚 3600 秒
注意:这仅控制自定义令牌本身过期的时间。但是一旦您使用signInWithCustomToken()登录用户,他们将保持登录到设备,直到他们的会话无效或用户注销。
uid已登录用户的唯一标识符必须是一个字符串,长度在 1-128 个字符之间(含)。较短的uid提供更好的性能。
claims (可选)包含在安全规则auth / request.auth变量中的可选自定义声明

以下是如何使用 Firebase Admin SDK 不支持的各种语言创建自定义令牌的一些示例实现:

PHP

使用php-jwt

// Requires: composer require firebase/php-jwt
use Firebase\JWT\JWT;

// Get your service account's email address and private key from the JSON key file
$service_account_email = "abc-123@a-b-c-123.iam.gserviceaccount.com";
$private_key = "-----BEGIN PRIVATE KEY-----...";

function create_custom_token($uid, $is_premium_account) {
  global $service_account_email, $private_key;

  $now_seconds = time();
  $payload = array(
    "iss" => $service_account_email,
    "sub" => $service_account_email,
    "aud" => "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
    "iat" => $now_seconds,
    "exp" => $now_seconds+(60*60),  // Maximum expiration time is one hour
    "uid" => $uid,
    "claims" => array(
      "premium_account" => $is_premium_account
    )
  );
  return JWT::encode($payload, $private_key, "RS256");
}

红宝石

使用ruby-jwt

require "jwt"

# Get your service account's email address and private key from the JSON key file
$service_account_email = "service-account@my-project-abc123.iam.gserviceaccount.com"
$private_key = OpenSSL::PKey::RSA.new "-----BEGIN PRIVATE KEY-----\n..."

def create_custom_token(uid, is_premium_account)
  now_seconds = Time.now.to_i
  payload = {:iss => $service_account_email,
             :sub => $service_account_email,
             :aud => "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
             :iat => now_seconds,
             :exp => now_seconds+(60*60), # Maximum expiration time is one hour
             :uid => uid,
             :claims => {:premium_account => is_premium_account}}
  JWT.encode payload, $private_key, "RS256"
end

创建自定义令牌后,将其发送到您的客户端应用以用于通过 Firebase 进行身份验证。请参阅上面的代码示例,了解如何执行此操作。

故障排除

本节概述了开发人员在创建自定义令牌时可能遇到的一些常见问题,以及如何解决这些问题。

IAM API 未启用

如果您指定用于签署令牌的服务帐户 ID,您可能会收到类似于以下内容的错误:

Identity and Access Management (IAM) API has not been used in project
1234567890 before or it is disabled. Enable it by visiting
https://console.developers.google.com/apis/api/iam.googleapis.com/overview?project=1234567890
then retry. If you enabled this API recently, wait a few minutes for the action
to propagate to our systems and retry.

Firebase Admin SDK 使用IAM API对令牌进行签名。此错误表明您的 Firebase 项目当前未启用 IAM API。在 Web 浏览器中打开错误消息中的链接,然后单击“启用 API”按钮为您的项目启用它。

服务帐号没有所需的权限

如果运行 Firebase Admin SDK 的服务帐户没有iam.serviceAccounts.signBlob权限,您可能会收到如下错误消息:

Permission iam.serviceAccounts.signBlob is required to perform this operation
on service account projects/-/serviceAccounts/{your-service-account-id}.

解决此问题的最简单方法是将“服务帐户令牌创建者”IAM 角色授予相关服务帐户,通常是{project-name}@appspot.gserviceaccount.com

  1. 在 Google Cloud Console 中打开IAM 和管理页面。
  2. 选择您的项目并单击“继续”。
  3. 单击与您要更新的服务帐户对应的编辑图标。
  4. 单击“添加另一个角色”。
  5. 在搜索过滤器中输入“Service Account Token Creator”,然后从结果中选择它。
  6. 单击“保存”以确认角色授予。

有关此过程的更多详细信息,请参阅IAM 文档,或了解如何使用 gcloud 命令行工具更新角色。

无法确定服务帐号

如果您收到类似于以下内容的错误消息,则表示 Firebase Admin SDK 未正确初始化。

Failed to determine service account ID. Initialize the SDK with service account
credentials or specify a service account ID with iam.serviceAccounts.signBlob
permission.

如果您依靠 SDK 自动发现服务帐户 ID,请确保将代码部署在具有元数据服务器的托管 Google 环境中。否则,请务必在 SDK 初始化时指定服务帐户 JSON 文件或服务帐户 ID。