Truy vấn dữ liệu một cách an toàn

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

Để được trợ giúp AI, hãy sao chép câu lệnh bên dưới vào tác nhân lập trình AI (như Gemini trong Android Studio, Antigravity, Claude Code, Codex và Cursor). Bạn có thể tuỳ chỉnh câu lệnh và lặp lại với tác nhân. Truy cập vào bài viết Kỹ năng của tác nhân Firebase để biết thông tin chi tiết về cách cài đặt.

Help me use Firebase in my app.
1. Make sure that **Firebase agent skills** are installed and updated. For Claude Code, Codex, or Cursor agents, help me install the Firebase plugin. For other agents, run `npx skills add firebase/agent-skills`. Always try to use **Firebase agent skills** for Firebase-related tasks.
2. Help me write, validate, and deploy Firebase Security Rules to protect my app's Firestore data.

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 lưu ý rằng quy tắc bảo mật không phải là bộ lọc – truy vấn là tất cả hoặc không có gì. Để tiết kiệm thời gian và tài nguyên cho bạn, Rules sẽ đánh giá một truy vấn dựa trên tập kết quả tiềm năng của truy vấn đó thay vì giá trị trường thực tế cho tất 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à ứng dụng 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 minh hoạ, bạn phải viết các truy vấn sao cho phù hợp với 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 auth.uid

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

/stories/{storyid}

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

Ngoài các trường title và content, mỗi tài liệu còn lưu trữ các trường author và published để dùng cho việc kiểm soát quyền truy cập. Các ví dụ này giả định rằng ứ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. Firebase Authentication cũng điền sẵn biến request.auth trong các quy tắc bảo mật.

Quy tắc bảo mật sau đây sử dụng các biến request.auth và resource.data để hạn chế quyền đọ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ử ứng dụng của bạn có một trang cho người dùng thấy danh sách tài liệu story mà họ đã tạo. Bạn có thể sử dụng truy vấn sau để điền sẵn trang này. Tuy nhiên, truy vấn này sẽ không thành công vì không bao gồm các quy tắc ràng buộc giống như quy tắc bảo mật của bạn:

Không hợp lệ: Các quy tắc ràng buộc của truy vấn không khớp với các quy tắc ràng buộc của 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 Rules áp dụng các quy tắc bảo mật, Rules sẽ đánh giá truy vấn dựa trên tập kết quả tiềm năng, chứ không phải 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. Nếu một truy vấn có khả năng bao gồm các tài liệu vi phạm quy tắc bảo mật, thì truy vấn đó sẽ không thành công.

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

Hợp lệ: Các quy tắc ràng buộc của truy vấn khớp với các quy tắc ràng buộc của 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

Để minh hoạ rõ hơn về hoạt động tương tác giữa các truy vấn và quy tắc, các quy tắc bảo mật dưới đây mở rộng quyền đọc cho tập hợp stories để cho phép mọi người dùng đọc các tài liệu story có 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 quy tắc ràng buộc giống như 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.published là true cho mọi kết quả. Do đó, truy vấn này đáp ứng các quy tắc bảo mật và được phép đọc dữ liệu.

OR truy vấn

Khi đánh giá một truy vấn OR logic (or, in hoặc array-contains-any) dựa trên một bộ quy tắc, Rules sẽ đánh giá riêng từng giá trị so sánh. Mỗi giá trị so sánh phải đáp ứng các quy tắc ràng buộc về 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ệ: Câu truy vấn không đảm bảo rằng x > 5 cho tất cả tài liệu tiềm năng

// These queries will fail
query(db.collection("mydocuments"),
      or(where("x", "==", 1),
         where("x", "==", 6)
      )
    )

query(db.collection("mydocuments"),
      where("x", "in", [1, 3, 6, 42, 99])
    )

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

query(db.collection("mydocuments"),
      or(where("x", "==", 6),
         where("x", "==", 42)
      )
    )

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

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

