創建自定義令牌

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

為此,您必須創建一個接受登錄憑據(例如用戶名和密碼)的服務器端點,並且如果憑據有效,則返回自定義 JWT。然後,客戶端設備可以使用從您的服務器返回的自定義 JWT 向 Firebase( iOS+Androidweb )進行身份驗證。一旦通過身份驗證,此身份將在訪問其他 Firebase 服務時使用,例如 Firebase 實時數據庫和雲存儲。此外,JWT 的內容將在實時數據庫規則中的auth對象和雲存儲安全規則中的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 管理的環境中使用時,此方法將使用指定服務帳戶的密鑰對令牌進行簽名。但是,它使用遠程 Web 服務,您可能必須通過 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 文件。

服務帳號 ID 可以在Google Cloud Console或下載的服務帳號 JSON 文件的client_email字段中找到。服務帳戶 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+

Objective-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-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-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。