Phân quyền

Phân quyền là một trong những bài toán quan trọng nhất trong phát triển phần mềm, nó quyết định ai được quyền thực hiện chức nào trên tập dữ liệu nào. Khi phần mềm mở rộng thì phân quyền càng phức tạp, đồi hỏi một bộ phân quyền có thể linh động đáp ứng được yêu cầu mà không phải chỉnh sửa code quá nhiều

1. Access Control List (ACL)


Đây là cách phân quyền đơn giản nhất, các đối tượng và quyền được quản lý bởi một ma trận.

Ưu điểm: Đơn giản, dễ cài đặt

Nhược điểm:

  • Khi hệ thống lớn hơn đối tượng và số lượng quyền trở nên rất lớn lúc này quản lý ma trận rất khó khăn.
  • Không đáp ứng được yêu cầu về phân quyền truy cập dữ liệu

2. Role Based Access Control (RBAC)


Khi ma trận ACL ở mục 1 quá lớn, người ta sẽ gom nhóm những đối tượng có cùng vai trò và quyền rồi gán vào một role, sau đó phân quyền trên role này. Cách này sẽ làm giảm độ phức tạp của ma trận. Các quyền của role có tính kế thừa ví dụ Sale Leader sẽ bao gồm quyền của Sale Manager.

Ưu điểm:

  • RBAC đáp ứng được tốt các yêu cầu phân quyền, làm giảm được độ phức tạp của ma trận phân quyền
  • Thời gian thiết lập phân quyền ngắn hơn so với ACL

Nhược điểm:

  • Phân quyền không được linh động và mịn, vd: Sale Manager chỉ được truy cập vào dữ liệu của khách hàng cùng khu vực của mình. Sale chỉ được truy cập vào dữ liệu khách hàng do mình tạo.

3. Attribute Based Access Control (ABAC)


ABAC phần quyền dựa trên các attribute (vd: user.id, user.role, user.region, customer.region, customer.region ...), các attribute này là dynamic không cố định

Các attribute trong ABAC được phân thành các category sau:

  • Subject: là đối tượng truy cập đến chức năng hoặc dữ liệu (vd: user, role)
  • Resource: Là đối tượng mà Subject truy cập tới (vd: product, customer...)
  • Action: Là hành động mà Subject thực hiện trên Resource (vd: read, update ...)
    Ví dụ ta có một policy sau: User được truy cập những Customer mà mình tạo
    Policy được định nghĩa dưới dạng JSON như sau:
{
  "id": "policy-1",
  "name": "Test policy",
  "description": "",
  "effect": "allow",
  "priority": 0,
  "subjects": ["user:*"],
  "resources": ["customer:*"],
  "actions": [ "customer:read"],
  "conditions": {
    "function": "and",
    "value": [
      {
        "function": "equal",
        "attribute": "User.Id|integer",
        "category": "subject",
        "value": {
          "attribute": "Customer.OwnerId|integer",
          "category": "resource"
        }
      }
    ]
  }
}

Giờ ta có một policy khác: User có role là "Manager" được quyền truy cập tất cả khách hàng có trạng thái là "active" trong khu vực của mình
Policy này được định nghĩa lại như sau:

{
  "id": "policy-2",
  "name": "Test policy",
  "description": "",
  "effect": "allow",
  "priority": 0,
  "subjects": {
    "function": "in",
    "attribute": "User.Role|integer",
    "category": "subject",
    "value": ["Manager"]
  },
  "resources": {
    "function": "equal",
    "attribute": "Customer.Status|string",
    "category": "resource",
    "value": "active"
  },
  "actions": [ "customer:read"],
  "conditions": {
    "function": "and",
    "value": [
      {
        "function": "equal",
        "attribute": "User.RegionId|integer",
        "category": "subject",
        "value": {
          "attribute": "Customer.RegionId|integer",
          "category": "resource"
        }
      }
    ]
  }
}

Ta thấy 2 ví dụ trên thì việc phân quyền trở nên linh động mà chặt chẽ hơn
Một policy bao gồm các thành phần chính sau:

  • Subject: đối tượng truy cập dữ liệu
  • Resource: dữ liệu được truy cập tới
  • Action: Hành động mà Subject thực hiện trên Resource
  • Effect: Allow, Deny
  • Condition: điều kiện kèm theo để thu hẹp phạm vi truy cập (vd: giới hạn thời gian, địa điểm, bussiness rule ...)

Có 2 câu hỏi phổ biến trong một hệ thống phân quyền:
Câu hỏi 1: User A này có được quyền truy cập vào Customer X không? Trường hợp này chỉ cần quét tất cả các policy liên quan và thực thi sẽ cho ra câu trả lời
Câu hỏi 2: Có bao nhiêu Customer mà User A được quyền truy cập?
Cách thứ nhất: Lấy tất cả Customer lên, với mỗi Customer ta hỏi câu hỏi 1 thì sau đó sẽ lấy được tập Customer mà User A được quyền truy cập, nhưng bài toán đặt ra là nếu số lượng Customer nhiều thì làm cách này sẽ ảnh hưởng tới hiệu năng của hệ thống.
Cách thứ hai: Với mỗi policy ta thực hiện cache thành một ma trận phân quyền (ACL) gồm (User, Resource, Action, Effect), đây là cách hiện tại IDTEK đang sử dụng cho các dự án. Nhưng nếu số lượng quyền, user, subject tăng lên, bảng cache và thời tạo ra cache sẽ tăng theo cấp số nhân gây ảnh hưởng tới hiệu năng.
Cách thứ ba: Phát triển hệ thống Reverse Query, từ những policy trên ta sẽ tạo Query tương ứng. Ví dụ: Viết lại 2 policy ở trên thành Reverse Query

Trường hợp: (UserId = 10, Role = ["Leader"], RegionId = 5), sẽ viết lại 2 policy thành Reverse Query như sau:

Select id from customer where customer.ownerId = 10
Trường hợp: (UserId = 10, Role = ["Manager"], RegionId = 5), sẽ viết lại 2 policy thành Reverse Query như sau:

Select id from customer where customer.ownerId = 10 or (customer.status = 'active' and customer.RegionId = 5)

Developer sẽ append câu Reverse Query trên vào câu Query truy vấn dữ liệu của mình:

Select * from customer where id in (Select id from customer where customer.ownerId = 10 or (customer.status = 'active' and customer.RegionId = 5))

Làm cách này sẽ tạo sự linh động và hiệu năng cho hệ thống.