繁体   English   中英

注销后实时数据库onDisconnect未执行

[英]Real-time database onDisconnect not executing after logging out

我已经实现了 Firebase 实时数据库存在系统,如官方 Firebase 文档中所示。 我想让数据库安全,以便登录用户只能写入他们自己在数据库中的存在条目。 因此,在登录时,用户写入参考路径/auth/{authId}/connections并同时设置onDisconnect以删除该值。

这是 Android 应用程序的代码,它在 rtdb 中设置存在:

getFirebaseDatabase().goOnline();
DatabaseReference.goOnline();

// Since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
final FirebaseDatabase database = getFirebaseDatabase();
final DatabaseReference myConnectionsRef = database.getReference("/auth/" + getFirebaseAuth().getUid() + "/connections");

// Stores the timestamp of my last disconnect (the last time I was seen online)
final DatabaseReference lastOnlineRef = database.getReference("/auth/" + getFirebaseAuth().getUid() + "/lastOnline");

connectedRef = database.getReference(".info/connected");
presenceChangeListener = connectedRef.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot snapshot) {
        boolean connected = snapshot.getValue(Boolean.class);
        if (connected) {
            DatabaseReference con = myConnectionsRef.push();

            // When this device disconnects, remove it
            con.onDisconnect().removeValue()
                    .addOnSuccessListener(new OnSuccessListener<Void>() {
                        @Override
                        public void onSuccess(Void aVoid) {
                            // Add this device to my connections list
                            // this value could contain info about the device or a timestamp too
                            con.setValue("ANDROID");
                        }
                    })
                    .addOnFailureListener(new OnFailureListener() {
                        @Override
                        public void onFailure(@NonNull Exception e) {
                            Log.d(TAG, "### Failed to set onDisconnect ###");
                            e.printStackTrace();
                        }
                    });

            // When I disconnect, update the last time I was seen online
            lastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP);
        }
    }

    @Override
    public void onCancelled(DatabaseError error) {
        Log.w(TAG, "Listener was cancelled at .info/connected");
    }
});

我遇到的问题是,如果用户注销,除非我先手动断开与 rtdb 的连接,否则onDisconnect不会执行。 我假设在实时数据库上运行的代码被拒绝,因为身份验证不再有效。

//If I don't go offline first the record in rtdb will not be removed.
DatabaseReference.goOffline();

AuthUI.getInstance().signOut(this)
.addOnCompleteListener(new OnCompleteListener<Void>() {
    public void onComplete(@NonNull Task<Void> task) {
        // user is now signed out
        Log.d(TAG, "Logged out");
        application.clearData();
        DatabaseReference.goOffline(); //This doesn't cause a presence update here
        finish();
    }
});

以上是我正在使用的解决方法,首先告诉数据库goOffline然后注销。 如果用户曾经通过另一种方式注销(web 应用程序正在查看是否有多个选项卡正在使用该应用程序并且一个已注销),则用户将留下未删除的连接。

如果我在注销之前没有调用goOffline() ,即使我强制关闭应用程序,rtdb 中的连接也不会被删除。
我还验证了如果我将 rtdb 规则更改为".write": "true" <-这不好,我可以让一切正常工作。 这告诉我,当用户注销身份验证时, onDisconnect运行时拒绝了权限。

我希望我的实时规则是这样的。

{
  "rules": {
    "auth": {
      "$uid": {
        ".read": "auth != null && auth.uid == $uid",
        ".write": "auth != null && auth.uid == $uid"
      }
    }
  }
}

我希望onDisconnect在设置onDisconnect时仍然能够使用用户的身份验证执行。

附加onDisconnect()处理程序时,您将在Firebase服务器上注册延迟的写入。 在附加处理程序和触发处理程序时,都会检查是否允许该写入。 并且由于触发写操作时您的用户已注销,因此规则会拒绝它。 没有配置选项可以更改此行为,因此您必须提出另一种方法。

因此,因为1.)根据RTDB的规则评估onDisconnect()执行,2.)设置onDisconnect()的用户可能会丢失身份验证,并且3.)我想使状态系统对我的安全经过身份验证的用户...我想出了以下解决方案:

首先,在包含用户的authId和UUID的路径下,将状态项写入RTDB,以使位置“可用”。
"/presence/" + {auth-uid} + "/connections/" + {UUID}
并设置一个.onDisconnect()以删除存储在无法猜测的位置的该值。

