Catch up on everything announced at Firebase Summit, and learn how Firebase can help you accelerate app development and run your app with confidence. Learn More

Truy vấn dữ liệu an toàn

Sử dụng bộ sưu tập để sắp xếp ngăn nắp các trang Lưu và phân loại nội dung dựa trên lựa chọn ưu tiên của bạn.

Trang này được xây dựng dựa trên các khái niệm trong Quy tắc bảo mật cấu trúcĐiều kiện viết cho Quy tắc bảo mật để giải thích cách các Quy tắc bảo mật Cloud Firestore tương tác với các truy vấn. Cần xem xét kỹ hơn cách các quy tắc bảo mật ảnh hưởng đến các truy vấn mà bạn có thể viết và mô tả cách đảm bảo các truy vấn của bạn sử dụng các ràng buộc giống như các quy tắc bảo mật của bạn. Trang này cũng mô tả cách viết các quy tắc bảo mật để cho phép hoặc từ chối các truy vấn dựa trên các thuộc tính của truy vấn như limitorderBy .

Quy tắc không phải là bộ lọc

Khi viết truy vấn để truy xuất tài liệu, hãy nhớ rằng các quy tắc bảo mật không phải là bộ lọc — các truy vấn là tất cả hoặc không có gì. Để giúp bạn tiết kiệm thời gian và tài nguyên, Cloud Firestore đánh giá một truy vấn dựa trên tập hợp kết quả tiềm năng của nó thay vì các giá trị trường thực tế cho tất cả các tài liệu của bạn. Nếu một truy vấn có khả năng trả về các tài liệu mà máy khách không có quyền đọc, thì toàn bộ yêu cầu sẽ không thành công.

Truy vấn và quy tắc bảo mật

Như các ví dụ dưới đây chứng minh, bạn phải viết các truy vấn của mình để phù hợp với các ràng buộc của các quy tắc bảo mật của bạn.

Bảo mật và truy vấn tài liệu dựa trên auth.uid

Ví dụ sau trình bày cách viết truy vấn để truy xuất tài liệu được bảo vệ bởi quy tắc bảo mật. Hãy xem xét một cơ sở dữ liệu có chứa một bộ sưu tập các tài liệu story :

/ story / {storyid}

{
  title: "A Great Story",
  content: "Once upon a time...",
  author: "some_auth_id",
  published: false
}

Ngoài các trường titlecontent , mỗi tài liệu lưu trữ author và các trường published để sử dụng cho việc kiểm soát truy cập. Các ví dụ này giả sử ứng dụng sử dụng Xác thực Firebase để đặt trường author thành UID của người dùng đã tạo tài liệu. Xác thực Firebase cũng điền biến request.auth vào các quy tắc bảo mật.

Quy tắc bảo mật sau sử dụng các biến request.authresource.data để hạn chế quyền truy cập đọc và ghi cho mỗi story đối với tác giả của nó:

