Phân quyền là một chức năng cực kỳ cơ bản tuy nhiên bao nhiêu năm nay nó vẫn làm khó tất cả các lập trình viên từ mới vào nghề cho đến lành nghề. Young Monkeys hiểu rõ điều này và đã trang bị cho EzyPlatform khả năng phân quyền cho cả admin lẫn web.

Nghiệp vụ phân quyền

Trông vậy thôi nhưng phần quyền có rất nhiều những nghiệp vụ lặt vặt:

  1. Có nhiều quyền.
  2. Có nhiều người dùng.
  3. Một người dùng có thể có nhiều quyền.
  4. Một quyền có thể được gán cho nhiều người dùng.
  5. Các quyền mức khác nhau.
  6. Người có quyền thấp hơn không thể phân quyền cho người có quyền cao hơn.
  7. Quyền cao nhất là super admin.
  8. Quyền cao thứ 2 là DevOps.
  9. Cả admin và DevOps đều có thể truy cập vào mọi API.
  10. Phân quyền chi tiết đến từng API.

Đối với EzyPlatform thì admin và web là 2 thành phần độc lập nên cả 2 thành phần phải đáp ứng được các nghiệp vụ kể trên.

EzyPlatform tổ chức việc phân quyền thế nào?

Phân quyền trong EzyPlatform.png

Như đã đề cập trong bài trước, các API của EzyPlatform sẽ được gom vào các tính năng cho gọn. Sẽ có các API có URI trùng nhau nên cần thiết phải có sự tham gia của phương thức HTTP để phân biệt.

Các bảng tham gia vào việc phân quyền này bao gồm:

Phân quyền trong EzyPlatform tables.png
  1. ezy_admin_role_names: Đây là bảng chứa tên của các quyền trong admin.
  2. ezy_admin_roles: Đây là bảng trung gian giữa các quyền và admin.
  3. ezy_role_features: Đây là bảng chứa toàn bộ tính năng, quyền và thông tin về API.
  4. ezy_user_role_names: Đây là bảng chứa tên của các quyền trong web.
  5. ezy_user_roles: Đây là bảng trung gian giữa các quyền và user.

Mã nguồn của các bảng này như sau:

CREATE TABLE IF NOT EXISTS `ezy_admin_role_names` (
    `id` bigint unsigned NOT NULL AUTO_INCREMENT,
    `name` varchar(45) NOT NULL COLLATE utf8mb4_unicode_520_ci NOT NULL,
    `display_name` varchar(60) COLLATE utf8mb4_unicode_520_ci NOT NULL,
    `priority` int NOT NULL DEFAULT 0,
    `created_at` datetime NOT NULL,
    `updated_at` datetime NOT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `key_name` (`name`),
    UNIQUE KEY `key_display_name` (`display_name`),
    INDEX `index_priority` (`priority`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

CREATE TABLE IF NOT EXISTS `ezy_admin_roles` (
    `role_id` bigint unsigned NOT NULL,
    `admin_id` bigint unsigned NOT NULL,
    `created_at` datetime NOT NULL,
    PRIMARY KEY (`role_id`, `admin_id`),
    INDEX `index_role_id` (`role_id`),
    INDEX `index_admin_id` (`admin_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

CREATE TABLE IF NOT EXISTS `ezy_role_features` (
    `role_id` bigint unsigned NOT NULL,
    `target` varchar(25) COLLATE utf8mb4_unicode_520_ci NOT NULL,
    `feature` varchar(120) COLLATE utf8mb4_unicode_520_ci NOT NULL,
    `feature_uri` varchar(300) COLLATE utf8mb4_unicode_520_ci NOT NULL,
    `feature_method` varchar(25) COLLATE utf8mb4_unicode_520_ci NOT NULL,
    `created_at` datetime NOT NULL,
    PRIMARY KEY (`role_id`, `target`, `feature`, `feature_uri`, `feature_method`),
    INDEX `index_role_id` (`role_id`),
    INDEX `index_role_id_target` (`role_id`, `target`),
    INDEX `index_feature_uri_method` (`target`, `feature_uri`, `feature_method`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

CREATE TABLE IF NOT EXISTS `ezy_user_role_names` (
    `id` bigint unsigned NOT NULL AUTO_INCREMENT,
    `name` varchar(45) NOT NULL COLLATE utf8mb4_unicode_520_ci NOT NULL,
    `display_name` varchar(60) COLLATE utf8mb4_unicode_520_ci NOT NULL,
    `priority` int NOT NULL DEFAULT 0,
    `created_at` datetime NOT NULL,
    `updated_at` datetime NOT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `key_name` (`name`),
    UNIQUE KEY `key_display_name` (`display_name`),
    INDEX `index_priority` (`priority`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

CREATE TABLE IF NOT EXISTS `ezy_user_roles` (
    `role_id` bigint unsigned NOT NULL,
    `user_id` bigint unsigned NOT NULL,
    `created_at` datetime NOT NULL,
    PRIMARY KEY (`role_id`, `user_id`),
    INDEX `index_role_id` (`role_id`),
    INDEX `index_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci;

Làm thế nào để đảm bảo hiệu năng?

Sau khi đã nhìn thấy các bảng, nhiều bạn sẽ nghĩ ngay đến việc, nếu như bất cứ API nào cũng phải bị đánh chặn để kiểm tra quyền, rõ ràng việc phải truy vấn liên tục vào cơ sở dữ liệu, phải join giữa 3 bảng thì chắc EzyPlatform sẽ không chịu nổi.

Quả đúng là như vậy, cơ sở dữ liệu luôn là trái tim của mọi hệ thống, mà khổ cái là hiệu năng của cơ sở dữ liệu là một vấn đề nan giải, vậy nên giải pháp EzyPlatform đưa ra là cache toàn bộ ezy_role_features tương ứng với từng thành phần để đảm bảo hiệu năng, nghĩa là ở admin sẽ cache toàn bộ bản ghi với target = ADMIN còn web thì sẽ cache toàn bộ bản ghi với target = WEB. Mỗi khi có phân quyền xảy ra thì dữ liệu lại được tải lên cache, bạn có thể tham khảo lớp DefaultRoleFeatureService để biết thêm thông tin.

Sử dụng thế nào?

Bạn có thể vào giao diện admin, tương ứng với admin, và người dùng sẽ có bảng phân quyền khác nhau tuy nhiên hình thức sẽ giống nhau.

Screenshot 2024-11-13 at 16.30.41.png

Bạn có thể chọn tích vào một tính năng nó sẽ chọn tất cả API, hoặc chọn riêng từng API sau đó kéo xuống cuối để nhấn lưu lưu. Khi bạn phân quyền ở admin thì những quyền nào admin không có sẽ không hiển thị ở sidebar.

Screenshot 2024-11-13 at 16.30.56.png

Tổng kết

Phân quyền là một trong những tính năng cơ bản nhưng lại rất mất công để làm hoàn thiện, EzyPlatform đã đóng gọi lại tính năng này và hy vọng các nhà phát triển sẽ không cần phải tạo lại cái bánh xe nữa.