Hướng dẫn này được xây dựng dựa trên hướng dẫn về các quy tắc bảo mật về cấu trúc để trình bày cách thêm điều kiện vào Quy tắc bảo mật của Cloud Firestore. Nếu bạn chưa hiểu rõ các khái niệm cơ bản về Quy tắc bảo mật của Cloud Firestore, hãy xem hướng dẫn bắt đầu.
Thành phần chính của Quy tắc bảo mật Cloud Firestore là điều kiện. Điều kiện là một biểu thức boolean xác định liệu bạn nên cho phép hay từ chối một thao tác cụ thể. Sử dụng quy tắc bảo mật để ghi các điều kiện kiểm tra việc 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ỉ muốn 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 hình 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 tính năng Xác thực Firebase hoặc Google Cloud Identity Platform, thì biến request.auth
chứa thông tin xác thực cho ứng dụng 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 trường trên các tài liệu trong cơ sở dữ liệu. Quy tắc bảo mật của Cloud Firestore 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
tham chiếu đến tài liệu được yêu cầu, và resource.data
là 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ý, 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 nhóm nhỏ các trường 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ị của trường trong request.resource
để ngăn việc 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 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 so với các tài liệu khác trong cơ sở dữ liệu. Cả hai hàm get()
và exists()
đều yêu cầu 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 thoát các biến một cách rõ ràng bằ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
thu thập 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 hoạt động ghi, bạn có thể dùng hàm getAfter()
để truy cập vào trạng thái của tài liệu sau khi một giao dịch hoặc lô ghi hoàn tất nhưng trước khi giao dịch hoặc thay đổi hàng loạt được thực hiện. Giống như get()
, hàm getAfter()
nhận 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 tập hợp ghi phải diễn ra cùng nhau dưới dạng giao dịch hoặc lô.
Truy cập giới hạn cuộc gọi
Có giới hạn về số lệnh gọi quyền truy cập vào tài liệu trong mỗi lần đánh giá bộ quy tắc:
- 10 đối với yêu cầu một tài liệu và yêu cầu truy vấn.
-
20 để đọc nhiều tài liệu, giao dịch và ghi hàng loạt. Giới hạn trước đó là 10 cũng áp dụng cho mỗi thao tác.
Ví dụ: giả sử bạn tạo một yêu cầu ghi hàng loạt có 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 lượt ghi. Trong trường hợp này, mỗi lượt ghi sử dụng 2 trong số 10 lệnh gọi truy cập còn yêu cầu ghi theo lô 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, ứng dụng sẽ gặp lỗi bị từ chối cấp quyền. Một số lệnh gọi quyền truy cập vào 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 này.
Để biết nội dung giải thích chi tiết về ảnh hưởng của những giới hạn này đối với các giao dịch và hoạt động ghi theo lô, hãy xem hướng dẫn về cách bảo mật hoạt động nguyên tử.
Truy cập cuộc gọi và giá
Việc sử dụng các hàm này sẽ thực thi 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í đọc tài liệu ngay cả khi quy tắc của bạn từ chối yêu cầu. Xem bài viết Giá của Cloud Firestore để biết thêm thông tin thanh toán cụ thể.
Hàm tuỳ chỉnh
Khi các quy tắc bảo mật của bạn trở nên phức tạp hơn, bạn nên gói các tập hợp điều kiện trong các hàm mà bạn có thể sử dụng lại trên bộ quy tắc của mình. Quy tắc bảo mật hỗ trợ các hàm tuỳ chỉnh. Cú pháp cho hàm tuỳ chỉnh hơi giống JavaScript, nhưng các hàm quy tắc bảo mật được viết bằng một ngôn ngữ đặc thù theo miền nhưng 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 được chứa thêm bất kỳ logic nào. Ví dụ: các trình xử lý này không thể thực thi vòng lặp hoặc gọi dịch vụ bên ngoài. - Hàm có thể tự động truy cập vào các hàm và biến từ phạm vi mà hàm đó đượ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 sẵn nhưget()
vàexists()
. - Các hàm có thể gọi các hàm khác nhưng có thể không lặp lại. Tổng chiều sâu ngăn xếp lệnh gọi được 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 từ khoálet
. Các hàm có thể có tối đa 10 liên kết let nhưng phải kết thúc bằng câu lệnh trả về.
Một hàm được xác định bằng từ khoá function
và không nhận hoặc 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 sử 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 dễ bảo trì hơn khi quy tắc trở nên phức tạp hơn.
Quy tắc không phải là bộ lọc
Sau khi bạn bảo mật dữ liệu của mình và bắt đầu ghi truy vấn, hãy lưu ý rằng quy tắc bảo mật không phải là bộ lọc. Bạn không thể ghi truy vấn cho tất cả tài liệu trong một bộ sưu tậ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ụ: dùng 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 sẽ từ chối truy vấn sau vì tập hợp kết quả có thể bao gồm các tài liệu trong đó 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()); }); });
Cho 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 hợ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()); }); });
Các quy tắc bảo mật của Cloud Firestore đánh giá từng truy vấn dựa trên kết quả tiềm năng của truy vấn đó và không thực hiện được yêu cầu nếu có thể trả về một tài liệu mà ứng dụng không có quyền đọc. Truy vấn phải tuân thủ các 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 và truy vấn bảo mật, hãy xem nội dung truy vấn dữ liệu một cách an toàn.
Các bước tiếp theo
- Tìm hiểu xem quy tắc bảo mật ảnh hưởng như thế nào đến truy vấn của bạn.
- Tìm hiểu cách xây dựng 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 các ứng dụng sử dụng Cloud Storage cho Firebase, hãy tìm hiểu cách ghi điều kiện của Cloud Storage về Quy tắc bảo mật để truy cập vào các tài liệu trên Cloud Firestore.