創建自定義令牌

Firebase 允許您使用安全的 JSON Web 令牌 (JWT) 對用戶或設備進行身份驗證,從而讓您完全控制身份驗證。您在服務器上生成這些令牌,將它們傳回客戶端設備,然後使用它們通過signInWithCustomToken()方法進行身份驗證。

為此,您必須創建一個接受登錄憑據(例如用戶名和密碼)的服務器端點,並且如果憑據有效,則返回自定義 JWT。然後,客戶端設備可以使用從您的服務器返回的自定義 JWT 來通過 Firebase( iOS+AndroidWeb )進行身份驗證。通過身份驗證後,將在訪問其他 Firebase 服務(例如 Firebase 實時數據庫和雲存儲)時使用此身份。此外,JWT 的內容將在您的Realtime Database Security 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.AuthResult result = task.Result;
  Debug.LogFormat("User signed in successfully: {0} ({1})",
      result.User.DisplayName, result.User.UserId);
});

C++

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

Web 命名空間 API

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

網絡模塊化 API

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。