【问题标题】:firebase realtime database sharding rulesfirebase 实时数据库分片规则
【发布时间】:2020-02-16 00:26:14
【问题描述】:

2019 年 11 月 3 日更新:添加了 live minimal reproduction of the error。在 Chrome 中加载链接后,点击 ctrl+shift+i 并选择控制台以查看输出。我努力确保这完全符合我的原始项目代码正在做的事情;我们会看看是不是这样,嗯?分片的规则文件与下面的原始帖子相同。 source is available on GitHub

<!DOCTYPE html>
<html>
<body>
 <script src="https://www.gstatic.com/firebasejs/7.2.3/firebase-app.js"></script>
 <script src="https://www.gstatic.com/firebasejs/7.2.3/firebase-auth.js"></script>
 <script src="https://www.gstatic.com/firebasejs/7.2.3/firebase-database.js"></script>
 <script>
  const config={
   apiKey: "AIzaSyDLMc0GUf5n2nQa3aqpELQu7lziprQOGs8",
   authDomain: "shardautherror.firebaseapp.com",
   databaseURL: "https://shardautherror.firebaseio.com",
   projectId: "shardautherror",
   storageBucket: "shardautherror.appspot.com",
   messagingSenderId: "841096336504",
   appId: "1:841096336504:web:9899961c8250caa552498d"
  };

  const shard="https://shardautherror-1e9ed.firebaseio.com/";

  async function init(){
   try{
    firebase.database.enableLogging(true);
    const defaultApp=firebase.initializeApp(config);
    const auth=defaultApp.auth();
    const s="alice@example.com";
    await auth.signInWithEmailAndPassword(s,s);
    const uid= auth.currentUser.uid;
    const shardApp=firebase.initializeApp({databaseURL:shard},'dbAppShard');
    const db=firebase.database(shardApp);
    const ref= db.ref("/chat/"+uid+"/fail/"+uid);
    const time= firebase.database.ServerValue.TIMESTAMP;
    ref.set({time});
   } catch(e) {
    console.error("init failed",e);
   }
  }

  init();
 </script>
</body>
</html>

原帖:

这些规则在模拟器中有效,但在我的真实网络应用程序中无效。模拟器路径和有效负载与下面的数据库日志输出中显示的相同。

database.rules.jsonmain 瞄准两个分片以使用此规则文件;我在部署时验证)

{
 "rules":{
  "chat":{
   "$ownerId":{
    "fail":{
     "$pId":{
      ".write": "$pId== auth.uid&& $ownerId== auth.uid",
      "time":{".validate": "newData.val()== now"},
      "$other":{".validate": "newData.isString()&& newData.val().length>= 28"}
     }
    }
   }
  }
 }
}

失败的 set 命令的 Firebase 日志记录输出。它只写入一个称为时间的值。这是我第一次尝试使用 rtdb。我已经设置了分片。它在尝试访问实时数据库之前从 firestore 获取分片名称,但它看起来不像是竞争条件(尽管有日志输出),原因我将在下面概述。

index.esm.js:81 [2019-10-19T03:02:53.281Z]  @firebase/database: 0: set 
 {"path":"/chat/rpNIK41hNpWkYY2KqndkwCzPJuF3/fail/rpNIK41hNpWkYY2KqndkwCzPJuF3",
  "value":{"time":{".sv":"timestamp"}},"priority":null} 
22:02:53.285 index.esm.js:81 [2019-10-19T03:02:53.285Z]  @firebase/database:
 p:0: Buffering put: /chat/rpNIK41hNpWkYY2KqndkwCzPJuF3/fail/rpNIK41hNpWkYY2KqndkwCzPJuF3 
22:02:53.293 index.esm.js:81 [2019-10-19T03:02:53.293Z]  @firebase/database:
 p:0: Making a connection attempt 
22:02:53.294 index.esm.js:81 [2019-10-19T03:02:53.294Z]  @firebase/database:
 getToken() completed. Creating connection. 
22:02:53.295 index.esm.js:81 [2019-10-19T03:02:53.295Z]  @firebase/database:
 c:0:0: Connection created 
22:02:53.296 index.esm.js:81 [2019-10-19T03:02:53.296Z]  @firebase/database:
 p:0: Auth token refreshed 
22:02:53.298 index.esm.js:81 [2019-10-19T03:02:53.298Z]  @firebase/database:
 c:0:0:0 Websocket connecting to wss://quickstart-1551998385825-7f7a6.firebaseio.com/.ws?v=5 
