Google is committed to advancing racial equity for Black communities. See how.
本頁面由 Cloud Translation API 翻譯而成。
Switch to English

用戶和組的安全數據訪問

許多協作應用程序允許用戶基於一組權限來讀取和寫入不同的數據。例如,在文檔編輯應用程序中,用戶可能希望允許少數用戶讀寫文檔,同時阻止不必要的訪問。

解決方案:基於角色的訪問控制

您可以利用Cloud Firestore的數據模型以及自定義安全規則來在應用程序中實現基於角色的訪問控制。

假設您正在構建一個協作寫作應用程序,用戶可以在其中創建具有以下安全要求的“故事”和“評論”:

  • 每個故事都有一個所有者,並且可以與“作家”,“評論員”和“讀者”共享。
  • 讀者只能看到故事和評論。他們無法編輯任何內容。
  • 評論員擁有讀者的所有訪問權限,他們還可以向故事添加評論。
  • 作家可以訪問評論者,還可以編輯故事內容。
  • 所有者可以編輯故事的任何部分,還可以控制其他用戶的訪問權限。

數據結構

假設您的應用程序有一個stories集合,其中每個文檔都代表一個故事。每個故事都有一個comments子集合,其中每個文檔都是對該故事的註釋。

要跟踪訪問角色,請添加一個roles字段,該字段是用戶ID到角色的映射:

/ stories / {storyid}

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

註釋僅包含兩個字段,即作者的用戶ID和一些內容:

/ stories / {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
            && request.resource.data.roles[request.auth.uid] == '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;
        }
     }
   }
}
 

局限性

上面顯示的解決方案演示了使用安全規則保護用戶數據的安全,但是您應該了解以下限制:

  • 粒度 :在上面的示例中,多個角色(寫作者和所有者)對同一文檔具有寫權限,但有不同的限制。使用更複雜的文檔可能會變得難以管理,並且將單個文檔拆分為多個文檔(每個文檔由一個角色擁有)可能會更好。
  • 大組 :如果需要與非常大或複雜的組共享,請考慮一個系統,其中角色存儲在自己的集合中,而不是作為目標文檔中的字段存儲。