Sử dụng các điều kiện trong Quy tắc bảo mật cơ sở dữ liệu thời gian thực

Hướng dẫn này được xây dựng dựa trên hướng dẫn ngôn ngữ cốt lõi về Quy tắc bảo mật Firebase để chỉ ra cách thêm điều kiện vào Quy tắc bảo mật cơ sở dữ liệu thời gian thực Firebase của bạn.

Khối xây dựng chính của Quy tắc bảo mật cơ sở dữ liệu thời gian thực 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ể nên được cho phép hay từ chối. Đối với các quy tắc cơ bản, việc sử dụng chữ truefalse làm điều kiện sẽ hoạt động hoàn hảo. Nhưng ngôn ngữ Quy tắc bảo mật cơ sở dữ liệu thời gian thực cung cấp cho bạn cách viết các điều kiện phức tạp hơn có thể:

  • Kiểm tra xác thực người dùng
  • Đánh giá dữ liệu hiện có so với dữ liệu mới được gửi
  • Truy cập và so sánh các phần khác nhau trong cơ sở dữ liệu của bạn
  • Xác thực dữ liệu đến
  • Sử dụng cấu trúc của các truy vấn đến cho logic bảo mật

Sử dụng biến $ để nắm bắt các đoạn đường dẫn

Bạn có thể nắm bắt các phần của đường dẫn để đọc hoặc ghi bằng cách khai báo các biến chụp bằng tiền tố $ . Điều này đóng vai trò như một thẻ đại diện và lưu trữ giá trị của khóa đó để sử dụng trong các điều kiện của quy tắc:

{
  "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')"
        }
      }
    }
  }
}

Các biến $ động cũng có thể được sử dụng song song với các tên đường dẫn không đổi. Trong ví dụ này, chúng tôi đang sử dụng biến $other để khai báo quy tắc .validate nhằm đảm bảo rằng widget không có con nào khác ngoài titlecolor . Bất kỳ thao tác viết nào dẫn đến việc tạo thêm trẻ em đều sẽ thất bại.

{
  "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 }
    }
  }
}

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.

Nếu ứng dụng của bạn sử dụng Xác thực Firebase, biến request.auth sẽ chứa thông tin xác thực cho ứng dụng 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 Firebase tích hợp với 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 dữ liệu trên cơ sở từng người dùng bằng cách sử dụng các điều kiện. Sau khi người dùng xác thực, biến auth trong quy tắc Quy tắc bảo mật cơ sở dữ liệu thời gian thực của bạn sẽ được điền thông tin của người dùng. Thông tin này bao gồm số nhận dạng duy nhất của họ ( uid ) cũng như dữ liệu tài khoản được liên kết, chẳng hạn như id Facebook hoặc địa chỉ email và thông tin khác. Nếu triển khai nhà cung cấp xác thực tùy chỉnh, bạn có thể thêm các trường của riêng mình vào trọng tải xác thực của người dùng.

Phần này giải thích cách kết hợp ngôn ngữ Quy tắc bảo mật cơ sở dữ liệu thời gian thực Firebase với thông tin xác thực về người dùng của bạn. Bằng cách kết hợp hai khái niệm này, bạn có thể kiểm soát quyền truy cập vào dữ liệu dựa trên danh tính người dùng.

Biến xác auth

Biến auth được xác định trước trong quy tắc là null trước khi quá trình xác thực diễn ra.

Khi người dùng được xác thực bằng Xác thực Firebase, nó sẽ chứa các thuộc tính sau:

các nhà cung cấp Phương thức xác thực được sử dụng ("mật khẩu", "ẩn danh", "facebook", "github", "google" hoặc "twitter").
uid Id người dùng duy nhất, được đảm bảo là duy nhất trên tất cả các nhà cung cấp.
mã thông báo Nội dung của mã thông báo ID xác thực Firebase. Xem tài liệu tham khảo về auth.token để biết thêm chi tiết.

