繁体   English   中英

如何防止同一用户与 Firebase 同时登录?

[英]How to prevent simultaneous logins of the same user with Firebase?

我希望新的 session 从本质上“注销”任何以前的 session。例如,当您在一台计算机上使用经过身份验证的 session 时,在另一台计算机上启动新的 session 并在我们的应用程序上使用 firebase 进行身份验证将注销第一台计算机上的另一个 session。

我还没有找到任何允许我“远程”注销 session 的方法。 我知道我可以从 session 中使用 unauth() 和 goOffline()。但是我如何从同一用户的不同认证 session 中做到这一点?

谢谢您的帮助!

背景资料:

  1. 我正在使用简单的电子邮件/密码登录进行 firebase 身份验证
  2. 我还没有设置安全规则,虽然这是在进行中
  3. 我正在使用 Javascript 和 Firebase

总体思路是,您希望在 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; 博士

https://pastebin.com/jWYu53Up


我们也遇到过这个话题。 尽管该线程已经过时并且它并没有完全勾勒出我们想要实现的完全相同的愿望,但我们可以吸收@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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM