創建自定義令牌

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 控制台配置一些額外的權限。
  • 使用服務帳戶 ID - 在 Google 管理的環境中使用時,此方法將使用指定服務帳戶的金鑰對令牌進行簽署。不過,它使用遠端網路服務,您可能需要透過 Google Cloud 控制台為此服務帳號配置其他權限。

使用服務帳戶 JSON 文件

服務帳戶JSON檔案包含與服務帳戶對應的所有資訊(包括RSA私鑰)。可以從 Firebase 控制台下載它們。請遵循管理 SDK 設定說明,以了解有關如何使用服務帳戶 JSON 檔案初始化管理 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 字串:

Node.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 控制台的IAM 和管理部分來授予預設服務帳號必要的權限。有關更多詳細信息,請參閱下面的故障排除部分。

使用服務帳戶 ID

為了保持應用程式各個部分之間的一致性,您可以指定一個服務帳戶 ID,其金鑰將用於在 Google 管理的環境中執行時對令牌進行簽署。這可以使 IAM 策略更簡單、更安全,並且避免在程式碼中包含服務帳戶 JSON 檔案。

服務帳號 ID 可以在Google Cloud 控制台或下載的服務帳號 JSON 檔案的client_email欄位中找到。服務帳號 ID 是具有以下格式的電子郵件地址: <client-id>@<project-id>.iam.gserviceaccount.com 。它們唯一標識 Firebase 和 Google Cloud 專案中的服務帳號。

若要使用單獨的服務帳戶 ID 建立自訂令牌,請初始化 SDK,如下所示:

Node.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 管理 SDK 必須呼叫遠端服務。此外,您還必須確保 Admin SDK 用於進行此呼叫的服務帳戶(通常為{project-name}@appspot.gserviceaccount.com )具有iam.serviceAccounts.signBlob權限。有關更多詳細信息,請參閱下面的故障排除部分。

使用 Firebase Admin SDK 建立自訂令牌

Firebase Admin SDK 有一個用於建立自訂令牌的內建方法。您至少需要提供一個uid ,它可以是任何字串,但應唯一標識您正在驗證的使用者或裝置。這些令牌在一小時後過期。

Node.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物件中可用:

Node.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.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物件和雲端儲存安全性規則中的request.auth物件將使用使用者的uid填入。在這種情況下, 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 個字元之間(含 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 管理 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 控制台中開啟IAM 和管理頁面。
  2. 選擇您的項目並點擊“繼續”。
  3. 按一下與您要更新的服務帳戶相對應的編輯圖示。
  4. 按一下“新增另一個角色”。
  5. 在搜尋篩選器中輸入“服務帳戶令牌建立者”,然後從結果中選擇它。
  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。