Dưới đây là một quy tắc mẫu sử dụng biến auth để đảm bảo rằng mỗi người dùng chỉ có thể ghi vào đường dẫn dành riêng cho người dùng:

{
  "rules": {
    "users": {
      "$user_id": {
        // grants write access to the owner of this user account
        // whose uid must exactly match the key ($user_id)
        ".write": "$user_id === auth.uid"
      }
    }
  }
}

Cấu trúc cơ sở dữ liệu của bạn để hỗ trợ các điều kiện xác thực

Thông thường, việc cấu trúc cơ sở dữ liệu của bạn theo cách giúp việc viết Quy tắc trở nên dễ dàng hơn thường rất hữu ích. Một mẫu phổ biến để lưu trữ dữ liệu người dùng trong Cơ sở dữ liệu thời gian thực là lưu trữ tất cả người dùng của bạn trong một nút users duy nhất có nút con là giá trị uid cho mọi người dùng. Nếu bạn muốn hạn chế quyền truy cập vào dữ liệu này để chỉ người dùng đã đăng nhập mới có thể xem dữ liệu của chính họ thì quy tắc của bạn sẽ trông giống như thế này.

{
  "rules": {
    "users": {
      "$uid": {
        ".read": "auth !== null && auth.uid === $uid"
      }
    }
  }
}

Làm việc với Xác nhận quyền sở hữu tùy chỉnh xác thực

Đối với các ứng dụng yêu cầu kiểm soát quyền truy cập tùy chỉnh cho những người dùng khác nhau, Xác thực Firebase cho phép nhà phát triển đặt xác nhận quyền sở hữu đối với người dùng Firebase . Những xác nhận quyền sở hữu này có thể truy cập được trong biến auth.token trong quy tắc của bạn. Dưới đây là ví dụ về các quy tắc sử dụng xác nhận quyền sở hữu tùy chỉnh hasEmergencyTowel :

{
  "rules": {
    "frood": {
      // A towel is about the most massively useful thing an interstellar
      // hitchhiker can have
      ".read": "auth.token.hasEmergencyTowel === true"
    }
  }
}

Các nhà phát triển tạo mã thông báo xác thực tùy chỉnh của riêng họ có thể tùy ý thêm xác nhận quyền sở hữu đối với các mã thông báo này. Những xác nhận quyền sở hữu này có sẵn trên biến auth.token trong quy tắc của bạn.

Dữ liệu hiện có so với dữ liệu mới

Biến data được xác định trước được sử dụng để tham chiếu đến dữ liệu trước khi thao tác ghi diễn ra. Ngược lại, biến newData chứa dữ liệu mới sẽ tồn tại nếu thao tác ghi thành công. newData thể hiện kết quả hợp nhất của dữ liệu mới được ghi và dữ liệu hiện có.

Để minh họa, quy tắc này sẽ cho phép chúng ta tạo bản ghi mới hoặc xóa bản ghi hiện có nhưng không thực hiện thay đổi đối với dữ liệu không có giá trị hiện có:

// we can write as long as old data or new data does not exist
// in other words, if this is a delete or a create, but not an update
".write": "!data.exists() || !newData.exists()"

Tham chiếu dữ liệu trong các đường dẫn khác

Bất kỳ dữ liệu nào cũng có thể được sử dụng làm tiêu chí cho các quy tắc. Bằng cách sử dụng các biến được xác định trước root , datanewData , chúng ta có thể truy cập bất kỳ đường dẫn nào tồn tại trước hoặc sau sự kiện ghi.

Hãy xem xét ví dụ này, cho phép các thao tác ghi miễn là giá trị của nút /allow_writes/true , nút cha không có bộ cờ readOnly và có một phần tử con có tên foo trong dữ liệu mới được ghi:

".write": "root.child('allow_writes').val() === true &&
          !data.parent().child('readOnly').exists() &&
          newData.child('foo').exists()"

