简体   繁体   English

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

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

I'd like for the new session to essentially "log out" of any previous session. For example, when you are in an authenticated session in one computer, starting a new session on another computer and authenticating with firebase on our app will log out the other session on the first computer.我希望新的 session 从本质上“注销”任何以前的 session。例如,当您在一台计算机上使用经过身份验证的 session 时,在另一台计算机上启动新的 session 并在我们的应用程序上使用 firebase 进行身份验证将注销第一台计算机上的另一个 session。

I haven't been able to find any method that allows me to log out of a session "remotely".我还没有找到任何允许我“远程”注销 session 的方法。 I know that I can unauth() and goOffline() from within a session. But how do I do it from a different authenticated session of the same user?我知道我可以从 session 中使用 unauth() 和 goOffline()。但是我如何从同一用户的不同认证 session 中做到这一点?

Thanks for the help!谢谢您的帮助!

Background Info:背景资料:

  1. I am using simple email/password login for firebase authentication我正在使用简单的电子邮件/密码登录进行 firebase 身份验证
  2. I don't have security rules setup yet, although this is in the works我还没有设置安全规则,虽然这是在进行中
  3. I'm using Javascript with Firebase我正在使用 Javascript 和 Firebase

The general idea is that you want to create some meta data in Firebase which tells you how many locations a user is logged in from.总体思路是,您希望在 Firebase 中创建一些元数据,以告诉您用户从多少个位置登录。 Then you can restrict their access using this information.然后您可以使用此信息限制他们的访问。

To do this, you'll need to generate your own tokens (so that the information is available to your security rules).为此,您需要生成自己的令牌(以便信息可用于您的安全规则)。

1) Generate a token 1) 生成令牌

Use custom login to generate your own tokens.使用自定义登录生成您自己的令牌。 Each token should contain a unique ID for the client (IP Address? UUID?)每个令牌都应包含客户端的唯一 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) Use presence to store the user's location_id 2)使用presence来存储用户的location_id

Check out the managing presence primer for details:查看管理存在入门了解详细信息:

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) Add security rules 3) 添加安全规则

In security rules, enforce that only the current unique location may read data在安全规则中,强制只有当前唯一位置可以读取数据

{
  "some_restricted_path": {
     ".read": "root.child('logged_in_users/'+auth.id).val() === auth.location_id"
  }
}

4) Control write access to logged_in_users 4) 控制对logged_in_users 的写访问

You'll want to set up some system of controlling write access to logged_in_users.您需要设置一些系统来控制对logged_in_users 的写访问。 Obviously a user should only be able to write to their own record.显然,用户应该只能写入他们自己的记录。 If you want the first login attempt to always win, then prevent write if a value exists (until they log out) by using ".write": "!data.exists()"如果您希望第一次登录尝试始终获胜,请使用".write": "!data.exists()"在值存在时阻止写入(直到他们注销".write": "!data.exists()"

However, you can greatly simplify by allowing the last login to win, in which case it overwrites the old location value and the previous logins will be invalidated and fail to read.但是,您可以通过让最后一次登录获胜来大大简化,在这种情况下,它会覆盖旧的位置值,并且之前的登录将失效并且无法读取。

5) This is not a solution to control the number of concurrents 5)这不是控制并发数的解决方案

You can't use this to prevent multiple concurrents to your Firebase.您不能使用它来防止多个并发到您的 Firebase。 See goOffline() and goOnline() for more data on accomplishing this (or get a paid plan so you have no hard cap on connections).有关完成此操作的更多数据,请参阅 goOffline() 和 goOnline()(或获得付费计划,这样您就没有连接的硬性上限)。

TL;DR TL; 博士

https://pastebin.com/jWYu53Up https://pastebin.com/jWYu53Up


We've come across this topic as well.我们也遇到过这个话题。 Despite the thread's obsolescence and the fact that it doesn't entirely outline our exact same desire we wanted to achieve, yet we could soak some of the general concepts of @kato 's answer up.尽管该线程已经过时并且它并没有完全勾勒出我们想要实现的完全相同的愿望,但我们可以吸收@kato回答的一些一般概念。 The conceptions have roughly remained the same but this thread definitely deserves a more up-to-date answer.概念大致保持不变,但这个线程绝对值得一个更新的答案。

Heads-up: before you read this explanation right off the bat, be aware of the fact you'll likely find it a bit out of context because it doesn't entirely cover the original SO question.注意事项:在您立即阅读此解释之前,请注意您可能会发现它有点断章取意,因为它没有完全涵盖原始的 SO 问题。 In fact, it's rather a different mental model to assemble a system to prevent multiple sessions at the same time.事实上,组装一个系统以防止同时进行多个会话是一种不同的心理模型。 To be more precise, it's our mental model that fits our scenario.更准确地说,是我们的心智模型适合我们的场景。 :) :)

For example, when you are in an authenticated session in one computer, starting a new session on another computer and authenticating with firebase on our app will log out the other session on the first computer.例如,当您在一台计算机上进行经过身份验证的会话时,在另一台计算机上启动一个新会话并在我们的应用程序上使用 firebase 进行身份验证将注销第一台计算机上的另一个会话。

Maintaining this type of "simultaneous-login-prevention" implies 1) the active sessions of each client should be differentiated even if it's from the same device 2) the client should be signed out from a particular device which AFAICT Firebase isn't capable of.保持这种类型的“同时登录预防”意味着 1) 每个客户端的活动会话应该被区分,即使它来自同一设备 2) 客户端应该从 AFAICT Firebase 无法登录的特定设备注销. FWIW you can revoke tokens to explicitly make ALL of the refresh tokens of the specified user expired and, therefore, it's prompted to sign in again but the downside of doing so is that it ruins ALL of the existing sessions(even the one that's just been activated). FWIW 您可以撤销令牌以明确使指定用户的所有刷新令牌过期,因此,它会提示再次登录,但这样做的缺点是它会破坏所有现有会话(即使是刚刚使用的会话)活性)。

