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

时间:2019-07-12 17:27:13

标签: android firebase firebase-realtime-database

我已经实现了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仍然能够通过用户的身份执行。

2 个答案:

答案 0 :(得分:0)

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

答案 1 :(得分:0)

因此,因为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时间戳更新为当前时间。

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