Xác thực dữ liệu

Việc thực thi cấu trúc dữ liệu và xác thực định dạng cũng như nội dung của dữ liệu phải được thực hiện bằng cách sử dụng quy tắc .validate , chỉ chạy sau khi quy tắc .write cấp quyền truy cập thành công. Dưới đây là định nghĩa quy tắc .validate mẫu chỉ cho phép ngày ở định dạng YYYY-MM-DD trong khoảng thời gian từ 1900-2099, được kiểm tra bằng biểu thức chính quy.

".validate": "newData.isString() &&
              newData.val().matches(/^(19|20)[0-9][0-9][-\\/. ](0[1-9]|1[012])[-\\/. ](0[1-9]|[12][0-9]|3[01])$/)"

Quy tắc .validate là loại quy tắc bảo mật duy nhất không xếp tầng. Nếu bất kỳ quy tắc xác thực nào không thành công trên bất kỳ bản ghi con nào thì toàn bộ thao tác ghi sẽ bị từ chối. Ngoài ra, các định nghĩa xác thực sẽ bị bỏ qua khi dữ liệu bị xóa (nghĩa là khi giá trị mới được ghi là null ).

Đây có vẻ như là những điểm tầm thường nhưng trên thực tế lại là những tính năng quan trọng để viết Quy tắc bảo mật cơ sở dữ liệu thời gian thực Firebase mạnh mẽ. Hãy xem xét các quy tắc sau:

{
  "rules": {
    // write is allowed for all paths
    ".write": true,
    "widget": {
      // a valid widget must have attributes "color" and "size"
      // allows deleting widgets (since .validate is not applied to delete rules)
      ".validate": "newData.hasChildren(['color', 'size'])",
      "size": {
        // the value of "size" must be a number between 0 and 99
        ".validate": "newData.isNumber() &&
                      newData.val() >= 0 &&
                      newData.val() <= 99"
      },
      "color": {
        // the value of "color" must exist as a key in our mythical
        // /valid_colors/ index
        ".validate": "root.child('valid_colors/' + newData.val()).exists()"
      }
    }
  }
}

Hãy lưu ý đến biến thể này, hãy xem kết quả của các thao tác ghi sau:

JavaScript
var ref = db.ref("/widget");

// PERMISSION_DENIED: does not have children color and size
ref.set('foo');

// PERMISSION DENIED: does not have child color
ref.set({size: 22});

// PERMISSION_DENIED: size is not a number
ref.set({ size: 'foo', color: 'red' });

// SUCCESS (assuming 'blue' appears in our colors list)
ref.set({ size: 21, color: 'blue'});

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
ref.child('size').set(99);
Mục tiêu-C
Lưu ý: Sản phẩm Firebase này không có sẵn trên mục tiêu App Clip.
FIRDatabaseReference *ref = [[[FIRDatabase database] reference] child: @"widget"];

// PERMISSION_DENIED: does not have children color and size
[ref setValue: @"foo"];

// PERMISSION DENIED: does not have child color
[ref setValue: @{ @"size": @"foo" }];

// PERMISSION_DENIED: size is not a number
[ref setValue: @{ @"size": @"foo", @"color": @"red" }];

// SUCCESS (assuming 'blue' appears in our colors list)
[ref setValue: @{ @"size": @21, @"color": @"blue" }];

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
[[ref child:@"size"] setValue: @99];
Nhanh
Lưu ý: Sản phẩm Firebase này không có sẵn trên mục tiêu App Clip.
var ref = FIRDatabase.database().reference().child("widget")

// PERMISSION_DENIED: does not have children color and size
ref.setValue("foo")

// PERMISSION DENIED: does not have child color
ref.setValue(["size": "foo"])

// PERMISSION_DENIED: size is not a number
ref.setValue(["size": "foo", "color": "red"])

// SUCCESS (assuming 'blue' appears in our colors list)
ref.setValue(["size": 21, "color": "blue"])

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
ref.child("size").setValue(99);
Java
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("widget");

