確保使用者和群組的資料存取安全

許多協作應用程式可讓使用者讀取及寫入不同的資料 預先闢謠介入措施例如,在文件編輯應用程式中 可能希望讓少數使用者 以及防止不必要的存取

解決方案:角色型存取權控管

使用 Cloud Firestore 的資料模型 安全性規則來導入角色式存取權 控制應用程式

假設您正在建構協作寫作應用程式,讓使用者在 可以建立故事和「留言數」並遵守下列安全性規範:

  • 每則故事都有一位擁有者,且可與「作者」、「留言者」和「讀者」分享。
  • 讀者只能查看報導和留言。他們無法編輯任何資料。
  • 留言者具備檢視者的所有權限,也能在故事中新增留言。
  • 寫入者和加註者俱備所有權限,還可以編輯故事內容。
  • 擁有者可以編輯故事的任何部分,也可以控管其他使用者的存取權。

資料結構

假設應用程式有 stories 集合,其中每個文件都代表 一則故事每個精選故事也有 comments 子集合,裡面列有每份文件 是針對該則故事的留言。

如要追蹤存取權角色,請新增 roles 欄位 將 User-ID 對應至角色:

/story/{storyid}

{
  title: "A Great Story",
  content: "Once upon a time ...",
  roles: {
    alice: "owner",
    bob: "reader",
    david: "writer",
    jane: "commenter"
    // ...
  }
}

留言只能包含兩個欄位:作者的使用者 ID 和一些內容:

/story/{storyid}/comments/{commentid}

{
  user: "alice",
  content: "I think this is a great story!"
}

規則

現在您完成了您需要寫入資料庫 驗證安全性規則。這些規則是假設應用程式使用 Firebase 驗證,讓 request.auth.uid 變數是使用者 ID。

步驟 1:先從基本規則檔案開始,其中包含故事空白規則 相關評論和留言:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
         // TODO: Story rules go here...

         match /comments/{comment} {
            // TODO: Comment rules go here...
         }
     }
   }
}

步驟 2:新增簡單的 write 規則,讓擁有者能全權掌控 故事定義的函式有助於決定使用者的角色,以及 文件的有效性:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
        function isSignedIn() {
          return request.auth != null;
        }

        function getRole(rsc) {
          // Read from the "roles" map in the resource (rsc).
          return rsc.data.roles[request.auth.uid];
        }

        function isOneOfRoles(rsc, array) {
          // Determine if the user is one of an array of roles
          return isSignedIn() && (getRole(rsc) in array);
        }

        function isValidNewStory() {
          // Valid if story does not exist and the new story has the correct owner.
          return resource == null && isOneOfRoles(request.resource, ['owner']);
        }

        // Owners can read, write, and delete stories
        allow write: if isValidNewStory() || isOneOfRoles(resource, ['owner']);

         match /comments/{comment} {
            // ...
         }
     }
   }
}

步驟 3:編寫規則,允許任何角色的使用者朗讀故事,並 留言。使用上一個步驟中定義的函式,即可保留規則 簡潔明瞭:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
        function isSignedIn() {
          return request.auth != null;
        }

        function getRole(rsc) {
          return rsc.data.roles[request.auth.uid];
        }

        function isOneOfRoles(rsc, array) {
          return isSignedIn() && (getRole(rsc) in array);
        }

        function isValidNewStory() {
          return resource == null
            && request.resource.data.roles[request.auth.uid] == 'owner';
        }

        allow write: if isValidNewStory() || isOneOfRoles(resource, ['owner']);

        // Any role can read stories.
        allow read: if isOneOfRoles(resource, ['owner', 'writer', 'commenter', 'reader']);

        match /comments/{comment} {
          // Any role can read comments.
          allow read: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                      ['owner', 'writer', 'commenter', 'reader']);
        }
     }
   }
}

步驟 4:允許故事撰寫者、留言者和擁有者發表留言。 請注意,這項規則也會驗證註解的 owner 是否與 要求使用者撰寫使用者留言,以防止使用者撰寫彼此的留言:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
        function isSignedIn() {
          return request.auth != null;
        }

        function getRole(rsc) {
          return rsc.data.roles[request.auth.uid];
        }

        function isOneOfRoles(rsc, array) {
          return isSignedIn() && (getRole(rsc) in array);
        }

        function isValidNewStory() {
          return resource == null
            && request.resource.data.roles[request.auth.uid] == 'owner';
        }

        allow write: if isValidNewStory() || isOneOfRoles(resource, ['owner'])
        allow read: if isOneOfRoles(resource, ['owner', 'writer', 'commenter', 'reader']);

        match /comments/{comment} {
          allow read: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                      ['owner', 'writer', 'commenter', 'reader']);

          // Owners, writers, and commenters can create comments. The
          // user id in the comment document must match the requesting
          // user's id.
          //
          // Note: we have to use get() here to retrieve the story
          // document so that we can check the user's role.
          allow create: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                        ['owner', 'writer', 'commenter'])
                        && request.resource.data.user == request.auth.uid;
        }
     }
   }
}

步驟 5:允許作者編輯故事內容,但無法編輯故事 角色或變更文件的任何其他屬性。這需要分割 故事 write 規則可分為 createupdatedelete,因為作曲者只能更新故事:

service cloud.firestore {
   match /databases/{database}/documents {
     match /stories/{story} {
        function isSignedIn() {
          return request.auth != null;
        }

        function getRole(rsc) {
          return rsc.data.roles[request.auth.uid];
        }

        function isOneOfRoles(rsc, array) {
          return isSignedIn() && (getRole(rsc) in array);
        }

        function isValidNewStory() {
          return request.resource.data.roles[request.auth.uid] == 'owner';
        }

        function onlyContentChanged() {
          // Ensure that title and roles are unchanged and that no new
          // fields are added to the document.
          return request.resource.data.title == resource.data.title
            && request.resource.data.roles == resource.data.roles
            && request.resource.data.keys() == resource.data.keys();
        }

        // Split writing into creation, deletion, and updating. Only an
        // owner can create or delete a story but a writer can update
        // story content.
        allow create: if isValidNewStory();
        allow delete: if isOneOfRoles(resource, ['owner']);
        allow update: if isOneOfRoles(resource, ['owner'])
                      || (isOneOfRoles(resource, ['writer']) && onlyContentChanged());
        allow read: if isOneOfRoles(resource, ['owner', 'writer', 'commenter', 'reader']);

        match /comments/{comment} {
          allow read: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                      ['owner', 'writer', 'commenter', 'reader']);
          allow create: if isOneOfRoles(get(/databases/$(database)/documents/stories/$(story)),
                                        ['owner', 'writer', 'commenter'])
                        && request.resource.data.user == request.auth.uid;
        }
     }
   }
}

限制

上述解決方案演示了運用安全性規則保護使用者資料。 但請注意下列限制:

  • 精細程度:在上述範例中,多個角色 (寫入者和擁有者) 可以寫入同一份文件,但有不同的限制。 由於文件較為複雜,因此需要花費更多心力。 最好將單一文件分割成多份文件 由單一角色擁有
  • 大型群組:如需與超大型或複雜的群組共用, 請考慮將角色儲存在專屬的集合中 而非做為目標文件上的欄位