然后,设置RTDB规则以执行以下操作:

  • 不允许任何读取状态数据
  • 允许用户仅在其auth目录下添加/修改数据
  • 允许任何用户删除记录(他们将需要知道不可猜测的路径)
    "presence": {
      ".read": "false",
      ".write": "false",

      "$auth_id": {
        "connections": {
          "$uuid": {
            ".write": "(newData.exists() && $auth_id === auth.uid) || !newData.exists()"
          }
        }
      }
    }

最后,在RTDB上设置触发函数以读取.ref('/presence/{authid}')位置,并将用户的状态推送到另一个用户可访问的位置(我将其推送到Firestore数据库)。 另外,如果用户从“联机”更改为“脱机”,则将lastOnline时间戳更新为当前时间。

考虑到我对拥有可靠和安全的状态系统的要求,这似乎是最好的解决方案。 我希望这对其他人有帮助。

这是一个老问题,但它让我想到了一个可能的解决方案,并带来了以下...

只要您对应用程序有控制/意识,就使用 onDisconnect.setValue/removeValue

在注销之前使用 onDisconnect.cancel 并删除数据

我采用了@FrankvanPuffelen 代码并对其进行了修改,但没有对其进行测试......

//getFirebaseDatabase().goOnline();
//DatabaseReference.goOnline();

// Since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
final FirebaseDatabase database = getFirebaseDatabase();
final DatabaseReference myConnectionsRef = database.getReference("/auth/" + getFirebaseAuth().getUid() + "/connections");

// Stores the timestamp of my last disconnect (the last time I was seen online)
final DatabaseReference lastOnlineRef = database.getReference("/auth/" + getFirebaseAuth().getUid() + "/lastOnline");

connectedRef = database.getReference(".info/connected");
presenceChangeListener = connectedRef.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot snapshot) {
        boolean connected = snapshot.getValue(Boolean.class);
        if (connected) {
            // simple solution to reuse the old unique key-name otherwise current solution is like performing new registration of a new client over and over on the same client. we should use the old unique key-name until logout is performed
            String keyName = SharedPrefUtil.INSTANCE.getFirebaseConnectionKeyName(context);

             DatabaseReference con;
             if (TextUtils.isEmpty(keyName)) {
                     con = myConnectionsRef.push();
                     SharedPrefUtil.INSTANCE.setFirebaseConnectionKeyName(context.getApplicationContext(), con.getKey());
                 }else{
                      con = myConnectionsRef.child(keyName);
                 }

            // When this device disconnects, remove it
            con.onDisconnect().removeValue()
                    .addOnSuccessListener(new OnSuccessListener<Void>() {
                        @Override
                        public void onSuccess(Void aVoid) {
                            // Add this device to my connections list
                            // this value could contain info about the device or a timestamp too
                            con.setValue("ANDROID");
                        }
                    })
                    .addOnFailureListener(new OnFailureListener() {
                        @Override
                        public void onFailure(@NonNull Exception e) {
                            Log.d(TAG, "### Failed to set onDisconnect ###");
                            e.printStackTrace();
                        }
                    });

            // When I disconnect, update the last time I was seen online
            lastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP);
        }
    }

    @Override
    public void onCancelled(DatabaseError error) {
        Log.w(TAG, "Listener was cancelled at .info/connected");
    }
});

在注销方法中我们需要取消断开连接

String keyName = SharedPrefUtil.INSTANCE.getFirebaseConnectionKeyName(context);
if (!TextUtils.isEmpty(keyName)) {
    final FirebaseDatabase database = getFirebaseDatabase();
    final DatabaseReference myConnectionsRef = database.getReference("/auth/" + getFirebaseAuth().getUid() + "/connections/" + keyName);

    // Stores the timestamp of my last disconnect (the last time I was seen online)
    final DatabaseReference lastOnlineRef = database.getReference("/auth/" + getFirebaseAuth().getUid() + "/lastOnline");
    // This client/user doesn't need the disconnect functionality 
    myConnectionsRef.onDisconnect().cancel();
    // now we are on our own so we need to remove the key-name from the rmdb
    myConnectionsRef.setValue(null);
    // remove the key-name from the preferences so we will create a new one in the next login session
    SharedPrefUtil.INSTANCE.removeFirebaseConnectionKeyName(context);
    // we will not forget to disconnect last time updates 
    lastOnlineRef.onDisconnect().cancel()
}
AuthUI.getInstance().signOut(this)

我没有测试它,它不会运行,因为它缺少 SharedPrefUtil 实现

暂无
暂无

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

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