22:02:53.534 index.esm.js:81 [2019-10-19T03:02:53.534Z]  @firebase/database:
 c:0:0:0 Websocket connected. 
22:02:53.539 index.esm.js:81 [2019-10-19T03:02:53.539Z]  @firebase/database:
 c:0:0: Realtime connection established. 
22:02:53.539 index.esm.js:81 [2019-10-19T03:02:53.539Z]  @firebase/database:
 p:0: connection ready 
22:02:53.542 index.esm.js:81 [2019-10-19T03:02:53.541Z]  @firebase/database:
 p:0: reportStats {"c":{"sdk.js.7-0-0":1}} 
22:02:53.542 index.esm.js:81 [2019-10-19T03:02:53.542Z]  @firebase/database:
 p:0: {"r":1,"a":"s","b":{"c":{"sdk.js.7-0-0":1}}} 
22:02:53.546 index.esm.js:81 [2019-10-19T03:02:53.546Z]  @firebase/database:
 p:0: {"r":2,"a":"p","b":{"p":"/chat/rpNIK41hNpWkYY2KqndkwCzPJuF3/fail/rpNIK41hNpWkYY2KqndkwCzPJuF3",
  "d":{"time":{".sv":"timestamp"}}}} 
22:02:53.591 index.esm.js:81 [2019-10-19T03:02:53.591Z]  @firebase/database:
 p:0: from server: {"r":1,"b":{"s":"ok","d":""}} 
22:02:53.595 index.esm.js:81 [2019-10-19T03:02:53.595Z]  @firebase/database:
 c:0:0: Primary connection is healthy. 
22:02:53.596 index.esm.js:81 [2019-10-19T03:02:53.596Z]  @firebase/database:
 p:0: from server: {"r":2,"b":{"s":"permission_denied","d":"Permission denied"}} 
22:02:53.597 index.esm.js:81 [2019-10-19T03:02:53.597Z]  @firebase/database:
 p:0: p response {"s":"permission_denied","d":"Permission denied"} 

因此,在此之后,如果我将规则更新为".write": true,,则时间戳的写入成功。在日志中它显示"r":3 ...,所以我知道它没有丢弃连接并重新启动。如果我然后将其更改为".write": "auth.uid != null",".write": "auth != null",(因此,不检查所有权,只是客户端是否登录,与上面不同)它再次拒绝许可,"r":4 ..." 表示第四个请求。因此,我的客户端似乎完全无法对分片进行身份验证。

模拟器输出:

建议?我确定我做错了什么。

顺便说一句,用户文档到处都是……所有这些实际上都有效吗?

"baskets": {
  ".read": "auth.uid != null &&// auth.uid!= null from https://firebase.google.com/docs/database/security/securing-data
".read": "auth != null && auth.uid == $uid" // auth != null from https://firebase.google.com/docs/database/security/user-security
".write": "$user_id === auth.uid" // triple equal from https://firebase.google.com/docs/database/security/user-security
 ".write": "request.auth.uid == uid" // request.auth from realtime database tab of content owner access from https://firebase.google.com/docs/rules/basics

【问题讨论】:

  • 明确地说,您是否同时使用 Firestore 和实时数据库?另外,我确定您已经检查过了,但是您是否验证了在运行 Web 应用程序时您实际上已经过身份验证?
  • rtdb 分片 URL 是从问题中提到的 firestore 获取的。除此之外,firestore 没有任何作用。之所以提到它,是因为在 rtdb 访问的调试输出中显示了 r1 在 r2 发送后返回的确认,如果应用程序更早地建立了 rtdb 连接,这可能(?)不会发生。
  • @Jay 我在原始帖子的顶部添加了一个最小复制。
  • 像往常一样,链接会断开,如果它们断开,它会使问题的那部分无效。我通过该链接得到的只是一个空白页面 - 没有错误或其他信息。
  • @Jay 如前所述,唯一的输出在控制台中。

标签: firebase firebase-realtime-database


【解决方案1】:

这一行

const shardApp=firebase.initializeApp({databaseURL:shard},'dbAppShard');

需要改为:

const shardApp=firebase.initializeApp({...config,databaseURL:shard},'dbAppShard');

如果客户端最近已经登录到 shardAppauth() 对象,这将起作用。如果没有,可以通过shardApp.auth().signInWithEmailAndPassword(email,pw)shardApp.auth().updateCurrentUser(user) 等函数来完成,其中user 可以来自例如默认应用的 auth().currentUser 对象。或者,目前仍然可以只使用默认应用,然后调用 defaultApp.database(shardURL),但在撰写本文时,我不知道此方法是否会保留在 Firebase 中。

current firebase sharding documentation 仅显示客户端将databaseURL 参数传递给initializeApp,这不足以使用基于身份验证的规则。与admin Functions answerolder-syntax multi-database sharding intro blog post 不同,当前文档要求客户端构造和管理多个应用程序对象。正如 Firebase 支持和我发现的那样,非默认应用程序当前不会从默认应用程序对象(也就是第一个应用程序,创建时没有 initializeApp 的第二个参数)中提取 API 密钥和其他参数。

Firebase 支持已经回答了我提出的有关规则的附带问题:

  • A) RTDB 使用 auth,而 Firestore 使用 request.auth
  • B) 在检查 auth.uid!=null 之前省略 auth!=null 预检查 在这种情况下是可以的,因为对象上的取消引用错误拒绝了 写。
  • C) 确认==在RTDB中转换为=== 案例。