// PERMISSION_DENIED: does not have children color and size
ref.setValue("foo");

// PERMISSION DENIED: does not have child color
ref.child("size").setValue(22);

// PERMISSION_DENIED: size is not a number
Map<String,Object> map = new HashMap<String, Object>();
map.put("size","foo");
map.put("color","red");
ref.setValue(map);

// SUCCESS (assuming 'blue' appears in our colors list)
map = new HashMap<String, Object>();
map.put("size", 21);
map.put("color","blue");
ref.setValue(map);

// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
ref.child("size").setValue(99);
NGHỈ NGƠI
# PERMISSION_DENIED: does not have children color and size
curl -X PUT -d 'foo' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# PERMISSION DENIED: does not have child color
curl -X PUT -d '{"size": 22}' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# PERMISSION_DENIED: size is not a number
curl -X PUT -d '{"size": "foo", "color": "red"}' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# SUCCESS (assuming 'blue' appears in our colors list)
curl -X PUT -d '{"size": 21, "color": "blue"}' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# If the record already exists and has a color, this will
# succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
# will fail to validate
curl -X PUT -d '99' \
https://docs-examples.firebaseio.com/rest/securing-data/example/size.json

Bây giờ chúng ta hãy xem cấu trúc tương tự, nhưng sử dụng quy tắc .write thay vì .validate :

{
  "rules": {
    // this variant will NOT allow deleting records (since .write would be disallowed)
    "widget": {
      // a widget must have 'color' and 'size' in order to be written to this path
      ".write": "newData.hasChildren(['color', 'size'])",
      "size": {
        // the value of "size" must be a number between 0 and 99, ONLY IF WE WRITE DIRECTLY TO SIZE
        ".write": "newData.isNumber() && newData.val() >= 0 && newData.val() <= 99"
      },
      "color": {
        // the value of "color" must exist as a key in our mythical valid_colors/ index
        // BUT ONLY IF WE WRITE DIRECTLY TO COLOR
        ".write": "root.child('valid_colors/'+newData.val()).exists()"
      }
    }
  }
}

Trong biến thể này, bất kỳ thao tác nào sau đây sẽ thành công:

JavaScript
var ref = new Firebase(URL + "/widget");

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
ref.set({size: 99999, color: 'red'});

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
ref.child('size').set(99);
Mục tiêu-C
Lưu ý: Sản phẩm Firebase này không có sẵn trên mục tiêu App Clip.
Firebase *ref = [[Firebase alloc] initWithUrl:URL];

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
[ref setValue: @{ @"size": @9999, @"color": @"red" }];

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
[[ref childByAppendingPath:@"size"] setValue: @99];
Nhanh
Lưu ý: Sản phẩm Firebase này không có sẵn trên mục tiêu App Clip.
var ref = Firebase(url:URL)

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
ref.setValue(["size": 9999, "color": "red"])

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
ref.childByAppendingPath("size").setValue(99)
Java
Firebase ref = new Firebase(URL + "/widget");

// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
Map<String,Object> map = new HashMap<String, Object>();
map.put("size", 99999);
map.put("color", "red");
ref.setValue(map);

// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
ref.child("size").setValue(99);
NGHỈ NGƠI
# ALLOWED? Even though size is invalid, widget has children color and size,
# so write is allowed and the .write rule under color is ignored
curl -X PUT -d '{size: 99999, color: "red"}' \
https://docs-examples.firebaseio.com/rest/securing-data/example.json

# ALLOWED? Works even if widget does not exist, allowing us to create a widget
# which is invalid and does not have a valid color.
# (allowed by the write rule under "color")
curl -X PUT -d '99' \
https://docs-examples.firebaseio.com/rest/securing-data/example/size.json