These "overheads" led to approaching the problem in a slightly different manner.这些“开销”导致以稍微不同的方式解决问题。 It differs in that 1) there's no need to keep track of concrete devices 2) the client is signed out programmatically without unnecessarily destroying any of its active sessions to enhance the user experience.它的不同之处在于 1) 无需跟踪具体设备 2) 客户端以编程方式注销,而无需破坏其任何活动会话以增强用户体验。


Leverage Firebase Presence to pass the heavy-lifting of keeping track of connection status changes of clients(even if the connection is terminated for some weird reason) but here's the catch: it does not natively come with Firestore.利用Firebase Presence来完成跟踪客户端连接状态变化的繁重工作(即使连接因某些奇怪的原因终止),但这里有一个问题:它本身并不随 Firestore 一起提供。 Refer to Connecting to Cloud Firestore to keep the databases in sync.请参阅连接到 Cloud Firestore以保持数据库同步。 It's also worthwhile to note we don't set a reference to the special .info/connected path compared to their examples.还值得注意的是,与他们的示例相比,我们没有设置对特殊.info/connected路径的引用。 Instead, we take advantage of the onAuthStateChanged() observer to act in response to the authentication status changes.相反,我们利用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  
            });
         );
   }
});

After onDisconnect() has been correctly set, you'll have to ensure the user's session if it tries kicking off a sign-in alongside another active session, for which, forward a request to the database and check against the corresponding flag.正确设置onDisconnect()后,您必须确保用户的会话是否尝试与另一个活动会话一起启动登录,为此,将请求转发到数据库并检查相应的标志。 Consequently, recognizing multiple sessions takes up a bit more time than usual due to this additional round-trip, hence the UI should be adjusted accordingly.因此,由于这种额外的往返,识别多个会话会比平时花费更多的时间,因此应相应地调整 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();
   });
};

Combining these two snippets together results in https://pastebin.com/jWYu53Up .将这两个片段组合在一起会产生https://pastebin.com/jWYu53Up

So I just ran across this issue and believe I have solved it in a much easier way than anything mentioned here so I thought I'd let you know what I ended up doing.所以我刚刚遇到了这个问题,并相信我已经以比这里提到的任何方法更容易的方式解决了它,所以我想我会让你知道我最终做了什么。 Firstly I am using a "realtime database" so this example uses that.首先,我使用的是“实时数据库”,所以这个例子使用了它。 I have a "users" table with an entry for each user.我有一个“用户”表,每个用户都有一个条目。 The entries, among other things, include a "lastLogin" timestamp.除其他事项外,这些条目包括“lastLogin”时间戳。 When a user logs in I update the fields in that table and put in a lastLogin timestamp.当用户登录时,我更新该表中的字段并输入 lastLogin 时间戳。 Yes I know there is a login timestamp in the user metaData, but you will see why I did this in a moment.是的,我知道用户元数据中有一个登录时间戳,但你马上就会明白我为什么这样做。

Once a user logs in I then simply setup a "watch" on that table.用户登录后,我只需在该表上设置一个“手表”。 EG例如

   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;
      }
    });

The "firstTime" value is because the first time you setup an "on" it gets called immediately. “firstTime”值是因为第一次设置“on”时它会立即被调用。 So we don't want to count that as the value being changed.因此,我们不想将其视为正在更改的值。 After that, this will be called whenever a value in the indicated table changes.之后,只要指示表中的值发生更改,就会调用它。

The challenge with detecting multiple sessions is detecting which sessions are legitimate.检测多个会话的挑战在于检测哪些会话是合法的。 You don't want to stop people from using your product on mobile+desktop or stop them when their mobile connection changes IP.您不想阻止人们在移动+桌面上使用您的产品,或者在他们的移动连接发生变化时阻止他们 IP。

Your probably want to stop people sharing passwords not stop your power users.您可能希望阻止人们共享密码而不是阻止您的高级用户。

You'll need to keep track of more than just IP addresses.您需要跟踪的不仅仅是 IP 地址。 Consider using cookies or phone/email verifications.考虑使用 cookies 或电话/电子邮件验证。 But they have their own challenges.但他们也有自己的挑战。

You might want to try a service like Upollo which has an API to tackle this.您可能想尝试像Upollo这样的服务,它有一个 API 来解决这个问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何防止用户删除Firebase App - How to prevent User from deleting Firebase App 如何防止恶意用户将数据推送到 Firebase 中的文档中? - How to prevent a malicious user from pushing data into a document in Firebase? 防止根据 firebase 中的用户类型写入文档? - prevent writings to document depending on user type in firebase? 如何将用户名存储在包含 email 和同一用户发送的聊天消息的同一文档中? 在 flutter 和 firebase - How do I store the username in the same document that contains the email and chat messages sent by the same user? in flutter and firebase 防止firebase用户删除自己 - prevent firebase user from deleting himself 如何防止将相同数据的多个副本添加到我的 firebase 数据库 - How would I prevent adding multiple copies of the same data to my firebase database Firebase 防止多个用户获取同一个物品 - Firebase prevent multiple users from getting the same item 如何防止注册到Firebase项目? - How to prevent registration to Firebase project? 如何防止 Firebase 身份验证在首次登录时隐式创建新用户? - How to prevent Firebase Authentication creating a new user implicitly on first sign-in? 如何在 firebase 中检索用户名 - How to retrieve name of user in firebase
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM