[英]How to prevent simultaneous logins of the same user with Firebase?
我希望新的 session 從本質上“注銷”任何以前的 session。例如,當您在一台計算機上使用經過身份驗證的 session 時,在另一台計算機上啟動新的 session 並在我們的應用程序上使用 firebase 進行身份驗證將注銷第一台計算機上的另一個 session。
我還沒有找到任何允許我“遠程”注銷 session 的方法。 我知道我可以從 session 中使用 unauth() 和 goOffline()。但是我如何從同一用戶的不同認證 session 中做到這一點?
謝謝您的幫助!
背景資料:
總體思路是,您希望在 Firebase 中創建一些元數據,以告訴您用戶從多少個位置登錄。 然后您可以使用此信息限制他們的訪問。
為此,您需要生成自己的令牌(以便信息可用於您的安全規則)。
1) 生成令牌
使用自定義登錄生成您自己的令牌。 每個令牌都應包含客戶端的唯一 ID(IP 地址?UUID?)
var FirebaseTokenGenerator = require("firebase-token-generator");
var tokenGenerator = new FirebaseTokenGenerator(YOUR_FIREBASE_SECRET);
var token = tokenGenerator.createToken({ id: USER_ID, location_id: IP_ADDRESS });
2)使用presence來存儲用戶的location_id
查看管理存在入門了解詳細信息:
var fb = new Firebase(URL);
// after getting auth token back from your server
var parts = deconstructJWT(token);
var ref = fb.child('logged_in_users/'+token.id);
// store the user's location id
ref.set(token.location_id);
// remove location id when user logs out
ref.onDisconnect().remove();
// Helper function to extract claims from a JWT. Does *not* verify the
// validity of the token.
// credits: https://github.com/firebase/angularFire/blob/e8c1d33f34ee5461c0bcd01fc316bcf0649deec6/angularfire.js
function deconstructJWT(token) {
var segments = token.split(".");
if (!segments instanceof Array || segments.length !== 3) {
throw new Error("Invalid JWT");
}
var claims = segments[1];
if (window.atob) {
return JSON.parse(decodeURIComponent(escape(window.atob(claims))));
}
return token;
}
3) 添加安全規則
在安全規則中,強制只有當前唯一位置可以讀取數據
{
"some_restricted_path": {
".read": "root.child('logged_in_users/'+auth.id).val() === auth.location_id"
}
}
4) 控制對logged_in_users 的寫訪問
您需要設置一些系統來控制對logged_in_users 的寫訪問。 顯然,用戶應該只能寫入他們自己的記錄。 如果您希望第一次登錄嘗試始終獲勝,請使用".write": "!data.exists()"
在值存在時阻止寫入(直到他們注銷".write": "!data.exists()"
但是,您可以通過讓最后一次登錄獲勝來大大簡化,在這種情況下,它會覆蓋舊的位置值,並且之前的登錄將失效並且無法讀取。
5)這不是控制並發數的解決方案
您不能使用它來防止多個並發到您的 Firebase。 有關完成此操作的更多數據,請參閱 goOffline() 和 goOnline()(或獲得付費計划,這樣您就沒有連接的硬性上限)。
TL; 博士
我們也遇到過這個話題。 盡管該線程已經過時並且它並沒有完全勾勒出我們想要實現的完全相同的願望,但我們可以吸收@kato回答的一些一般概念。 概念大致保持不變,但這個線程絕對值得一個更新的答案。
注意事項:在您立即閱讀此解釋之前,請注意您可能會發現它有點斷章取意,因為它沒有完全涵蓋原始的 SO 問題。 事實上,組裝一個系統以防止同時進行多個會話是一種不同的心理模型。 更准確地說,是我們的心智模型適合我們的場景。 :)
例如,當您在一台計算機上進行經過身份驗證的會話時,在另一台計算機上啟動一個新會話並在我們的應用程序上使用 firebase 進行身份驗證將注銷第一台計算機上的另一個會話。
保持這種類型的“同時登錄預防”意味着 1) 每個客戶端的活動會話應該被區分,即使它來自同一設備 2) 客戶端應該從 AFAICT Firebase 無法登錄的特定設備注銷. FWIW 您可以撤銷令牌以明確使指定用戶的所有刷新令牌過期,因此,它會提示再次登錄,但這樣做的缺點是它會破壞所有現有會話(即使是剛剛使用的會話)活性)。
這些“開銷”導致以稍微不同的方式解決問題。 它的不同之處在於 1) 無需跟蹤具體設備 2) 客戶端以編程方式注銷,而無需破壞其任何活動會話以增強用戶體驗。
利用Firebase Presence來完成跟蹤客戶端連接狀態變化的繁重工作(即使連接因某些奇怪的原因終止),但這里有一個問題:它本身並不隨 Firestore 一起提供。 請參閱連接到 Cloud Firestore以保持數據庫同步。 還值得注意的是,與他們的示例相比,我們沒有設置對特殊.info/connected
路徑的引用。 相反,我們利用onAuthStateChanged()
觀察者來響應身份驗證狀態的變化。
const getUserRef = userId => firebase.database().ref(`/users/${userId}`);
firebase.auth().onAuthStateChanged(user => {
if (user) {
const userRef = getUserRef(user.uid);
return userRef
.onDisconnect()
.set({
is_online: false,
last_seen: firebase.database.ServerValue.TIMESTAMP
})
.then(() =>
// This sets the flag to true once `onDisconnect()` has been attached to the user's ref.
userRef.set({
is_online: true,
last_seen: firebase.database.ServerValue.TIMESTAMP
});
);
}
});
正確設置onDisconnect()
后,您必須確保用戶的會話是否嘗試與另一個活動會話一起啟動登錄,為此,將請求轉發到數據庫並檢查相應的標志。 因此,由於這種額外的往返,識別多個會話會比平時花費更多的時間,因此應相應地調整 UI。
const ensureUserSession = userId => {
const userRef = getUserRef(userId);
return userRef.once("value").then(snapshot => {
if (!snapshot.exists()) {
// If the user entry does not exist, create it and return with the promise.
return userRef.set({
last_seen: firebase.database.ServerValue.TIMESTAMP
});
}
const user = snapshot.data();
if (user.is_online) {
// If the user is already signed in, throw a custom error to differentiate from other potential errors.
throw new SessionAlreadyExists(...);
}
// Otherwise, return with a resolved promise to permit the sign-in.
return Promise.resolve();
});
};
將這兩個片段組合在一起會產生https://pastebin.com/jWYu53Up 。
所以我剛剛遇到了這個問題,並相信我已經以比這里提到的任何方法更容易的方式解決了它,所以我想我會讓你知道我最終做了什么。 首先,我使用的是“實時數據庫”,所以這個例子使用了它。 我有一個“用戶”表,每個用戶都有一個條目。 除其他事項外,這些條目包括“lastLogin”時間戳。 當用戶登錄時,我更新該表中的字段並輸入 lastLogin 時間戳。 是的,我知道用戶元數據中有一個登錄時間戳,但你馬上就會明白我為什么這樣做。
用戶登錄后,我只需在該表上設置一個“手表”。 例如
var firstTime = true;
var ref = firebase.database.ref(PATH_TO_TABLE_OR_FIELD);
ref.on('value',(snapshot)=> {
if(!firstTime) {
// CHECK THE SNAPSHOT FOR THE LOGIN FIELD. SINCE IT HAS CHANGED THAT MEANS ANOTHER USER LOGGED IN WITH SAME USERNAME
} else {
firstTime = false;
}
});
“firstTime”值是因為第一次設置“on”時它會立即被調用。 因此,我們不想將其視為正在更改的值。 之后,只要指示表中的值發生更改,就會調用它。
檢測多個會話的挑戰在於檢測哪些會話是合法的。 您不想阻止人們在移動+桌面上使用您的產品,或者在他們的移動連接發生變化時阻止他們 IP。
您可能希望阻止人們共享密碼而不是阻止您的高級用戶。
您需要跟蹤的不僅僅是 IP 地址。 考慮使用 cookies 或電話/電子郵件驗證。 但他們也有自己的挑戰。
您可能想嘗試像Upollo這樣的服務,它有一個 API 來解決這個問題。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.