我希望新会话基本上“退出”任何以前的会话。例如,当您在一台计算机上进行经过身份验证的会话时,在另一台计算机上启动新会话并在我们的应用程序上使用firebase进行身份验证将在第一台计算机上注销另一个会话。
我无法找到任何允许我“远程”退出会话的方法。我知道我可以在会话中使用unauth()和goOffline()。但是,如何从同一用户的其他经过身份验证的会话中执行此操作?
感谢您的帮助!
背景信息:
答案 0 :(得分:9)
一般的想法是,您希望在Firebase中创建一些元数据,告诉您用户登录的位置数。然后,您可以使用此信息限制其访问权限。
为此,您需要生成自己的令牌(以便您的安全规则可以使用这些信息)。
1)生成令牌
使用custom login生成自己的令牌。每个令牌应包含客户端的唯一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)使用状态存储用户的location_id
查看managing presence引物了解详情:
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()"
但是,您可以通过允许上次登录获胜来大大简化,在这种情况下,它会覆盖旧的位置值,之前的登录将无效并且无法读取。
5)这不是控制同步数量的解决方案
您不能使用它来阻止Firebase的多个并发。有关完成此操作的更多数据,请参阅goOffline()和goOnline()(或获取付费计划,以便您无需连接上限)。
答案 1 :(得分:6)
TL; DR
我们也遇到了这个话题。尽管该线程已经过时,并且它并未完全概述我们想要实现的完全相同的愿望,但是我们可以吸收@kato的一些通用概念。概念大致相同,但此主题绝对值得更新。
抬头:立即阅读该说明,要意识到一个事实,您可能会发现它与上下文无关,因为它不能完全涵盖原始的SO问题。实际上,组装一个系统以防止同时进行多个会话是完全不同的思维模式。更准确地说,这是适合我们情况的心理模型。 :)
例如,当您在一台计算机上处于经过身份验证的会话中时,在另一台计算机上启动新会话并在我们的应用程序上使用firebase进行身份验证将注销第一台计算机上的另一会话。
维护这种“同时登录预防”类型意味着1)即使每个客户端的活动会话都来自同一设备,也应加以区分2)客户端应从AFAICT Firebase所在的特定设备中退出。能够。 FWIW,您可以revoke tokens明确使指定用户的所有刷新令牌都过期,因此,系统会提示您再次登录,但这样做的缺点是会破坏所有现有会话(甚至是一个会话)刚刚被激活)。
这些“开销”导致解决问题的方式略有不同。区别在于1)无需跟踪具体设备2)以编程方式将客户端注销,而不必破坏其任何活动会话以增强用户体验。
利用Firebase Presence通过繁重的工作来跟踪客户端的连接状态变化(即使由于某种奇怪的原因而终止了连接),但这是要注意的:它不是 Firestore自带。请参考Connecting to 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。