Điều này minh họa sự khác biệt giữa quy tắc .write.validate . Như đã trình bày, tất cả các quy tắc này phải được viết bằng cách sử dụng .validate , ngoại trừ quy tắc newData.hasChildren() , điều này sẽ phụ thuộc vào việc có được phép xóa hay không.

Quy tắc dựa trên truy vấn

Mặc dù bạn không thể sử dụng quy tắc làm bộ lọc nhưng bạn có thể giới hạn quyền truy cập vào các tập hợp con dữ liệu bằng cách sử dụng tham số truy vấn trong quy tắc của mình. Sử dụng query. biểu thức trong quy tắc của bạn để cấp quyền truy cập đọc hoặc ghi dựa trên các tham số truy vấn.

Ví dụ: quy tắc dựa trên truy vấn sau đây sử dụng quy tắc bảo mật dựa trên người dùng và quy tắc dựa trên truy vấn để hạn chế quyền truy cập vào dữ liệu trong bộ sưu tập baskets chỉ ở các giỏ hàng mà người dùng đang hoạt động sở hữu:

"baskets": {
  ".read": "auth.uid !== null &&
            query.orderByChild === 'owner' &&
            query.equalTo === auth.uid" // restrict basket access to owner of basket
}

Truy vấn sau đây bao gồm các tham số truy vấn trong quy tắc sẽ thành công:

db.ref("baskets").orderByChild("owner")
                 .equalTo(auth.currentUser.uid)
                 .on("value", cb)                 // Would succeed

Tuy nhiên, các truy vấn không bao gồm các tham số trong quy tắc sẽ không thành công kèm theo lỗi PermissionDenied :

db.ref("baskets").on("value", cb)                 // Would fail with PermissionDenied

Bạn cũng có thể sử dụng các quy tắc dựa trên truy vấn để giới hạn lượng dữ liệu khách hàng tải xuống thông qua thao tác đọc.

Ví dụ: quy tắc sau chỉ giới hạn quyền truy cập đọc vào 1000 kết quả đầu tiên của truy vấn, theo thứ tự ưu tiên:

messages: {
  ".read": "query.orderByKey &&
            query.limitToFirst <= 1000"
}

// Example queries:

db.ref("messages").on("value", cb)                // Would fail with PermissionDenied

db.ref("messages").limitToFirst(1000)
                  .on("value", cb)                // Would succeed (default order by key)

Truy query. các biểu thức có sẵn trong Quy tắc bảo mật cơ sở dữ liệu thời gian thực.

Biểu thức quy tắc dựa trên truy vấn
Sự biểu lộ Kiểu Sự miêu tả
truy vấn.orderByKey
query.orderByPriority
truy vấn.orderByValue
boolean Đúng cho các truy vấn được sắp xếp theo khóa, mức độ ưu tiên hoặc giá trị. Sai nếu không.
truy vấn.orderByChild sợi dây
vô giá trị
Sử dụng một chuỗi để biểu thị đường dẫn tương đối đến nút con. Ví dụ: query.orderByChild === "address/zip" . Nếu truy vấn không được sắp xếp theo nút con thì giá trị này là null.
truy vấn.startAt
truy vấn.endAt
truy vấn.equalTo
sợi dây
con số
boolean
vô giá trị
Truy xuất các giới hạn của truy vấn đang thực thi hoặc trả về null nếu không có tập hợp giới hạn nào.
truy vấn.limitToFirst
truy vấn.limitToLast
con số
vô giá trị
Truy xuất giới hạn cho truy vấn đang thực thi hoặc trả về null nếu không có giới hạn nào được đặt.

Bước tiếp theo

Sau cuộc thảo luận về các điều kiện này, bạn đã hiểu rõ hơn về Quy tắc và sẵn sàng:

Tìm hiểu cách xử lý các trường hợp sử dụng cốt lõi và tìm hiểu quy trình phát triển, thử nghiệm và triển khai Quy tắc:

Tìm hiểu các tính năng Quy tắc dành riêng cho Cơ sở dữ liệu thời gian thực: