Firebase 允許您使用安全的 JSON Web 令牌 (JWT) 對用戶或設備進行身份驗證,從而讓您完全控制身份驗證。您在服務器上生成這些令牌,將它們傳回客戶端設備,然後使用它們通過signInWithCustomToken()
方法進行身份驗證。
為此,您必須創建一個接受登錄憑據(例如用戶名和密碼)的服務器端點,並且如果憑據有效,則返回自定義 JWT。然後,客戶端設備可以使用從您的服務器返回的自定義 JWT 來通過 Firebase( iOS+ 、 Android 、 Web )進行身份驗證。通過身份驗證後,將在訪問其他 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
:
- 在 Google Cloud Console 中打開IAM 和管理頁面。
- 選擇您的項目並單擊“繼續”。
- 單擊與您要更新的服務帳戶對應的編輯圖標。
- 單擊“添加另一個角色”。
- 在搜索過濾器中輸入“Service Account Token Creator”,然後從結果中選擇它。
- 單擊“保存”以確認角色授予。
有關此過程的更多詳細信息,請參閱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。