创建自定义令牌

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

为了实现上述目标,您必须创建一个接受登录凭据(如用户名和密码)并在凭据有效时返回自定义 JWT 的服务器端点。然后,客户端设备即可使用从您的服务器返回的这个自定义 JWT 进行 Firebase 身份验证(iOSAndroid网页)。通过验证的身份会被用于访问其他 Firebase 服务(如 Firebase 实时数据库和 Cloud Storage)。此外,您的 Firebase 实时数据库安全规则中的 auth 对象和 Cloud Storage 安全规则中的 request.auth 对象都会包含 JWT 内容。

您可以使用 Firebase Admin SDK 创建自定义令牌;或者,如果您的服务器是使用非 Firebase 原生支持的语言编写的,您可以使用第三方 JWT 库进行创建。

准备工作

自定义令牌是已签署的 JWT,签名时使用的私钥属于 Google 服务帐号。您可以通过多种方法指定供 Firebase Admin SDK 用于签署自定义令牌的 Google 服务帐号:

  • 使用服务帐号 JSON 文件 - 此方法可用在任何环境中,但要求您必须将服务帐号 JSON 文件与代码一起打包。必须特别注意,确保不会向外部方公开服务帐号 JSON 文件。
  • 使用服务帐号 ID - 此方法可用在任何环境中,但通常比使用完整服务帐号 JSON 文件更简单、更安全。但是,它使用远程网络服务签署令牌,您可能必须通过 Google Cloud Platform Console 配置一些其他权限。
  • 让 Admin SDK 发现服务帐号 - 此方法可用在由 Google 管理的环境中,例如 Google Cloud Functions 和 Google App Engine。您可能必须通过 Google Cloud Platform Console 配置一些其他权限。

使用服务帐号 JSON 文件

服务帐号 JSON 文件包含与服务帐号对应的所有信息(包括 RSA 私钥)。可以从 Firebase 控制台下载它们。请参阅 Admin SDK 设置说明,详细了解如何使用服务帐号 JSON 文件初始化 Admin SDK。

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

使用服务帐号 ID

您可以仅使用服务帐号 ID 字符串初始化 Firebase Admin SDK,而不是将完整服务帐号 JSON 文件与代码一起打包。该 ID 可以在 Firebase 控制台中找到。或者,您可以在下载的服务帐号 JSON 文件的 client_email 字段中找到它。服务帐号 ID 是具有以下格式的电子邮件地址:<client-id>@<project-id>.iam.gserviceaccount.com。这些 ID 用于唯一标识 Firebase 和 Google Cloud Platform 项目中的服务帐号。

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

Node.js

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

Java

FirebaseOptions options = new FirebaseOptions.Builder()
    .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)

Go

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 不是敏感信息,因此公开这些 ID 无关紧要。但是,要仅使用服务帐号 ID 签署自定义令牌,Firebase Admin SDK 必须调用远程服务。此外,您还必须确保选择用于此用途的服务帐号具有 iam.serviceAccounts.signBlob 权限。要了解详情,请参阅下面的问题排查部分。

让 Admin SDK 发现服务帐号

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

  • 如果您的代码部署在适用于 Java、Python 或 Go 的 Google 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 字符串:

Node.js

admin.initializeApp();

Java

FirebaseApp.initializeApp();

Python

default_app = firebase_admin.initialize_app()

Go

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 Platform 提供的默认服务帐号之一:

与明确指定的服务帐号 ID 一样,自动发现的服务帐号 ID 必须具有 iam.serviceAccounts.signBlob 权限才能创建自定义令牌。您可能必须使用 Google Cloud Platform Console 的 IAM 和管理部分为默认服务帐号授予必要的权限。要了解详情,请参阅下面的问题排查部分。

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

Firebase Admin SDK 内置了创建自定义令牌的方法。您至少需要提供一个 uid - 它可以是任何可唯一标识要进行身份验证的用户或设备的字符串。这些令牌会在 1 小时后过期。

Node.js

var uid = "some-uid";

admin.auth().createCustomToken(uid)
  .then(function(customToken) {
    // Send token back to client
  })
  .catch(function(error) {
    console.log("Error creating custom token:", error);
  });

Java

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)

Go

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 对象中:

Node.js

var uid = "some-uid";
var additionalClaims = {
  premiumAccount: true
};

admin.auth().createCustomToken(uid, additionalClaims)
  .then(function(customToken) {
    // Send token back to client
  })
  .catch(function(error) {
    console.log("Error creating custom token:", error);
  });

Java

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)

Go

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

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

Android

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);
                }
            }
        });

Unity

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

网页

firebase.auth().signInWithCustomToken(token).catch(function(error) {
  // Handle Errors here.
  var errorCode = error.code;
  var errorMessage = error.message;
  // ...
});

如果身份验证成功,则系统现在会使用自定义令牌中的 uid 所指定的帐号,将您的用户登录到客户端应用中。如果先前不存在此帐号,则会为该用户创建一条记录。

与使用其他登录方法(如 signInWithEmailAndPassword()signInWithCredential())时一样,您的 Firebase 实时数据库安全规则中的 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.uid == "some-uid";
    }
  }
}

如果自定义令牌包含额外的声明,您可以从规则中的 auth.token(Firebase 实时数据库)或 request.auth.token (Cloud Storage) 对象引用这些声明:

数据库规则

{
  "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-36 个字符的字符串)
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

使用 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。在网络浏览器中打开错误消息中的链接,然后点击“启用 API”按钮为您的项目启用此 API。

服务帐号没有所需的权限

如果用于签署令牌的服务帐号 ID 没有 iam.serviceAccounts.signBlob 权限,您可能会收到如下错误消息:

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

解决此问题的最简单方法是向相关服务帐号授予“服务帐号令牌创建者”IAM 角色:

  1. 在 Google Cloud Platform Console 中打开 IAM 和管理页面。
  2. 选择您的项目,然后点击“继续”。
  3. 点击与要更新的服务帐号 ID 对应的修改图标。
  4. 点击“添加其他角色”。
  5. 在搜索过滤器中输入“服务帐号令牌创建者”,然后从结果中选择它。
  6. 点击“保存”确认授予此角色。

要详细了解此过程,或了解如何使用 gcloud 命令行工具更新角色,请参阅 IAM 文档

无法确定服务帐号

如果您收到类似如下的错误消息,则表明 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。

发送以下问题的反馈:

此网页
需要帮助?请访问我们的支持页面