Các quy tắc bảo mật của Cơ sở dữ liệu thời gian thực Firebase cho phép bạn kiểm soát quyền truy cập vào dữ liệu được lưu trữ trong cơ sở dữ liệu của mình. Cú pháp quy tắc linh hoạt cho phép bạn tạo các quy tắc phù hợp với mọi thứ, từ tất cả các thao tác ghi vào cơ sở dữ liệu cho đến các thao tác trên từng nút riêng lẻ.
Quy tắc bảo mật của Realtime Database là cấu hình khai báo cho cơ sở dữ liệu của bạn. Điều này có nghĩa là các quy tắc được xác định riêng biệt với logic sản phẩm. Điều này có một số ưu điểm: các ứng dụng không chịu trách nhiệm thực thi bảo mật, các cách triển khai có lỗi sẽ không làm ảnh hưởng đến dữ liệu của bạn và có lẽ quan trọng nhất là không cần có một bên trung gian, chẳng hạn như máy chủ, để bảo vệ dữ liệu khỏi thế giới bên ngoài.
Chủ đề này mô tả cú pháp và cấu trúc cơ bản của Quy tắc bảo mật Realtime Database được dùng để tạo các bộ quy tắc hoàn chỉnh.
Cấu trúc hoá các quy tắc bảo mật
Các quy tắc bảo mật của Realtime Database bao gồm các biểu thức tương tự như JavaScript có trong một tài liệu JSON. Cấu trúc của các quy tắc phải tuân theo cấu trúc của dữ liệu mà bạn đã lưu trữ trong cơ sở dữ liệu.
Các quy tắc cơ bản xác định một nhóm các nút cần được bảo mật, phương thức truy cập (ví dụ: đọc, ghi) có liên quan và điều kiện mà theo đó quyền truy cập được cho phép hoặc bị từ chối.
Trong các ví dụ sau, điều kiện của chúng ta sẽ là các câu lệnh true
và false
đơn giản, nhưng trong chủ đề tiếp theo, chúng ta sẽ đề cập đến những cách linh hoạt hơn để thể hiện điều kiện.
Ví dụ: nếu chúng ta đang cố gắng bảo mật một child_node
trong parent_node
, thì cú pháp chung cần tuân theo là:
{ "rules": { "parent_node": { "child_node": { ".read": <condition>, ".write": <condition>, ".validate": <condition>, } } } }
Hãy áp dụng mẫu này. Ví dụ: giả sử bạn đang theo dõi một danh sách tin nhắn và có dữ liệu như sau:
{ "messages": { "message0": { "content": "Hello", "timestamp": 1405704370369 }, "message1": { "content": "Goodbye", "timestamp": 1405704395231 }, ... } }
Các quy tắc của bạn phải được cấu trúc theo cách tương tự. Sau đây là một bộ quy tắc bảo mật chỉ đọc có thể phù hợp với cấu trúc dữ liệu này. Ví dụ này minh hoạ cách chúng ta chỉ định các nút cơ sở dữ liệu mà quy tắc áp dụng và các điều kiện để đánh giá quy tắc tại những nút đó.
{ "rules": { // For requests to access the 'messages' node... "messages": { // ...and the individual wildcarded 'message' nodes beneath // (we'll cover wildcarding variables more a bit later).... "$message": { // For each message, allow a read operation if <condition>. In this // case, we specify our condition as "true", so read access is always granted. ".read": "true", // For read-only behavior, we specify that for write operations, our // condition is false. ".write": "false" } } } }
Các thao tác cơ bản về quy tắc
Có 3 loại quy tắc để thực thi tính bảo mật dựa trên loại thao tác đang được thực hiện trên dữ liệu: .write
, .read
và .validate
. Sau đây là nội dung tóm tắt ngắn gọn về mục đích của các chỉ số này:
Các loại quy tắc | |
---|---|
.read | Mô tả việc người dùng có được phép đọc dữ liệu hay không và thời điểm được phép đọc dữ liệu. |
.write | Mô tả việc có được phép ghi dữ liệu hay không và thời điểm được phép ghi dữ liệu. |
.validate | Xác định giá trị có định dạng chính xác sẽ trông như thế nào, liệu giá trị đó có thuộc tính con hay không và kiểu dữ liệu. |
Biến chụp ký tự đại diện
Tất cả các câu lệnh quy tắc đều trỏ đến các nút. Một câu lệnh có thể trỏ đến một nút cụ thể hoặc sử dụng $
ký tự đại diện biến chụp để trỏ đến các nhóm nút ở một cấp độ của hệ thống phân cấp. Sử dụng các biến chụp này để lưu trữ giá trị của khoá nút để sử dụng trong các câu lệnh quy tắc tiếp theo. Kỹ thuật này cho phép bạn viết các Rules điều kiện phức tạp hơn. Đây là nội dung chúng ta sẽ đề cập chi tiết hơn trong chủ đề tiếp theo.
{ "rules": { "rooms": { // this rule applies to any child of /rooms/, the key for each room id // is stored inside $room_id variable for reference "$room_id": { "topic": { // the room's topic can be changed if the room id has "public" in it ".write": "$room_id.contains('public')" } } } } }
Bạn cũng có thể sử dụng song song các biến $
động với tên đường dẫn hằng số. Trong ví dụ này, chúng ta đang dùng biến $other
để khai báo một quy tắc .validate
nhằm đảm bảo rằng widget
không có phần tử con nào khác ngoài title
và color
.
Mọi thao tác ghi dẫn đến việc tạo thêm các phần tử con đều sẽ không thành công.
{ "rules": { "widget": { // a widget can have a title or color attribute "title": { ".validate": true }, "color": { ".validate": true }, // but no other child paths are allowed // in this case, $other means any key excluding "title" and "color" "$other": { ".validate": false } } } }
Đọc và ghi quy tắc xếp tầng
Các quy tắc .read
và .write
hoạt động từ trên xuống dưới, trong đó các quy tắc nông hơn sẽ ghi đè các quy tắc sâu hơn. Nếu một quy tắc cấp quyền đọc hoặc ghi tại một đường dẫn cụ thể, thì quy tắc đó cũng cấp quyền truy cập vào tất cả các nút con bên dưới đường dẫn đó. Hãy cân nhắc cấu trúc sau:
{ "rules": { "foo": { // allows read to /foo/* ".read": "data.child('baz').val() === true", "bar": { /* ignored, since read was allowed already */ ".read": false } } } }
Cấu trúc bảo mật này cho phép đọc /bar/
bất cứ khi nào /foo/
chứa một baz
con có giá trị true
.
Quy tắc ".read": false
trong /foo/bar/
không có hiệu lực ở đây, vì đường dẫn con không thể thu hồi quyền truy cập.
Mặc dù có vẻ không trực quan ngay lập tức, nhưng đây là một phần mạnh mẽ của ngôn ngữ quy tắc và cho phép triển khai các đặc quyền truy cập rất phức tạp mà không tốn nhiều công sức. Điều này sẽ được minh hoạ khi chúng ta tìm hiểu về bảo mật dựa trên người dùng ở phần sau của hướng dẫn này.
Xin lưu ý rằng các quy tắc .validate
không xếp tầng. Bạn phải đáp ứng tất cả các quy tắc xác thực ở mọi cấp trong hệ phân cấp để được phép ghi.
Quy tắc không phải là bộ lọc
Các quy tắc được áp dụng theo cách thức nguyên tử. Điều đó có nghĩa là thao tác đọc hoặc ghi sẽ thất bại ngay lập tức nếu không có quy tắc nào tại vị trí đó hoặc tại một vị trí mẹ cấp quyền truy cập. Ngay cả khi mọi đường dẫn con bị ảnh hưởng đều có thể truy cập được, việc đọc tại vị trí gốc sẽ hoàn toàn không thành công. Hãy cân nhắc cấu trúc sau:
{ "rules": { "records": { "rec1": { ".read": true }, "rec2": { ".read": false } } } }
Nếu không hiểu rằng các quy tắc được đánh giá một cách riêng lẻ, thì có vẻ như việc tìm nạp đường dẫn /records/
sẽ trả về rec1
chứ không phải rec2
. Tuy nhiên, kết quả thực tế là một lỗi:
var db = firebase.database(); db.ref("records").once("value", function(snap) { // success method is not called }, function(err) { // error callback triggered with PERMISSION_DENIED });
FIRDatabaseReference *ref = [[FIRDatabase database] reference]; [[_ref child:@"records"] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { // success block is not called } withCancelBlock:^(NSError * _Nonnull error) { // cancel block triggered with PERMISSION_DENIED }];
var ref = FIRDatabase.database().reference() ref.child("records").observeSingleEventOfType(.Value, withBlock: { snapshot in // success block is not called }, withCancelBlock: { error in // cancel block triggered with PERMISSION_DENIED })
FirebaseDatabase database = FirebaseDatabase.getInstance(); DatabaseReference ref = database.getReference("records"); ref.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { // success method is not called } @Override public void onCancelled(FirebaseError firebaseError) { // error callback triggered with PERMISSION_DENIED }); });
curl https://docs-examples.firebaseio.com/rest/records/ # response returns a PERMISSION_DENIED error
Vì thao tác đọc tại /records/
là nguyên tử và không có quy tắc đọc nào cấp quyền truy cập vào tất cả dữ liệu trong /records/
, nên thao tác này sẽ gửi lỗi PERMISSION_DENIED
. Nếu đánh giá quy tắc này trong trình mô phỏng bảo mật trong bảng điều khiển Firebase, chúng ta có thể thấy rằng thao tác đọc đã bị từ chối vì không có quy tắc đọc nào cho phép truy cập vào đường dẫn /records/
. Tuy nhiên, lưu ý rằng quy tắc cho rec1
chưa bao giờ được đánh giá vì quy tắc này không nằm trong đường dẫn mà chúng tôi yêu cầu. Để tìm nạp rec1
, chúng ta cần truy cập trực tiếp vào rec1
:
var db = firebase.database(); db.ref("records/rec1").once("value", function(snap) { // SUCCESS! }, function(err) { // error callback is not called });
FIRDatabaseReference *ref = [[FIRDatabase database] reference]; [[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { // SUCCESS! }];
var ref = FIRDatabase.database().reference() ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in // SUCCESS! })
FirebaseDatabase database = FirebaseDatabase.getInstance(); DatabaseReference ref = database.getReference("records/rec1"); ref.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { // SUCCESS! } @Override public void onCancelled(FirebaseError firebaseError) { // error callback is not called } });
curl https://docs-examples.firebaseio.com/rest/records/rec1 # SUCCESS!
Câu trùng lặp
Một nút có thể áp dụng nhiều quy tắc. Trong trường hợp nhiều biểu thức quy tắc xác định một nút, phương thức truy cập sẽ bị từ chối nếu bất kỳ điều kiện nào là false
:
{ "rules": { "messages": { // A rule expression that applies to all nodes in the 'messages' node "$message": { ".read": "true", ".write": "true" }, // A second rule expression applying specifically to the 'message1` node "message1": { ".read": "false", ".write": "false" } } } }
Trong ví dụ trên, các lượt đọc đến nút message1
sẽ bị từ chối vì quy tắc thứ hai luôn là false
, ngay cả khi quy tắc đầu tiên luôn là true
.
Các bước tiếp theo
Bạn có thể tìm hiểu sâu hơn về Quy tắc bảo mật của Cơ sở dữ liệu thời gian thực Firebase:
Tìm hiểu khái niệm chính tiếp theo của ngôn ngữ Rules, điều kiện động. Điều kiện này cho phép Rules kiểm tra quyền uỷ quyền của người dùng, so sánh dữ liệu hiện có và dữ liệu đến, xác thực dữ liệu đến, kiểm tra cấu trúc của các truy vấn đến từ máy khách, v.v.
Xem xét các trường hợp sử dụng bảo mật điển hình và các định nghĩa về Quy tắc bảo mật của Firebase giải quyết các trường hợp đó.