Các quy tắc bảo mật cũng có thể chấp nhận hoặc từ chối truy vấn dựa trên các quy tắc ràng buộc của chúng. Biến request.query chứa các thuộc tính limit, offset và orderBy của một truy vấn. Ví dụ: quy tắc bảo mật của bạn có thể từ chối mọi truy vấn 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 minh hoạ cách viết các quy tắc bảo mật để đánh giá các quy tắc ràng buộc được đặt trên truy vấn. Ví dụ này mở rộng bộ quy tắc stories trước đó bằng những thay đổi sau:

  • Bộ quy tắc này tách quy tắc đọc thành các quy tắc cho get và list.
  • Quy tắc get hạn chế việc truy xuất các tài liệu đơn lẻ đối với tài liệu công khai hoặc tài liệu do người dùng tạo.
  • Quy tắc list áp dụng các quy định hạn chế tương tự như get nhưng đối với cụm từ tìm kiếm. Nó cũng kiểm tra giới hạn truy vấn, sau đó từ chối mọi truy vấn 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 hàm 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 tập hợp và quy tắc bảo mật

Theo mặc định, các truy vấn được đặt trong phạm vi một tập hợp và chỉ truy xuất kết quả từ tập hợp đó. Với truy vấn nhóm tập hợp, bạn có thể truy xuất kết quả từ một nhóm tập hợp bao gồm tất cả các tập hợp có cùng mã nhận dạng. Phần này mô tả cách bảo mật các truy vấn nhóm tập hợp bằng quy tắc bảo mật.

Bảo mật và truy vấn tài liệu dựa trên nhóm tập hợp

Trong quy tắc bảo mật, 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 trong bộ quy tắc của bạn. Các truy vấn nhóm tập hợp yêu cầu hành vi ký tự đại diện đệ quy mới {name=**} 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 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 sắp xếp thành các tài liệu forum chứa các tập hợp con posts:

/forums/{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 cho phép chủ sở hữu chỉnh sửa bài đăng và người dùng đã xác thực có thể đọc bài đăng:

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 đã xác thực nào cũng có thể truy xuất bài đăng của một diễn đàn bất kỳ:

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

Nhưng nếu bạn muốn cho người dùng hiện tại thấy 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 tập hợp để truy xuất kết quả từ tất cả tập hợp posts:

var user = firebase.auth().currentUser;

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

Trong quy tắc bảo mật, bạn phải cho phép truy vấn này bằng cách viết quy tắc đọc hoặc liệt kê cho nhóm tập hợ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, xin 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ó mã nhận dạng posts, bất kể hệ 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 đây:

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

Truy vấn nhóm tập hợp bảo mật dựa trên một trường

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

/forums/{forumid}/posts/{postid}

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

Sau đó, chúng ta có thể viết quy tắc cho nhóm tập hợp posts dựa trên trạng thái published và bài đăng author:

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 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ể truy xuất các bài đăng đã xuất bản của một 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ả 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 bộ sưu tập và đường dẫn đến tài liệu

Trong một số trường hợp, bạn nên hạn chế các truy vấn nhóm tập hợp dựa trên đường dẫn tài liệu. Để tạo các quy định hạn chế này, bạn có thể sử dụng cùng một kỹ thuậ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 giao dịch của từng người dùng trong số một số sàn giao dịch chứng khoán và tiền mã hoá:

/users/{userid}/exchange/{exchangeid}/transactions/{transaction}

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

Hãy lưu ý trường user. Mặc dù chúng ta 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, nhưng chúng ta vẫn sao chép thông tin này trong mỗi tài liệu transaction vì việc này cho phép chúng ta thực hiện hai việc:

  • Viết các truy vấn nhóm tập hợp chỉ áp dụng cho những tài liệu có chứa một /users/{userid} cụ thể trong đường dẫn tài liệu. 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 quy định hạn chế này cho tất cả các truy vấn trên nhóm tập hợp transactions để người dùng này 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 quy định hạn chế này trong các quy tắc bảo mật và thêm quy trình 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
    }
  }
}

Các bước tiếp theo