Hướng dẫn này dựa trên hướng dẫn cấu trúc các quy tắc bảo mật để cho biết cách thêm điều kiện vào Cloud Firestore Security Rules. Nếu bạn chưa nắm rõ những kiến thức cơ bản về Cloud Firestore Security Rules, hãy xem hướng dẫn bắt đầu sử dụng.
Thành phần cơ bản của Cloud Firestore Security Rules là điều kiện. Điều kiện là một biểu thức boolean xác định xem một thao tác cụ thể có được phép hay không. Sử dụng các quy tắc bảo mật để viết các điều kiện kiểm tra hoạt động xác thực người dùng, xác thực dữ liệu đến hoặc thậm chí truy cập vào các phần khác trong cơ sở dữ liệu của bạn.
Xác thực
Một trong những mẫu quy tắc bảo mật phổ biến nhất là kiểm soát quyền truy cập dựa trên trạng thái xác thực của người dùng. Ví dụ: ứng dụng của bạn có thể chỉ cho phép người dùng đã đăng nhập ghi dữ liệu:
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to access documents in the "cities" collection
// only if they are authenticated.
match /cities/{city} {
allow read, write: if request.auth != null;
}
}
}
Một mẫu phổ biến khác là đảm bảo người dùng chỉ có thể đọc và ghi dữ liệu của riêng họ:
service cloud.firestore {
match /databases/{database}/documents {
// Make sure the uid of the requesting user matches name of the user
// document. The wildcard expression {userId} makes the userId variable
// available in rules.
match /users/{userId} {
allow read, update, delete: if request.auth != null && request.auth.uid == userId;
allow create: if request.auth != null;
}
}
}
Nếu ứng dụng của bạn sử dụng Firebase Authentication hoặc Google Cloud Identity Platform, thì biến request.auth
sẽ chứa thông tin xác thực cho máy khách yêu cầu dữ liệu.
Để biết thêm thông tin về request.auth
, hãy xem tài liệu tham khảo.
Xác thực dữ liệu
Nhiều ứng dụng lưu trữ thông tin kiểm soát quyền truy cập dưới dạng các trường trên tài liệu trong cơ sở dữ liệu. Cloud Firestore Security Rules có thể linh động cho phép hoặc từ chối quyền truy cập dựa trên dữ liệu tài liệu:
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to read data if the document has the 'visibility'
// field set to 'public'
match /cities/{city} {
allow read: if resource.data.visibility == 'public';
}
}
}
Biến resource
đề cập đến tài liệu được yêu cầu và resource.data
là một bản đồ của tất cả các trường và giá trị được lưu trữ trong tài liệu. Để biết thêm thông tin về biến resource
, hãy xem tài liệu tham khảo.
Khi ghi dữ liệu, bạn có thể muốn so sánh dữ liệu đến với dữ liệu hiện có.
Trong trường hợp này, nếu bộ quy tắc của bạn cho phép thao tác ghi đang chờ xử lý, thì biến request.resource
sẽ chứa trạng thái trong tương lai của tài liệu. Đối với các thao tác update
chỉ sửa đổi một số trường của tài liệu, biến request.resource
sẽ chứa trạng thái tài liệu đang chờ xử lý sau thao tác. Bạn có thể kiểm tra các giá trị trường trong request.resource
để ngăn chặn các bản cập nhật dữ liệu không mong muốn hoặc không nhất quán:
service cloud.firestore {
match /databases/{database}/documents {
// Make sure all cities have a positive population and
// the name is not changed
match /cities/{city} {
allow update: if request.resource.data.population > 0
&& request.resource.data.name == resource.data.name;
}
}
}
Truy cập vào các tài liệu khác
Bằng cách sử dụng các hàm get()
và exists()
, các quy tắc bảo mật của bạn có thể đánh giá các yêu cầu đến dựa trên các tài liệu khác trong cơ sở dữ liệu. Cả hai hàm get()
và exists()
đều mong đợi các đường dẫn tài liệu được chỉ định đầy đủ. Khi sử dụng các biến để tạo đường dẫn cho get()
và exists()
, bạn cần phải thoát biến một cách rõ ràng bằng cách sử dụng cú pháp $(variable)
.
Trong ví dụ bên dưới, biến database
được câu lệnh so khớp match /databases/{database}/documents
nắm bắt và dùng để tạo đường dẫn:
service cloud.firestore {
match /databases/{database}/documents {
match /cities/{city} {
// Make sure a 'users' document exists for the requesting user before
// allowing any writes to the 'cities' collection
allow create: if request.auth != null && exists(/databases/$(database)/documents/users/$(request.auth.uid));
// Allow the user to delete cities if their user document has the
// 'admin' field set to 'true'
allow delete: if request.auth != null && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true;
}
}
}
Đối với các thao tác ghi, bạn có thể dùng hàm getAfter()
để truy cập vào trạng thái của một tài liệu sau khi một giao dịch hoặc một loạt các thao tác ghi hoàn tất nhưng trước khi giao dịch hoặc loạt thao tác đó được xác nhận. Giống như get()
, hàm getAfter()
có một đường dẫn tài liệu được chỉ định đầy đủ. Bạn có thể sử dụng getAfter()
để xác định các nhóm thao tác ghi phải diễn ra cùng nhau dưới dạng một giao dịch hoặc lô.
Giới hạn số lần truy cập
Có giới hạn về số lần gọi truy cập vào tài liệu cho mỗi lần đánh giá bộ quy tắc:
- 10 cho yêu cầu về một tài liệu và yêu cầu truy vấn.
-
20 cho các giao dịch, hoạt động đọc nhiều tài liệu và các hoạt động ghi theo lô. Giới hạn trước đây là 10 cũng áp dụng cho từng thao tác.
Ví dụ: giả sử bạn tạo một yêu cầu ghi theo lô với 3 thao tác ghi và các quy tắc bảo mật của bạn sử dụng 2 lệnh gọi truy cập tài liệu để xác thực từng thao tác ghi. Trong trường hợp này, mỗi thao tác ghi sẽ sử dụng 2 trong số 10 lệnh gọi truy cập và yêu cầu ghi theo lô sẽ sử dụng 6 trong số 20 lệnh gọi truy cập.
Nếu vượt quá một trong hai giới hạn này, bạn sẽ gặp lỗi bị từ chối cấp quyền. Một số lệnh gọi truy cập tài liệu có thể được lưu vào bộ nhớ đệm và các lệnh gọi được lưu vào bộ nhớ đệm sẽ không được tính vào giới hạn.
Để biết nội dung giải thích chi tiết về cách các giới hạn này ảnh hưởng đến giao dịch và các thao tác ghi theo lô, hãy xem hướng dẫn về bảo mật các thao tác nguyên tử.
Truy cập vào các cuộc gọi và giá
Việc sử dụng các hàm này sẽ thực hiện một thao tác đọc trong cơ sở dữ liệu của bạn, tức là bạn sẽ bị tính phí khi đọc tài liệu ngay cả khi các quy tắc của bạn từ chối yêu cầu. Hãy xem phần Cloud Firestore Định giá để biết thêm thông tin cụ thể về việc thanh toán.
Hàm tuỳ chỉnh
Khi các quy tắc bảo mật trở nên phức tạp hơn, bạn có thể muốn gói các điều kiện trong các hàm mà bạn có thể sử dụng lại trong bộ quy tắc. Quy tắc bảo mật hỗ trợ các hàm tuỳ chỉnh. Cú pháp cho các hàm tuỳ chỉnh hơi giống với JavaScript, nhưng các hàm quy tắc bảo mật được viết bằng một ngôn ngữ dành riêng cho miền có một số hạn chế quan trọng:
- Các hàm chỉ có thể chứa một câu lệnh
return
duy nhất. Chúng không thể chứa bất kỳ logic bổ sung nào. Ví dụ: chúng không thể thực thi các vòng lặp hoặc gọi các dịch vụ bên ngoài. - Các hàm có thể tự động truy cập vào các hàm và biến từ phạm vi mà chúng được xác định. Ví dụ: một hàm được xác định trong phạm vi
service cloud.firestore
có quyền truy cập vào biếnresource
và các hàm tích hợp nhưget()
vàexists()
. - Các hàm có thể gọi các hàm khác nhưng không được đệ quy. Tổng số ngăn xếp lệnh gọi bị giới hạn ở mức 10.
- Trong phiên bản quy tắc
v2
, các hàm có thể xác định biến bằng cách sử dụng từ khoálet
. Hàm có thể có tối đa 10 liên kết let, nhưng phải kết thúc bằng một câu lệnh return.
Hàm được xác định bằng từ khoá function
và nhận 0 hoặc nhiều đối số. Ví dụ: bạn có thể muốn kết hợp 2 loại điều kiện được dùng trong các ví dụ ở trên thành một hàm duy nhất:
service cloud.firestore {
match /databases/{database}/documents {
// True if the user is signed in or the requested data is 'public'
function signedInOrPublic() {
return request.auth.uid != null || resource.data.visibility == 'public';
}
match /cities/{city} {
allow read, write: if signedInOrPublic();
}
match /users/{user} {
allow read, write: if signedInOrPublic();
}
}
}
Việc sử dụng các hàm trong quy tắc bảo mật giúp bạn dễ dàng duy trì các quy tắc hơn khi độ phức tạp của quy tắc tăng lên.
Quy tắc không phải là bộ lọc
Sau khi bạn bảo mật dữ liệu và bắt đầu viết truy vấn, hãy lưu ý rằng các quy tắc bảo mật không phải là bộ lọc. Bạn không thể viết một truy vấn cho tất cả các tài liệu trong một tập hợp và mong đợi Cloud Firestore chỉ trả về những tài liệu mà ứng dụng hiện tại có quyền truy cập.
Ví dụ: hãy xem quy tắc bảo mật sau:
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to read data if the document has the 'visibility'
// field set to 'public'
match /cities/{city} {
allow read: if resource.data.visibility == 'public';
}
}
}
Bị từ chối: Quy tắc này từ chối truy vấn sau đây vì tập kết quả có thể bao gồm những tài liệu mà visibility
không phải là public
:
Web
db.collection("cities").get() .then(function(querySnapshot) { querySnapshot.forEach(function(doc) { console.log(doc.id, " => ", doc.data()); }); });
Được phép: Quy tắc này cho phép truy vấn sau vì mệnh đề where("visibility", "==", "public")
đảm bảo rằng tập kết quả đáp ứng điều kiện của quy tắc:
Web
db.collection("cities").where("visibility", "==", "public").get() .then(function(querySnapshot) { querySnapshot.forEach(function(doc) { console.log(doc.id, " => ", doc.data()); }); });
Cloud Firestorecác quy tắc bảo mật đánh giá từng truy vấn dựa trên kết quả tiềm năng của truy vấn đó và sẽ không thực hiện yêu cầu nếu truy vấn có thể trả về một tài liệu mà ứng dụng không có quyền đọc. Các truy vấn phải tuân theo những ràng buộc do quy tắc bảo mật của bạn đặt ra. Để biết thêm thông tin về các quy tắc bảo mật và truy vấn, hãy xem phần truy vấn dữ liệu một cách an toàn.
Các bước tiếp theo
- Tìm hiểu cách các quy tắc bảo mật ảnh hưởng đến truy vấn của bạn.
- Tìm hiểu cách cấu trúc các quy tắc bảo mật.
- Đọc tài liệu tham khảo về quy tắc bảo mật.
- Đối với những ứng dụng dùng Cloud Storage for Firebase, hãy tìm hiểu cách viết các điều kiện Cloud Storage Security Rules truy cập vào tài liệu Cloud Firestore.