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

Hướng dẫn này được xây dựng dựa trên hướng dẫn tìm hiểu ngôn ngữ chính trong Quy tắc bảo mật của Firebase để biết cách thêm điều kiện vào Quy tắc bảo mật cơ sở dữ liệu theo thời gian thực của Firebase.

Thành phần chính của Quy tắc bảo mật cơ sở dữ liệu theo thời gian thực là điều kiện. Đáp điều kiện là biểu thức Boolean xác định liệu một toán tử cụ thể được phép hoặc bị từ chối. Đối với các quy tắc cơ bản, sử dụng giá trị cố định truefalse như điều kiện hoạt động hoàn toàn tốt. Tuy nhiên, ngôn ngữ của Quy tắc bảo mật cơ sở dữ liệu theo thời gian thực cung cấp cho bạn để viết các điều kiện phức tạp hơn, có thể:

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

Sử dụng $ Variables để thu thập phân đoạn đường dẫn

Bạn có thể chụp các phần đường dẫn cho một lượt đọc hoặc ghi bằng cách khai báo chụp các biến bằng tiền tố $. Lớp này đóng vai trò là một ký tự đại diện và lưu trữ giá trị của khoá đó để sử dụng bên 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')"
        }
      }
    }
  }
}

Bạn cũng có thể sử dụng các biến $ động song song với đường dẫn không đổi tên. Trong ví dụ này, chúng ta sẽ dùng biến $other để khai báo một quy tắc .validate để đảm bảo rằng widget không có phần tử con nào khác ngoài titlecolor. Mọi hoạt động 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 }
    }
  }
}

Xác thực

Một trong những 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 tính năng Xác thực Firebase, thì 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 tài liệu.

Tính năng Xác thực Firebase tích hợp với Cơ sở dữ liệu theo thời gian thực của Firebase để cho phép bạn kiểm soát dữ liệu quyền truy cập trên cơ sở mỗi người dùng theo các điều kiện. Sau khi người dùng xác thực, auth trong quy tắc Quy tắc bảo mật cơ sở dữ liệu theo thời gian thực sẽ được điền cùng với của bạn. Thông tin này có chứa giá trị nhận dạng duy nhất của sản phẩm (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 một nhà cung cấp dịch vụ xác thực tuỳ chỉnh, bạn có thể thêm các trường của riêng mình vào tải trọng uỷ quyền của người dùng.

Phần này giải thích cách kết hợp ngôn ngữ của Quy tắc bảo mật cơ sở dữ liệu theo thời gian thực Firebase với xác thực về người dùng của mình. 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 auth

Biến auth được xác định trước trong các quy tắc có giá trị rỗng trước các bước xác thực.

Sau khi người dùng được xác thực bằng tính năng Xác thực Firebase thì sẽ có các thuộc tính sau:

đám mây Phương pháp xác thực được sử dụng ("password", "anonymous", "facebook", "github", "google", hoặc "twitter").
uid Mã nhận dạng người dùng duy nhất, đả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 mã xác thực Firebase. Xem tài liệu tham khảo tài liệu cho auth.token để biết thêm thông tin.

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 một đườ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"
      }
    }
  }
}

Xây dựng cơ sở dữ liệu để hỗ trợ các điều kiện xác thực

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

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

Làm việc với các tuyên bố tuỳ chỉnh về việc 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 tuỳ chỉnh cho những người dùng khác nhau, tính năng Xác thực Firebase cho phép nhà phát triển xác nhận quyền sở hữu đối với một người dùng Firebase. Bạn có thể xem các thông báo xác nhận quyền sở hữu này trong biến auth.token của các quy tắc. Dưới đây là ví dụ về các quy tắc sử dụng hasEmergencyTowel khiếu nại tuỳ chỉnh:

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

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

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

Biến data xác định trước được dùng để tham chiếu đến dữ liệu trước 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 biểu thị kết quả hợp nhất của việc dữ liệu mới đang được ghi và dữ liệu hiện có.

Để minh hoạ, quy tắc này sẽ cho phép chúng ta tạo bản ghi mới hoặc xoá bản ghi hiện có nhưng không thực hiện thay đổi đối với dữ liệu không rỗng 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

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

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

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

Đang 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 và nội dung dữ liệu phải được thực hiện bằng cách sử dụng quy tắc .validate, các quy tắc này chỉ chạy sau Quy tắc .write đã cấp quyền truy cập thành công. Dưới đây là mẫu Định nghĩa quy tắc .validate 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 phân tầng. Nếu có không thực hiện được quy tắc xác thực 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ị xoá (tức là khi giá trị mới đang được viết là null).

Đây có vẻ là những điểm bình thường nhưng thực ra lại là những tính năng quan trọng để viết các quy tắc bảo mật mạnh mẽ của Firebase cho cơ sở dữ liệu theo thời gian thực. Hãy cân nhắc 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()"
      }
    }
  }
}

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

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);
Objective-C
Lưu ý: Sản phẩm Firebase này không dùng được trên mục tiêu App Clip (Đoạn video ứng dụng).
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];
Swift
Lưu ý: Sản phẩm Firebase này không dùng được trên mục tiêu App Clip (Đoạn video ứng dụng).
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);
Kiến trúc chuyển trạng thái đại diện (REST)
# 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ờ, 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 đều 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);
Objective-C
Lưu ý: Sản phẩm Firebase này không dùng được trên mục tiêu App Clip (Đoạn video ứng dụng).
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];
Swift
Lưu ý: Sản phẩm Firebase này không dùng được trên mục tiêu App Clip (Đoạn video ứng dụng).
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);
Kiến trúc chuyển trạng thái đại diện (REST)
# 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

Hình ảnh này minh hoạ sự khác biệt giữa các quy tắc .write.validate. Như đã minh hoạ, tất cả các quy tắc này phải được viết bằng cách sử dụng .validate, với phương thức trường hợp ngoại lệ có thể có của quy tắc newData.hasChildren(), điều này sẽ phụ thuộc vào việc được phép xoá.

Quy tắc dựa trên cụm từ tìm kiếm

Mặc dù 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 nhóm nhỏ dữ liệu bằng cách sử dụng tham số truy vấn trong quy tắc của bạn. Sử dụng biểu thức query. trong quy tắc của bạn để cấp quyền đọc hoặc ghi dựa trên tham số truy vấn.

Ví dụ: quy tắc dựa trên truy vấn sau đây sử dụng các quy tắc bảo mật dựa trên người dùng và các quy tắc dựa trên truy vấn để hạn chế quyền truy cập vào dữ liệu trong tập hợp baskets cho 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, 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, những truy vấn không bao gồm các thông số trong quy tắc sẽ không thực hiện được với 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 mà khách hàng tải xuống thông qua thao tác đọc.

Ví dụ: quy tắc sau đây giới hạn quyền đọc chỉ cho 1.000 quy tắc đầu tiên các kết quả của một truy vấn được sắp xếp theo mức độ ư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)

Biểu thức query. sau đây có trong Quy tắc bảo mật cơ sở dữ liệu theo thời gian thực.

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

Các bước tiếp theo

Sau cuộc thảo luận về các điều kiện này, bạn đã có được kiến thức chuyên sâu hơn hiểu 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 cũng như quy trình phát triển, thử nghiệm và triển khai các quy tắc:

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