Firebase允許您使用安全的JSON Web令牌(JWT)對用戶或設備進行身份驗證,從而使您可以完全控制身份驗證。您在服務器上生成這些令牌,將它們傳遞回客戶端設備,然後使用它們通過signInWithCustomToken()
方法進行身份驗證。
為此,您必須創建一個服務器端點,該端點接受登錄憑據(例如用戶名和密碼),並且如果憑據有效,則返回自定義JWT。然後,客戶端設備可以使用從服務器返回的自定義JWT向Firebase( iOS , Android , Web )進行身份驗證。身份驗證後,將在訪問其他Firebase服務(如Firebase實時數據庫和雲存儲)時使用此身份。此外,JWT的內容將在您的實時數據庫規則中的auth
像中以及您的Cloud Storage安全規則中的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 Service使用Google App Engine為您的應用程序提供的服務帳戶對數據進行簽名。
如果您的代碼部署在其他託管環境(例如Google Cloud Functions,Google Compute Engine)中,則Firebase Admin SDK可以從本地元數據服務器自動發現服務帳戶ID字符串。然後,將發現的服務帳戶ID與IAM服務結合使用,以對令牌進行遠程簽名。
要使用這些簽名方法,請使用Google Application Default憑據初始化SDK,並且不要指定服務帳戶ID字符串:
Node.js
admin.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和admin部分來授予默認服務帳戶必要的權限。有關更多詳細信息,請參見下面的故障排除部分。
使用服務帳戶ID
為了保持應用程序各個部分之間的一致性,您可以指定服務帳戶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:
Node.js
admin.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
,它可以是任何字符串,但應唯一地標識要認證的用戶或設備。這些令牌在一小時後過期。
Node.js
const uid = 'some-uid';
admin
.auth()
.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,
};
admin
.auth()
.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
物鏡
[[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);
網頁
firebase.auth().signInWithCustomToken(token)
.then((userCredential) => {
// Signed in
var user = userCredential.user;
// ...
})
.catch((error) => {
var errorCode = error.code;
var 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到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
:
- 在Google Cloud Console中打開IAM和管理頁面。
- 選擇您的項目,然後單擊“繼續”。
- 單擊與您要更新的服務帳戶相對應的編輯圖標。
- 單擊“添加其他角色”。
- 在搜索過濾器中鍵入“服務帳戶令牌創建者”,然後從結果中選擇它。
- 單擊“保存”以確認角色授予。
有關此過程的更多詳細信息,請參閱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。