service cloud.firestore {
  match /databases/{database}/documents {
    match /stories/{storyid} {
      // Only the authenticated user who authored the document can read or write
      allow read, write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

Giả sử rằng ứng dụng của bạn bao gồm một trang hiển thị cho người dùng danh sách các tài liệu story mà họ là tác giả. Bạn có thể mong đợi rằng bạn có thể sử dụng truy vấn sau để điền trang này. Tuy nhiên, truy vấn này sẽ không thành công, vì nó không bao gồm các ràng buộc giống như các quy tắc bảo mật của bạn:

Không hợp lệ : Ràng buộc truy vấn không khớp với ràng buộc quy tắc bảo mật

// This query will fail
db.collection("stories").get()

Truy vấn không thành công ngay cả khi người dùng hiện tại thực sự là tác giả của mọi tài liệu story . Lý do cho hành vi này là khi Cloud Firestore áp dụng các quy tắc bảo mật của bạn, nó sẽ đánh giá truy vấn dựa trên tập kết quả tiềm năng của nó, không dựa trên các thuộc tính thực tế của tài liệu trong cơ sở dữ liệu của bạn. Nếu một truy vấn có thể bao gồm các tài liệu vi phạm các quy tắc bảo mật của bạn, thì truy vấn sẽ không thành công.

Ngược lại, truy vấn sau thành công, bởi vì nó bao gồm cùng một ràng buộc trên trường author như các quy tắc bảo mật:

Hợp lệ : Ràng buộc truy vấn khớp với ràng buộc quy tắc bảo mật

var user = firebase.auth().currentUser;

db.collection("stories").where("author", "==", user.uid).get()

Bảo mật và truy vấn tài liệu dựa trên một trường

Để chứng minh thêm sự tương tác giữa các truy vấn và quy tắc, các quy tắc bảo mật bên dưới mở rộng quyền truy cập đọc cho bộ sưu tập stories để cho phép bất kỳ người dùng nào đọc tài liệu story trong đó trường published được đặt thành true .

service cloud.firestore {
  match /databases/{database}/documents {
    match /stories/{storyid} {
      // Anyone can read a published story; only story authors can read unpublished stories
      allow read: if resource.data.published == true || (request.auth != null && request.auth.uid == resource.data.author);
      // Only story authors can write
      allow write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

Truy vấn cho các trang đã xuất bản phải bao gồm các ràng buộc giống như các quy tắc bảo mật:

db.collection("stories").where("published", "==", true).get()

Ràng buộc truy vấn .where("published", "==", true) đảm bảo rằng resource.data.publishedtrue cho bất kỳ kết quả nào. Do đó, truy vấn này thỏa mãn các quy tắc bảo mật và được phép đọc dữ liệu.

inarray-contains-any truy vấn nào

Khi đánh giá mệnh đề truy vấn in hoặc array-contains-any đối với một bộ quy tắc, Cloud Firestore sẽ đánh giá từng giá trị so sánh riêng biệt. Mỗi giá trị so sánh phải đáp ứng các ràng buộc quy tắc bảo mật. Ví dụ, đối với quy tắc sau:

match /mydocuments/{doc} {
  allow read: if resource.data.x > 5;
}

Không hợp lệ : Truy vấn không đảm bảo rằng x > 5 cho tất cả các tài liệu tiềm năng

// This query will fail
db.collection("mydocuments").where("x", "in", [1, 3, 6, 42, 99]).get()

Hợp lệ : Truy vấn đảm bảo rằng x > 5 cho tất cả các tài liệu tiềm năng

db.collection("mydocuments").where("x", "in", [6, 42, 99, 105, 200]).get()

Đánh giá các ràng buộc đối với các truy vấn

Các quy tắc bảo mật của bạn cũng có thể chấp nhận hoặc từ chối các truy vấn dựa trên các ràng buộc của chúng. Biến request.query chứa các thuộc tính limit , offsetorderBy của một truy vấn. Ví dụ: các quy tắc bảo mật của bạn có thể từ chối bất kỳ truy vấn nào không giới hạn số lượng tài liệu tối đa được truy xuất trong một phạm vi nhất định:

allow list: if request.query.limit <= 10;

Bộ quy tắc sau đây trình bày cách viết các quy tắc bảo mật đánh giá các ràng buộc được đặt trên các truy vấn. Ví dụ này mở rộng bộ quy tắc các stories trước đó với các thay đổi sau:

  • Bộ quy tắc tách quy tắc đọc thành các quy tắc cho getlist .
  • Quy tắc get hạn chế việc truy xuất các tài liệu đơn lẻ đối với các tài liệu công khai hoặc tài liệu do người dùng tạo ra.
  • Quy tắc list áp dụng các hạn chế tương tự như get nhưng đối với các truy vấn. Nó cũng kiểm tra giới hạn truy vấn, sau đó từ chối bất kỳ truy vấn nào mà không có giới hạn hoặc có giới hạn lớn hơn 10.
  • Bộ quy tắc xác định một authorOrPublished() để tránh trùng lặp mã.
service cloud.firestore {

  match /databases/{database}/documents {

    match /stories/{storyid} {

      // Returns `true` if the requested story is 'published'
      // or the user authored the story
      function authorOrPublished() {
        return resource.data.published == true || request.auth.uid == resource.data.author;
      }

      // Deny any query not limited to 10 or fewer documents
      // Anyone can query published stories
      // Authors can query their unpublished stories
      allow list: if request.query.limit <= 10 &&
                     authorOrPublished();

      // Anyone can retrieve a published story
      // Only a story's author can retrieve an unpublished story
      allow get: if authorOrPublished();

      // Only a story's author can write to a story
      allow write: if request.auth.uid == resource.data.author;
    }

  }
}

Truy vấn nhóm thu thập và quy tắc bảo mật

Theo mặc định, các truy vấn được xác định phạm vi đến một tập hợp duy nhất và chúng chỉ lấy kết quả từ tập hợp đó. Với truy vấn nhóm bộ sưu tập , bạn có thể lấy kết quả từ một nhóm bộ sưu tập bao gồm tất cả các bộ sưu tập có cùng một ID. Phần này mô tả cách bảo mật các truy vấn nhóm thu thập của bạn bằng cách sử dụng các quy tắc bảo mật.

Bảo mật và truy vấn tài liệu dựa trên các nhóm thu thập

Trong các quy tắc bảo mật của mình, bạn phải cho phép rõ ràng các truy vấn nhóm tập hợp bằng cách viết quy tắc cho nhóm tập hợp:

  1. Đảm bảo rules_version = '2'; là dòng đầu tiên của bộ quy tắc của bạn. Truy vấn nhóm tập hợp yêu cầu hành vi ký tự đại diện đệ quy {name=**} mới của quy tắc bảo mật phiên bản 2.
  2. Viết quy tắc cho nhóm tập hợp của bạn bằng cách sử dụng match /{path=**}/ [COLLECTION_ID] /{doc} .

Ví dụ: hãy xem xét một diễn đàn được tổ chức thành các tài liệu forum có chứa các bộ sưu tập con của các posts :

/ forum / {forumid} / posts / {postid}

{
  author: "some_auth_id",
  authorname: "some_username",
  content: "I just read a great story.",
}

Trong ứng dụng này, chúng tôi làm cho các bài đăng có thể được chủ sở hữu chỉnh sửa và người dùng đã xác thực có thể đọc được:

service cloud.firestore {
  match /databases/{database}/documents {
    match /forums/{forumid}/posts/{post} {
      // Only authenticated users can read
      allow read: if request.auth != null;
      // Only the post author can write
      allow write: if request.auth != null && request.auth.uid == resource.data.author;
    }
  }
}

Bất kỳ người dùng được xác thực nào cũng có thể truy xuất các bài đăng của bất kỳ diễn đàn nào:

db.collection("forums/technology/posts").get()

Nhưng nếu bạn muốn hiển thị cho người dùng hiện tại các bài đăng của họ trên tất cả các diễn đàn thì sao? Bạn có thể sử dụng truy vấn nhóm bộ sưu tập để truy xuất kết quả từ tất cả các bộ sưu tập posts :

var user = firebase.auth().currentUser;

db.collectionGroup("posts").where("author", "==", user.uid).get()

Trong quy tắc bảo mật của mình, bạn phải cho phép truy vấn này bằng cách viết quy tắc đọc hoặc danh sách cho nhóm thu thập posts :

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {
    // Authenticated users can query the posts collection group
    // Applies to collection queries, collection group queries, and
    // single document retrievals
    match /{path=**}/posts/{post} {
      allow read: if request.auth != null;
    }
    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth != null && request.auth.uid == resource.data.author;

    }
  }
}

Tuy nhiên, lưu ý rằng các quy tắc này sẽ áp dụng cho tất cả các bộ sưu tập có posts ID, bất kể phân cấp. Ví dụ: các quy tắc này áp dụng cho tất cả các bộ sưu tập posts sau:

  • /posts/{postid}
  • /forums/{forumid}/posts/{postid}
  • /forums/{forumid}/subforum/{subforumid}/posts/{postid}

Các truy vấn nhóm thu thập bảo mật dựa trên một trường

Giống như truy vấn tập hợp đơn, truy vấn nhóm tập hợp cũng phải đáp ứng các ràng buộc do các quy tắc bảo mật của bạn đặt ra. Ví dụ: chúng ta có thể thêm một trường published vào mỗi bài đăng trên diễn đàn giống như chúng ta đã làm trong ví dụ stories ở trên:

/ forum / {forumid} / posts / {postid}

{
  author: "some_auth_id",
  authorname: "some_username",
  content: "I just read a great story.",
  published: false
}

Sau đó, chúng tôi có thể viết quy tắc cho nhóm thu thập posts dựa trên trạng thái publishedauthor bài đăng:

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {

    // Returns `true` if the requested post is 'published'
    // or the user authored the post
    function authorOrPublished() {
      return resource.data.published == true || request.auth.uid == resource.data.author;
    }

    match /{path=**}/posts/{post} {

      // Anyone can query published posts
      // Authors can query their unpublished posts
      allow list: if authorOrPublished();

      // Anyone can retrieve a published post
      // Authors can retrieve an unpublished post
      allow get: if authorOrPublished();
    }

    match /forums/{forumid}/posts/{postid} {
      // Only a post's author can write to a post
      allow write: if request.auth.uid == resource.data.author;
    }
  }
}

Với các quy tắc này, ứng dụng khách Web, Apple và Android có thể thực hiện các truy vấn sau:

  • Bất kỳ ai cũng có thể truy xuất các bài đăng đã xuất bản trong một diễn đàn:

    db.collection("forums/technology/posts").where('published', '==', true).get()
    
  • Bất kỳ ai cũng có thể lấy các bài viết đã xuất bản của tác giả trên tất cả các diễn đàn:

    db.collectionGroup("posts").where("author", "==", "some_auth_id").where('published', '==', true).get()
    
  • Tác giả có thể truy xuất tất cả các bài đăng đã xuất bản và chưa xuất bản của họ trên tất cả các diễn đàn:

    var user = firebase.auth().currentUser;
    
    db.collectionGroup("posts").where("author", "==", user.uid).get()
    

Bảo mật và truy vấn tài liệu dựa trên nhóm thu thập và đường dẫn tài liệu

Trong một số trường hợp, bạn có thể muốn hạn chế các truy vấn nhóm thu thập dựa trên đường dẫn tài liệu. Để tạo các hạn chế này, bạn có thể sử dụng các kỹ thuật tương tự để bảo mật và truy vấn tài liệu dựa trên một trường.

Hãy xem xét một ứng dụng theo dõi các giao dịch của mỗi người dùng giữa một số sàn giao dịch chứng khoán và tiền điện tử:

/ users / {userid} / exchange / {exchangeid} / giao dịch / {giao dịch}

{
  amount: 100,
  exchange: 'some_exchange_name',
  timestamp: April 1, 2019 at 12:00:00 PM UTC-7,
  user: "some_auth_id",
}

Lưu ý trường user . Mặc dù chúng tôi biết người dùng nào sở hữu tài liệu transaction từ đường dẫn của tài liệu, chúng tôi sao chép thông tin này trong mỗi tài liệu transaction vì nó cho phép chúng tôi thực hiện hai việc:

  • Viết các truy vấn nhóm thu thập được giới hạn cho các tài liệu bao gồm một /users/{userid} cụ thể trong đường dẫn tài liệu của họ. Ví dụ:

    var user = firebase.auth().currentUser;
    // Return current user's last five transactions across all exchanges
    db.collectionGroup("transactions").where("user", "==", user).orderBy('timestamp').limit(5)
    
  • Thực thi hạn chế này cho tất cả các truy vấn trên nhóm thu thập transactions để một người dùng không thể truy xuất tài liệu transaction của người dùng khác.

Chúng tôi thực thi hạn chế này trong các quy tắc bảo mật của mình và bao gồm xác thực dữ liệu cho trường user :

rules_version = '2';
service cloud.firestore {

  match /databases/{database}/documents {

    match /{path=**}/transactions/{transaction} {
      // Authenticated users can retrieve only their own transactions
      allow read: if resource.data.user == request.auth.uid;
    }

    match /users/{userid}/exchange/{exchangeid}/transactions/{transaction} {
      // Authenticated users can write to their own transactions subcollections
      // Writes must populate the user field with the correct auth id
      allow write: if userid == request.auth.uid && request.data.user == request.auth.uid
    }
  }
}

Bước tiếp theo