Trang này dựa trên các khái niệm trong
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. Trang này xem xét kỹ hơn cách 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 rằng các truy vấn sử dụng cùng cá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 các truy vấn dựa trên các thuộc tính truy vấn như limit và orderBy.
Quy tắc không phải là bộ lọc
Khi viết các 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 – cá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, Cloud Firestore đánh giá một truy vấn dựa trên tập hợp kết quả tiềm năng thay vì các 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ó thể 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ụ bên dưới minh hoạ, bạn phải viết các truy vấn để phù hợp với các ràng buộc của 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 một quy tắc bảo mật. Hãy xem xét một cơ sở dữ liệu chứa một bộ sưu tập 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 lưu trữ các trường author và published để sử 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. Xác thực Firebase
cũng điền biến request.auth vào
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 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ử ứng dụng của bạn có một trang hiển thị cho người dùng danh sách các tài liệu story mà họ đã tạo. Bạn có thể cho rằng mình có thể sử dụng truy vấn sau để điền dữ liệu vào 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 ràng buộc tương tự như quy tắc bảo mật:
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 quy tắc bảo mật, hệ thống sẽ đánh giá truy vấn
dựa trên tập hợ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ó thể 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ì bao gồm cùng một giới hạn trên trường author như 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
Để minh hoạ thêm về 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 sẽ 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 mà 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 tương tự 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.
Truy vấn OR
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, 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ả 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 ràng buộc trên truy vấn
Quy tắc bảo mật cũng có thể chấp nhận hoặc từ chối các truy vấn dựa trên 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ó 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 quy tắc bảo mật đánh giá cá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 đó với những thay đổi sau:
- Bộ quy tắc tách quy tắc đọc thành quy tắc cho
getvàlist. - Quy tắc
gethạn chế việc truy xuất tài liệu riêng lẻ đối với tài liệu công khai hoặc tài liệu mà người dùng đã tạo. - Quy tắc
listáp dụng các hạn chế tương tự nhưgetnhưng đối với các truy vấn. Quy tắc này 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 một 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 bộ sưu tập và quy tắc bảo mật
Theo mặc định, các truy vấn được giới hạn trong một bộ sưu tập và chỉ truy xuất kết quả từ bộ sưu tập đó. Với các truy vấn nhóm bộ sưu tập, bạn có thể truy xuất 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ã. Phần này mô tả cách bảo mật các truy vấn nhóm bộ sưu tậ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 bộ sưu tậ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 bộ sưu tập bằng cách viết một quy tắc cho nhóm bộ sưu tập:
- Đảm bảo rằng
rules_version = '2';là dòng đầu tiên của bộ quy tắc. Các truy vấn nhóm bộ sưu tậ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. - Viết một quy tắc cho nhóm bộ sưu tậ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 bộ sưu tậ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 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ả 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ả 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, bạn phải cho phép truy vấn này bằng cách viết một quy tắc đọc hoặc quy tắc danh sách cho nhóm bộ sưu tậ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ả bộ sưu tập có mã 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ả bộ sưu tập posts sau:
/posts/{postid}/forums/{forumid}/posts/{postid}/forums/{forumid}/subforum/{subforumid}/posts/{postid}
Bảo mật các truy vấn nhóm bộ sưu tập dựa trên một trường
Giống như các truy vấn một bộ sưu tập, các truy vấn nhóm bộ sưu tập cũng phải đáp ứng cá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ụ 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 bộ sưu tập posts dựa trên trạng thái published và author của 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 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ả 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ả 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 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 bộ sưu tậ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 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",
}
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ì điều này cho phép chúng ta thực hiện hai việc:
Viết các truy vấn nhóm bộ sưu tập bị hạn chế đối với 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 chúng. 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 bộ sưu tập
transactionsđể một người dùng không thể truy xuất tài liệutransactioncủa người dùng khác.
Chúng tôi thực thi hạn chế này trong quy tắc bảo mật và bao gồ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
- Để biết ví dụ chi tiết hơn về kiểm soát quyền truy cập dựa trên vai trò, hãy xem bài viết Bảo mật quyền truy cập dữ liệu cho người dùng và nhóm.
- Đọc tài liệu tham khảo về quy tắc bảo mật.