將使用者從現有的應用程式遷出
本文說明如何將使用者從現有應用程式遷移至 Identity Platform。
事前準備
使用 Admin SDK
您可以使用 Admin SDK 匯入使用者,而無須將使用者資料匯出至 CSV 或 JSON。您可以將使用者匯入 Identity Platform 支援的所有提供者,包括 OAuth、SAML 和 OIDC。
單一 API 呼叫最多可匯入 1,000 位使用者。匯入作業會針對速度進行最佳化,且不會檢查重複欄位。匯入與現有 uid
衝突的使用者,會取代現有使用者。如果匯入的使用者有重複的其他欄位 (例如 email
),系統會產生具有相同值的額外使用者。
即使發生使用者專屬錯誤,Admin SDK 仍會嘗試上傳提供的使用者完整清單。此作業會傳回結果,其中包含成功和失敗的匯入作業摘要。系統會針對每個失敗的使用者匯入作業傳回錯誤詳細資料。
匯入使用者,並使用 HMAC 雜湊密碼
HMAC 雜湊演算法包括 HMAC_MD5
、HMAC_SHA1
、HMAC_SHA256
和 HMAC_SHA512
。您必須提供雜湊簽署金鑰。
Node.js
getAuth() .importUsers( [ { uid: 'some-uid', email: 'user@example.com', // Must be provided in a byte buffer. passwordHash: Buffer.from('password-hash'), // Must be provided in a byte buffer. passwordSalt: Buffer.from('salt'), }, ], { hash: { algorithm: 'HMAC_SHA256', // Must be provided in a byte buffer. key: Buffer.from('secret'), }, } ) .then((results) => { results.errors.forEach((indexedError) => { console.log(`Error importing user ${indexedError.index}`); }); }) .catch((error) => { console.log('Error importing users :', error); });
Java
try { List<ImportUserRecord> users = Collections.singletonList(ImportUserRecord.builder() .setUid("some-uid") .setEmail("user@example.com") .setPasswordHash("password-hash".getBytes()) .setPasswordSalt("salt".getBytes()) .build()); UserImportOptions options = UserImportOptions.withHash( HmacSha256.builder() .setKey("secret".getBytes()) .build()); UserImportResult result = FirebaseAuth.getInstance().importUsers(users, options); for (ErrorInfo indexedError : result.getErrors()) { System.out.println("Failed to import user: " + indexedError.getReason()); } } catch (FirebaseAuthException e) { System.out.println("Error importing users: " + e.getMessage()); }
Python
users = [ auth.ImportUserRecord( uid='some-uid', email='user@example.com', password_hash=b'password_hash', password_salt=b'salt' ), ] hash_alg = auth.UserImportHash.hmac_sha256(key=b'secret') try: result = auth.import_users(users, hash_alg=hash_alg) for err in result.errors: print('Failed to import user:', err.reason) except exceptions.FirebaseError as error: print('Error importing users:', error)
Go
users := []*auth.UserToImport{ (&auth.UserToImport{}). UID("some-uid"). Email("user@example.com"). PasswordHash([]byte("password-hash")). PasswordSalt([]byte("salt")), } h := hash.HMACSHA256{ Key: []byte("secret"), } result, err := client.ImportUsers(ctx, users, auth.WithHash(h)) if err != nil { log.Fatalln("Error importing users", err) } for _, e := range result.Errors { log.Println("Failed to import user", e.Reason) }
匯入使用者,並使用 MD5、SHA 和 PBKDF 雜湊密碼
MD5、SHA 和 PBKDF 雜湊演算法包括 MD5
、SHA1
、SHA256
、SHA512
、PBKDF_SHA1
和 PBKDF2_SHA256
。您必須提供用於將密碼轉換為雜湊值的輪次 (MD5
為 0 到 8192 之間、SHA1
、SHA256
和 SHA512
為 1 到 8192 之間,PBKDF_SHA1
和 PBKDF2_SHA256
為 0 到 120000 之間)。
Node.js
getAuth() .importUsers( [ { uid: 'some-uid', email: 'user@example.com', // Must be provided in a byte buffer. passwordHash: Buffer.from('password-hash'), // Must be provided in a byte buffer. passwordSalt: Buffer.from('salt'), }, ], { hash: { algorithm: 'PBKDF2_SHA256', rounds: 100000, }, } ) .then((results) => { results.errors.forEach((indexedError) => { console.log(`Error importing user ${indexedError.index}`); }); }) .catch((error) => { console.log('Error importing users :', error); });
Java
try { List<ImportUserRecord> users = Collections.singletonList(ImportUserRecord.builder() .setUid("some-uid") .setEmail("user@example.com") .setPasswordHash("password-hash".getBytes()) .setPasswordSalt("salt".getBytes()) .build()); UserImportOptions options = UserImportOptions.withHash( Pbkdf2Sha256.builder() .setRounds(100000) .build()); UserImportResult result = FirebaseAuth.getInstance().importUsers(users, options); for (ErrorInfo indexedError : result.getErrors()) { System.out.println("Failed to import user: " + indexedError.getReason()); } } catch (FirebaseAuthException e) { System.out.println("Error importing users: " + e.getMessage()); }
Python
users = [ auth.ImportUserRecord( uid='some-uid', email='user@example.com', password_hash=b'password_hash', password_salt=b'salt' ), ] hash_alg = auth.UserImportHash.pbkdf2_sha256(rounds=100000) try: result = auth.import_users(users, hash_alg=hash_alg) for err in result.errors: print('Failed to import user:', err.reason) except exceptions.FirebaseError as error: print('Error importing users:', error)
Go
users := []*auth.UserToImport{ (&auth.UserToImport{}). UID("some-uid"). Email("user@example.com"). PasswordHash([]byte("password-hash")). PasswordSalt([]byte("salt")), } h := hash.PBKDF2SHA256{ Rounds: 100000, } result, err := client.ImportUsers(ctx, users, auth.WithHash(h)) if err != nil { log.Fatalln("Error importing users", err) } for _, e := range result.Errors { log.Println("Failed to import user", e.Reason) }
匯入使用者,並使用標準 SCRYPT 雜湊密碼
Admin SDK 支援標準 SCRYPT
演算法,以及內部修改版本。以下是必要參數:
memoryCost
:散列演算法的 CPU/記憶體成本。parallelization
:雜湊演算法的平行處理。blockSize
:雜湊演算法的區塊大小 (通常為 8)。derivedKeyLength
:雜湊演算法的衍生金鑰長度。
Node.js
getAuth() .importUsers( [ { uid: 'some-uid', email: 'user@example.com', // Must be provided in a byte buffer. passwordHash: Buffer.from('password-hash'), // Must be provided in a byte buffer. passwordSalt: Buffer.from('salt'), }, ], { hash: { algorithm: 'STANDARD_SCRYPT', memoryCost: 1024, parallelization: 16, blockSize: 8, derivedKeyLength: 64, }, } ) .then((results) => { results.errors.forEach((indexedError) => { console.log(`Error importing user ${indexedError.index}`); }); }) .catch((error) => { console.log('Error importing users :', error); });
Java
try { List<ImportUserRecord> users = Collections.singletonList(ImportUserRecord.builder() .setUid("some-uid") .setEmail("user@example.com") .setPasswordHash("password-hash".getBytes()) .setPasswordSalt("salt".getBytes()) .build()); UserImportOptions options = UserImportOptions.withHash( StandardScrypt.builder() .setMemoryCost(1024) .setParallelization(16) .setBlockSize(8) .setDerivedKeyLength(64) .build()); UserImportResult result = FirebaseAuth.getInstance().importUsers(users, options); for (ErrorInfo indexedError : result.getErrors()) { System.out.println("Failed to import user: " + indexedError.getReason()); } } catch (FirebaseAuthException e) { System.out.println("Error importing users: " + e.getMessage()); }
Python
users = [ auth.ImportUserRecord( uid='some-uid', email='user@example.com', password_hash=b'password_hash', password_salt=b'salt' ), ] hash_alg = auth.UserImportHash.standard_scrypt( memory_cost=1024, parallelization=16, block_size=8, derived_key_length=64) try: result = auth.import_users(users, hash_alg=hash_alg) for err in result.errors: print('Failed to import user:', err.reason) except exceptions.FirebaseError as error: print('Error importing users:', error)
Go
users := []*auth.UserToImport{ (&auth.UserToImport{}). UID("some-uid"). Email("user@example.com"). PasswordHash([]byte("password-hash")). PasswordSalt([]byte("salt")), } h := hash.StandardScrypt{ MemoryCost: 1024, Parallelization: 16, BlockSize: 8, DerivedKeyLength: 64, } result, err := client.ImportUsers(ctx, users, auth.WithHash(h)) if err != nil { log.Fatalln("Error importing users", err) } for _, e := range result.Errors { log.Println("Failed to import user", e.Reason) }
使用 Identity Platform SCRYPT 雜湊密碼匯入使用者
Identity Platform 和 Firebase 使用 SCRYPT
演算法的修改版本。如果您需要將使用者從 Firebase 遷移至 Identity Platform,或是從一個 Identity Platform 專案遷移至另一個專案,就需要內部雜湊參數。
如要存取 Identity Platform 中的參數,請按照下列步驟操作:
- 在 Google Cloud 控制台中開啟「使用者」頁面。
- 按一下「匯入使用者」。密碼雜湊參數會隨即顯示。
如要存取 Firebase 中的參數,請按照下列步驟操作:
在 Firebase 控制台中開啟「使用者」分頁。
在使用者清單的右上角下拉式選單中,選取「密碼雜湊參數」。密碼雜湊參數會隨即顯示。
您需要在程式碼中提供下列資訊:
key
:通常會在base64
編碼中提供的簽署者金鑰。saltSeparator
:(選用) 通常在base64
編碼中提供的鹽分隔符。rounds
:用於雜湊密碼的輪次數。memoryCost
:這個演算法所需的記憶體成本。
Node.js
getAuth() .importUsers( [ { uid: 'some-uid', email: 'user@example.com', // Must be provided in a byte buffer. passwordHash: Buffer.from('base64-password-hash', 'base64'), // Must be provided in a byte buffer. passwordSalt: Buffer.from('base64-salt', 'base64'), }, ], { hash: { algorithm: 'SCRYPT', // All the parameters below can be obtained from the Firebase Console's users section. // Must be provided in a byte buffer. key: Buffer.from('base64-secret', 'base64'), saltSeparator: Buffer.from('base64SaltSeparator', 'base64'), rounds: 8, memoryCost: 14, }, } ) .then((results) => { results.errors.forEach((indexedError) => { console.log(`Error importing user ${indexedError.index}`); }); }) .catch((error) => { console.log('Error importing users :', error); });
Java
try { List<ImportUserRecord> users = Collections.singletonList(ImportUserRecord.builder() .setUid("some-uid") .setEmail("user@example.com") .setPasswordHash(BaseEncoding.base64().decode("password-hash")) .setPasswordSalt(BaseEncoding.base64().decode("salt")) .build()); UserImportOptions options = UserImportOptions.withHash( Scrypt.builder() // All the parameters below can be obtained from the Firebase Console's "Users" // section. Base64 encoded parameters must be decoded into raw bytes. .setKey(BaseEncoding.base64().decode("base64-secret")) .setSaltSeparator(BaseEncoding.base64().decode("base64-salt-separator")) .setRounds(8) .setMemoryCost(14) .build()); UserImportResult result = FirebaseAuth.getInstance().importUsers(users, options); for (ErrorInfo indexedError : result.getErrors()) { System.out.println("Failed to import user: " + indexedError.getReason()); } } catch (FirebaseAuthException e) { System.out.println("Error importing users: " + e.getMessage()); }
Python
users = [ auth.ImportUserRecord( uid='some-uid', email='user@example.com', password_hash=base64.urlsafe_b64decode('password_hash'), password_salt=base64.urlsafe_b64decode('salt') ), ] # All the parameters below can be obtained from the Firebase Console's "Users" # section. Base64 encoded parameters must be decoded into raw bytes. hash_alg = auth.UserImportHash.scrypt( key=base64.b64decode('base64_secret'), salt_separator=base64.b64decode('base64_salt_separator'), rounds=8, memory_cost=14 ) try: result = auth.import_users(users, hash_alg=hash_alg) for err in result.errors: print('Failed to import user:', err.reason) except exceptions.FirebaseError as error: print('Error importing users:', error)
Go
b64URLdecode := func(s string) []byte { b, err := base64.URLEncoding.DecodeString(s) if err != nil { log.Fatalln("Failed to decode string", err) } return b } b64Stddecode := func(s string) []byte { b, err := base64.StdEncoding.DecodeString(s) if err != nil { log.Fatalln("Failed to decode string", err) } return b } // Users retrieved from Firebase Auth's backend need to be base64URL decoded users := []*auth.UserToImport{ (&auth.UserToImport{}). UID("some-uid"). Email("user@example.com"). PasswordHash(b64URLdecode("password-hash")). PasswordSalt(b64URLdecode("salt")), } // All the parameters below can be obtained from the Firebase Console's "Users" // section. Base64 encoded parameters must be decoded into raw bytes. h := hash.Scrypt{ Key: b64Stddecode("base64-secret"), SaltSeparator: b64Stddecode("base64-salt-separator"), Rounds: 8, MemoryCost: 14, } result, err := client.ImportUsers(ctx, users, auth.WithHash(h)) if err != nil { log.Fatalln("Error importing users", err) } for _, e := range result.Errors { log.Println("Failed to import user", e.Reason) }
匯入使用者,並使用 BCRYPT 雜湊密碼
BCRYPT
雜湊密碼不需要額外參數或密碼鹽。
Node.js
getAuth() .importUsers( [ { uid: 'some-uid', email: 'user@example.com', // Must be provided in a byte buffer. passwordHash: Buffer.from('password-hash'), }, ], { hash: { algorithm: 'BCRYPT', }, } ) .then((results) => { results.errors.forEach((indexedError) => { console.log(`Error importing user ${indexedError.index}`); }); }) .catch((error) => { console.log('Error importing users :', error); });
Java
try { List<ImportUserRecord> users = Collections.singletonList(ImportUserRecord.builder() .setUid("some-uid") .setEmail("user@example.com") .setPasswordHash("password-hash".getBytes()) .setPasswordSalt("salt".getBytes()) .build()); UserImportOptions options = UserImportOptions.withHash(Bcrypt.getInstance()); UserImportResult result = FirebaseAuth.getInstance().importUsers(users, options); for (ErrorInfo indexedError : result.getErrors()) { System.out.println("Failed to import user: " + indexedError.getReason()); } } catch (FirebaseAuthException e) { System.out.println("Error importing users: " + e.getMessage()); }
Python
users = [ auth.ImportUserRecord( uid='some-uid', email='user@example.com', password_hash=b'password_hash', password_salt=b'salt' ), ] hash_alg = auth.UserImportHash.bcrypt() try: result = auth.import_users(users, hash_alg=hash_alg) for err in result.errors: print('Failed to import user:', err.reason) except exceptions.FirebaseError as error: print('Error importing users:', error)
Go
users := []*auth.UserToImport{ (&auth.UserToImport{}). UID("some-uid"). Email("user@example.com"). PasswordHash([]byte("password-hash")). PasswordSalt([]byte("salt")), } h := hash.Bcrypt{} result, err := client.ImportUsers(ctx, users, auth.WithHash(h)) if err != nil { log.Fatalln("Error importing users", err) } for _, e := range result.Errors { log.Println("Failed to import user", e.Reason) }
匯入不含密碼的使用者
如果使用者透過 OAuth、SAML 或 OIDC 向外部識別資訊提供者進行驗證,您就無法直接存取使用者的密碼。
Node.js
getAuth() .importUsers([ { uid: 'some-uid', displayName: 'John Doe', email: 'johndoe@gmail.com', photoURL: 'http://www.example.com/12345678/photo.png', emailVerified: true, phoneNumber: '+11234567890', // Set this user as admin. customClaims: { admin: true }, // User with Google provider. providerData: [ { uid: 'google-uid', email: 'johndoe@gmail.com', displayName: 'John Doe', photoURL: 'http://www.example.com/12345678/photo.png', providerId: 'google.com', }, ], }, ]) .then((results) => { results.errors.forEach((indexedError) => { console.log(`Error importing user ${indexedError.index}`); }); }) .catch((error) => { console.log('Error importing users :', error); });
Java
try { List<ImportUserRecord> users = Collections.singletonList(ImportUserRecord.builder() .setUid("some-uid") .setDisplayName("John Doe") .setEmail("johndoe@gmail.com") .setPhotoUrl("http://www.example.com/12345678/photo.png") .setEmailVerified(true) .setPhoneNumber("+11234567890") .putCustomClaim("admin", true) // set this user as admin .addUserProvider(UserProvider.builder() // user with Google provider .setUid("google-uid") .setEmail("johndoe@gmail.com") .setDisplayName("John Doe") .setPhotoUrl("http://www.example.com/12345678/photo.png") .setProviderId("google.com") .build()) .build()); UserImportResult result = FirebaseAuth.getInstance().importUsers(users); for (ErrorInfo indexedError : result.getErrors()) { System.out.println("Failed to import user: " + indexedError.getReason()); } } catch (FirebaseAuthException e) { System.out.println("Error importing users: " + e.getMessage()); }
Python
users = [ auth.ImportUserRecord( uid='some-uid', display_name='John Doe', email='johndoe@gmail.com', photo_url='http://www.example.com/12345678/photo.png', email_verified=True, phone_number='+11234567890', custom_claims={'admin': True}, # set this user as admin provider_data=[ # user with Google provider auth.UserProvider( uid='google-uid', email='johndoe@gmail.com', display_name='John Doe', photo_url='http://www.example.com/12345678/photo.png', provider_id='google.com' ) ], ), ] try: result = auth.import_users(users) for err in result.errors: print('Failed to import user:', err.reason) except exceptions.FirebaseError as error: print('Error importing users:', error)
Go
users := []*auth.UserToImport{ (&auth.UserToImport{}). UID("some-uid"). DisplayName("John Doe"). Email("johndoe@gmail.com"). PhotoURL("http://www.example.com/12345678/photo.png"). EmailVerified(true). PhoneNumber("+11234567890"). CustomClaims(map[string]interface{}{"admin": true}). // set this user as admin ProviderData([]*auth.UserProvider{ // user with Google provider { UID: "google-uid", Email: "johndoe@gmail.com", DisplayName: "John Doe", PhotoURL: "http://www.example.com/12345678/photo.png", ProviderID: "google.com", }, }), } result, err := client.ImportUsers(ctx, users) if err != nil { log.Fatalln("Error importing users", err) } for _, e := range result.Errors { log.Println("Failed to import user", e.Reason) }
請注意,providerId
會在 Identity Platform 中用來描述特定提供者。對於 OIDC 和 SAML 供應器,這項資訊會在建立時定義。對於其他提供者,這個值則具有預先定義的值 (例如 google.com
或 facebook.com
)。您可以從已登入使用者的宣告中擷取 providerId
。
匯入使用多重驗證要素的使用者
如果現有使用者已註冊用於多重要素驗證的電話號碼,您可以將這些電話號碼匯入 Identity Platform。
使用多重驗證功能的使用者必須擁有已驗證的電子郵件地址和支援的第一因素。每位使用者最多可設定 5 個次要驗證因素。
您可以匯入下列多重因素屬性:
屬性 | 類型 | 說明 |
---|---|---|
uid |
字串 |
已註冊的第二重驗證專屬 ID (選填)。如未提供,系統會自動產生隨機 uid 。 |
phoneNumber |
字串 | 已註冊的第二重驗證電話號碼。這個電話號碼必須符合 E.164 標準。 |
displayName |
字串 | 選用顯示名稱。如果使用者註冊了多個第二層驗證方法,這個方法就很實用。 |
enrollmentTime |
字串 | 註冊第二因素的日期,格式為世界標準時間字串。如未提供,系統會使用目前的日期。 |
factorId |
字串 |
次要驗證方式類型 ID。這個值一律會設為 phone 。 |
以下範例說明如何匯入多因素使用者:
Node.js
// Up to 1000 users can be imported at once.
const userImportRecords = [
{
uid: 'uid1',
email: 'user1@example.com',
emailVerified: true,
passwordHash: Buffer.from('passwordHash1'),
passwordSalt: Buffer.from('salt1'),
multiFactor: {
enrolledFactors: [
{
// Enrolled second factor uid is optional.
uid: 'uid1-unique-mfa-identifier1',
displayName: 'Personal phone',
phoneNumber: '+16505551234',
factorId: 'phone',
// Enrollment time is also optional.
enrollmentTime: 'Fri, 22 Sep 2017 01:49:58 GMT',
},
],
},
},
{
// User with multiple second factors.
uid: 'uid2',
email: 'user2@example.com',
emailVerified: true,
passwordHash: Buffer.from('passwordHash2'),
passwordSalt: Buffer.from('salt2'),
multiFactor: {
enrolledFactors: [
{
displayName: 'Work phone',
phoneNumber: '+16505550007',
factorId: 'phone',
},
{
displayName: 'Backup phone',
phoneNumber: '+16505550008',
factorId: 'phone',
},
],
},
},
{
// User with no second factor.
uid: 'uid3',
email: 'user3@example.com',
passwordHash: Buffer.from('passwordHash3'),
passwordSalt: Buffer.from('salt3'),
},
...
];
const userImportOptions = {
hash: {
algorithm: 'HMAC_SHA256',
key: Buffer.from('secretKey'),
},
};
admin.auth().importUsers(userImportRecords, userImportOptions)
.then((userImportResult) => {
// The number of successful imports is determined via: userImportResult.successCount.
// The number of failed imports is determined via: userImportResult.failureCount.
// To get the error details.
userImportResult.forEach(function(indexedError) {
// The corresponding user that failed to upload.
console.log(userImportRecords[indexedError.index].uid +' failed to import',
indexedError.error);
});
})
.catch((error) => {
// Some unrecoverable error occurred that prevented the operation from running.
});
使用指令列介面
如果使用者資料是以 JSON 或 CSV 格式儲存,您可以使用 Firebase 指令列工具匯入資料:
firebase auth:import account_file \
--hash-algo=[HASH-ALGORITHM] \
--hash-key=[KEY] \
--salt-separator=[SALT-SEPARATOR] \
--rounds=[ROUNDS] \
--mem-cost=[MEM-COST] \
--parallelization=[PARALLELIZATION] \
--block-size=[BLOCK-SIZE] \
--dk-len=[DK-LEN] \
--hash-input-order=[HASH-INPUT-ORDER] \
如需每個參數的完整說明,請參閱指令列介面參考說明文件。
後續步驟
- 請參閱匯入使用者時發生的常見錯誤代碼。
- 瞭解如何使用 Identity Platform 在 App Engine 上驗證使用者。
- 瞭解如何使用 Identity Platform 設定自訂憑證附加資訊。
- 請參閱 Admin Auth API 相關說明。