注意,A) 表示这个example from the documentation 是错误的,因为它在 RTDB 中使用了request.auth

{
  "rules": {
    "some_path": {
      "$uid": {
        // Allow only authenticated content owners access to their data
        ".read": "request.auth.uid == uid"
        ".write": "request.auth.uid == uid"
      }
    }
  }
}

【讨论】:

  • 谢谢!对于未来的读者,shardApp.auth() 对应于 firebase v9 中的getAuth(shardApp)
  • 没问题,感谢您的支持!
【解决方案2】:

这里有两个问题,问题的第一部分需要更多信息。

问题的第二部分

顺便说一句,用户文档到处都是……都是 这些真的有效吗?

文档并不是到处都是。您包含的每个规则示例都来自不同的用例。

例如,“baskets”中的 .read 规则适用于该特定节点“baskets”,确保只有经过身份验证的用户才能读取 baskets 节点。它将允许任何经过身份验证的用户读取该节点。该行中有一个额外的 &&,所以不确定规则的其余部分是什么。

第二条读取规则将适用于它所在的任何节点,并确保用户经过身份验证,并且只有经过身份验证的用户才能读取该节点(即它是他们的节点,其他任何人都无法访问它)

documentation 覆盖了 ===(三等号)并表示

注意:: == 被视为 ===。如果您在安全规则中使用 ==,它 运行规则时将转换为 ===。

最后一次写入只是检查请求的 uid 是否是当前经过身份验证的用户。

【讨论】:

  • Baskets 被包括在内,因为我只是在页面上显示规则发生的位置。右边的 cmets 表示我为每一行指出的内容。他们似乎可以互换使用 auth != null 和 auth.uid != null ,== 和 === 互换使用(正如您所说,这似乎与 firestore 不同),以及 request.auth 和 auth 互换使用。
  • 明白。使用此auth != null &amp;&amp; auth.uid == $uid 的第二个链接是这样读取的; 在尝试读取 uid 属性之前确保 auth 变量不为空。这样做是为了确保有一个经过身份验证的用户 *before 尝试读取它的 uid - 尝试从 nil 我们读取一些东西通常很糟糕。在第一条语句auth.uid != null - 如果您在链接中继续该示例,稍后,这会再次发生auth.uid,在尝试读取该属性之前验证该属性不为零是最佳实践。
  • 我了解如何阅读它。我是说综合起来,他们介绍的伪 javascript 结构的示例并不一致。如果他们在某些规则中首先检查 auth 是否是虚假的,那么他们应该在任何地方都这样做(否则,javascript 类比会崩溃;他们会尝试访问可能为 null 的属性)。如果他们说身份验证是某些规则中请求的一部分,那么出于同样的原因,他们应该在任何地方都这样做,除非他们还引入了全局变量的概念,在普通的 javascript 中将request 视为window
  • 顺便说一句,我在一天多前对原帖的评论中回复了您对更多信息的要求,但我忘记使用 @ 东西,所以它可能没有通知您.对此感到抱歉。
  • @ChrisChiasson 这不是我要说的——让我澄清一下。在某些情况下,我们只想验证用户是否经过身份验证 - 所以检查 auth == nil。在其他情况下,我们要验证用户是否经过身份验证,然后我们将进一步检查尝试访问节点的用户是否是 this 用户,因此我们确保 auth.uid 不是nil 并且非常那个 uid = 这个用户 uid(例如)。如果您认为文档空洞或不一致(发生了这种情况!),请通过错误报告告知 Firebase 团队,因为我们无法在此处修复它们。
猜你喜欢
  • 2021-01-31
  • 1970-01-01
  • 2020-08-08
  • 2019-10-11
相关资源
最近更新 更多