0

Phân quyền hệ thống: Cú lừa mang tên cột role và sự thức tỉnh với User - Role - Permission.

Chào anh em! Trong chuỗi bài trước, chúng ta đã bàn về cách xử lý phân quyền trên Frontend (View-level Authz) và tối ưu form. Hôm nay, mình xin phép rủ anh em lặn sâu xuống tầng đáy của hệ thống: Database.

Chủ đề hôm nay là một bài toán kinh điển mà 100% các dự án từ bé đến lớn đều phải đối mặt: Thiết kế mô hình dữ liệu phân quyền. Nếu bạn làm sai từ bước này, thì mọi pattern ở Frontend hay Backend đều vô nghĩa. Hãy để mình kể cho anh em nghe về sự tiến hóa (và cả sự đau khổ) trong tư duy thiết kế Database phân quyền của mình.

1. Mở bài: Sự bùng nổ của những cột Boolean và "Bãi rác" If-Else

Nhớ lại cái thời chập chững nhận dự án freelance đầu tiên, requirement rất đơn giản: "Web có người dùng bình thường và Admin". Quá dễ! Mình mở table users, thêm ngay một cột thần thánh: is_admin (TINYINT - Default 0).

Tháng thứ 2, khách bảo: "Thêm cho anh quyền Quản lý (Manager) nha, tụi này được duyệt bài nhưng không được xóa User như Admin". Thế là mình bỏ cột is_admin, thay bằng cột role (ENUM: 'user', 'manager', 'admin').

Đến tháng thứ 4, ác mộng thực sự ập đến: "Công ty anh mới có thêm đội Marketing, đội này được quyền đăng bài, không được duyệt bài, nhưng lại được quyền xem thống kê (vốn trước giờ chỉ Admin có)".

Lúc này, cái cột role trở nên vô dụng. Để chống cháy, mình bắt đầu nhét thêm một nùi cột vào table users: can_post_article, can_view_stats, can_approve_post... Bảng users của mình phình to thành một con quái vật với 20

cột boolean. Ở phía Backend, code của mình trông như một bãi rác thực sự:

if ($user->role == 'admin' || $user->role == 'manager' || $user->can_approve_post) {
    // Duyệt bài...
}

Mỗi lần khách hàng muốn thêm một chức năng mới hoặc tạo ra một chức danh (role) mới, mình lại toát mồ hôi hột đi sửa database và grep (tìm kiếm) toàn bộ source code để update các lệnh if-else. Mình đã tự đào một cái hố chôn mình!

2. Hành trình giác ngộ: Mô hình RBAC kinh điển (Role-Based Access Control)

Đến khi chịu không nổi technical debt, mình phải đập đi xây lại. Và "đấng cứu thế" xuất hiện mang tên: Mô hình User -> Roles -> Permissions (RBAC).

Thay vì gán trực tiếp quyền hạn cho User, hoặc hardcode quyền theo tên Role trong code, RBAC tách biệt hoàn toàn danh tính (Identity) và thẩm quyền (Authority) thông qua một cấu trúc 5 bảng (5 tables) kinh điển:

Mô hình dữ liệu (ERD cơ bản):

  1. users (Bảng người dùng: id, name, email...)
  2. roles (Bảng nhóm quyền/chức danh: id, name - VD: Admin, Editor, Marketing)
  3. permissions (Bảng các quyền chi tiết: id, name - VD: edit_post, delete_user, view_stats)

Và 2 bảng trung gian (Pivot tables) để giải quyết mối quan hệ Nhiều - Nhiều (Many-to-Many):

  1. user_roles (user_id, role_id): Một user có thể giữ nhiều Role.
  2. role_permissions (role_id, permission_id): Một Role được cấu thành từ nhiều Permission.

Sự thay đổi mang tính cách mạng trong tư duy:

Nhờ mô hình này, Code của chúng ta sẽ không bao giờ quan tâm đến Role nữa, mà chỉ kiểm tra Permission.

Role bây giờ chỉ đóng vai trò là một "cái rổ" (container) chứa các permissions để gán cho User nhanh hơn mà thôi.

Hãy nhìn sự khác biệt trong code Backend:

Trước khi có RBAC (Kiểm tra Role - Tồi tệ):

// Hardcode tên Role trong code. Nếu cty đổi tên chức danh là toang.
if (in_array($user->role, ['SuperAdmin', 'ContentManager'])) {
    $post->delete();
}

Sau khi có RBAC (Kiểm tra Permission - Tuyệt vời):

// Code ngu ngơ không cần biết user là Giám đốc hay Thực tập sinh.
// Chỉ cần mày có cái "giấy phép" mang tên 'delete_post' là tao cho qua!
if ($user->hasPermission('delete_post')) {
    $post->delete();
}

Nếu một ngày khách hàng bảo: "Cho đội Marketing quyền xóa bài nhé". Mình KHÔNG CẦN CHẠM VÀO 1 DÒNG CODE NÀO. Mình chỉ cần vào giao diện Admin, tick thêm quyền delete_post cho Role Marketing (bản chất là insert 1 dòng vào bảng role_permissions). Hệ thống tự động thích ứng. Thật sự là một cảm giác "phê" chữ ê kéo dài!

3. Bài học xương máu rút ra (The Takeaways)

  1. Role is just a group of Permissions (Role chỉ là cái hộp chứa quyền): Đừng bao giờ viết logic if (role === 'admin') trong source code. Hãy viết if (can === 'do_something').
  2. Khả năng mở rộng (Scalability): Hệ thống 5 bảng này cho phép bạn xây dựng một màn hình "Quản lý phân quyền" cực kỳ xịn sò, nơi Admin có thể tự do tạo Role mới, gán quyền cho Role đó bằng checkbox mà không cần Dev phải can thiệp.
  3. Hiệu năng (Performance): Dù dùng 5 bảng, nhưng thực tế với các framework hiện tại (như package spatie/laravel-permission), các quyền của user đã được cache lại ở Redis hoặc Memory. Đừng lo lắng về việc phải JOIN 5 bảng trong mỗi request.

Lời kết

Từ việc lạm dụng những cột Boolean ngây ngô cho đến việc nắm vững mô hình RBAC 5 bảng, đó là một sự trưởng thành bắt buộc của mọi backend developer. Nó giúp hệ thống của bạn linh hoạt, dễ bảo trì và "sống sót" trước những yêu cầu thay đổi chóng mặt của khách hàng.

Tuy nhiên, RBAC kinh điển vẫn có một điểm yếu chết người! Nó chỉ trả lời được câu hỏi: "User có quyền sửa bài viết không?", chứ KHÔNG trả lời được câu hỏi: "User có quyền sửa bài viết CỦA NGƯỜI KHÁC hay không?" (Sửa bài của chính mình thì được, của người khác thì cấm).

Anh em thường xử lý bài toán "Quyền sở hữu" (Ownership) này như thế nào? Chuyển sang mô hình ABAC/PBAC (Attribute-based) hay dùng Policy layer trong code? Cùng comment chém gió phía dưới nhé!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí