Phân quyền user với Laravel và Vue-router

385

Người viết: Mai Trung Đức

Chào mừng các bạn quay trở lại với bài viết của mình. Ở bài này mình sẽ hướng dẫn các bạn cách phân quyền user theo role và permission bằng laravel và giới hạn khả năng truy cập và thao tác của user theo từng route trong Vue-router nhé.

Mô tả một chút về những gì ta sẽ làm. Ở bài này, chúng ta sẽ có các routes, mỗi routes sẽ có 1 số chức năng nhất định. Chỉ các user có role tương ứng mới truy cập được vào từng route, và tại mỗi route, user phải có các permission nhất định để thực hiện được 1 hành động nào đó. Cùng bắt tay vào làm nhé 😉

Setup

Đầu tiên ta tạo mới project Laravel, đặt tên tuỳ ý các bạn, ở đây mình đặt là role_permission_vuenhé:

Sau đó ta tiến hành cài đặt npm:

Cài luôn cả vue-router nhé các bạn:

Ở bài này để đơn giản chúng ta sẽ dùng auth mặc định của Laravel luôn nhé. Các bạn chạy câu lệnh sau:

Sau đó sửa lại tên database ở file .env theo tên các bạn mong muốn nhé

Models & Migrations

Đầu tiên chúng ta sẽ tạo các model RolePermission nhé:

Tiếp theo ta vào database/migrations/create_permissions_table sửa lại hàm up() như sau nhé:

- - - - - - - - - - - - - - - - - - - - Được tài trợ - - - - - - - - - - - - - - - - - - - -

Ở đây ta có 2 trường cần chú ý là name: tên của quyền là gì (ví dụ: được xem user, được thêm bài post,…) và slug là định danh của quyền đó và là duy nhất, mục đích có trường này để sau này mỗi khi cần check ta sử dụng can('slug') trông sẽ thân thiện hơn việc dùng id

Tương tự ta sửa migration của Role nhé:

Giải thích: trong bài này, ta sẽ tách role và permission khỏi nhau. Không giống như bình thường: 1 user có 1 role (admin, mod,…), mỗi role sẽ có 1 số permission, ở đây việc user có role nào và quyền gì 2 điều đó không liên quan tới nhau, các user với các role khác nhau có thể có cùng 1 số permission. Bằng cách này, sau này khi ta muốn giới hạn permission cho mỗi user sẽ dễ hơn, tránh gán thừa thãi quyền cho mỗi user. Ví dụ: ta muốn user A có thêm quyền để xem toàn bộ hoá đơn trong hệ thống, ta chỉ cần thêm quyền view-order thay vì như bình thường ta phải gán cho user đó làm admin để có thể xem, nhưng khi làm admin thì user lại có thể làm được nhiều việc khác nữa ngoài view-order.

Từ đó, ta cần tạo thêm các bảng trung gian để liên hệ giữa user-roleuser-permission. Ta chạy các câu lệnh sau để làm việc đó:

Sau đó ở file migration users_permissions các bạn sửa lại hàm up() như sau:

Bảng này chưa thông tin user sẽ có quyền gì. Do đó ta sẽ có các trường user_idpermission_id. Đồng thời ta thiết lập thêm khoá ngoại từ bảng này tới các bảng users và permission theo idtương ứng, option onDelete('cascade') để khi ở bảng users hoặc permissions ta xoá thì ở bảng này các record cũng xoá theo nhé.

Tương tự các bạn sửa lại file migration users_roles nhé:

Ổn rồi đó, đến đây ta migrate DB nhé:

Nếu bạn nào bị lỗi ...key too long thì mở file app/providers/AppServiceProvider sửa lại như sau nhé:

Sau đó migrate lại nhé.

Tạo Trait

Ta sẽ tạo 1 trait ở trong thư mục app, đặt tên là HasPermissionsTrait, với nội dung như sau nhé:

Sau đó ở model User ta sẽ sử dụng trait này nhé:

Cùng dừng lại 1 chút và xem ta có gì nhé:

  • Ở đây mình dùng Trait để nếu sau này các bạn có thêm các loại model khác kiểu UserTest, Customer,... thì các bạn có thể dùng lại Trait này luôn.
  • Cùng nhìn vào file Trait. Các bạn chú ý $this trỏ về model mà chúng ta sử dụng Trait đó, ở đây $this chính là User nhé. Mọi hàm ở Trait sẽ thao tác trực tiếp trên $user nhé
  • Ở đó ta có hàm roles thể hiện cho 1 user có nhiều role, id của user và role tương ứng được map trong bảng users_roles. Tương tự cho permissions nhé 😃
  • Hàm hasRole check xem user có 1 hoặc 1 số role nhất định. Tham số nhận vào các bạn để ý là ...roles, tức là ở đây các bạn có thể truyền vào bao nhiêu tham số tuỳ ý. Giống như bên Javascript nhé (tuyệt vời ❤). Ví dụ hasRole('create', 'delete', 'view')
  • Hàm givePermissionsTo để thêm permission cho user hiện tại.
  • Còn các hàm khác các bạn chú ý đọc là có thể hiểu nó làm gì nhé. (nếu không thì comment cho mình được biết nhé 😃)

Tiếp theo, để nâng cao hơn xíu, ta sẽ tận dụng can trong Laravel để check quyền, giờ đây thay vì $user->hasPermission('view-orders') ta có thể viết ngắn gọn và ý nghĩa hơn $user->can('view-orders') bằng cách như sau. Ta tạo 1 Provider đặt tên là PermissionsServiceProvider:

Sau đó vào app/providers/PermissionsServiceProvider và sửa lại như sau:

Ở đây trong hàm boot ta lấy toàn bộ Permissionsau đó duyệt qua chúng sử dụng hàm map (ôi lại giống javascript 😃 ). Với mỗi permission định nghĩa nó bằng Gate. Mình biết đoạn này hơi khó hiểu. Để chi tiết hơn, bên trong khối lệnh map sẽ xảy ra như sau với mỗi vòng lặp:

Nhờ thế mà sau này ta mới có thể dùng $user->can('view-orders'), tức với mỗi quyền ta phải định nghĩa nó lại bằng Gate thì mới dùng với can được. Nhưng tin mình đi, đáng để dùng lắm 😉

Sau đó các bạn khai báo PermissionsServiceProvider vào file config/app.php nhé:

Tạo seeder

Tiếp theo chúng ta tạo các file để seed sẵn dữ liệu nhé

Sửa nội dung các file này như sau:

PermissionTableSeeder.php

Ở trên ta có 3 permission:

  • create-tasks: để tạo mới 1 task
  • delete-tasks: xoá task
  • view-users: xem tất cả user RoleTableSeeder.php

Ở đây ta có 2 roledeveloper và manager

UserTableSeeder.php

Ở trên các bạn có thể thấy ta có 5 users, mỗi user có 1 hoặc 1 số permission, hoặc như user manager1 dù là manager nhưng không có permission nào cả.

Xong tất cả các bạn chạy lại DB và seed dữ liệu bằng command:

Option refresh để rollback lại toàn bộ các bảng bạn đã migrate từ trước và migrate lại

Testing

Đến đây ta thử test xem mọi thứ ổn chưa nhé.

Các bạn mở file routes/web.php sửa lại như sau:

Khởi động app php artisan serve đã nhé 😄

Ở đây ta in ra user đăng nhập có quyền create-tasks hay không. Thử đăng nhập với user dev_create@test.com nhé. Chúng ta sẽ thấy in ra màn hình:

Laravel_Var_dump

Tương tự với các user khác, các bạn có thể test thêm nhé.

Trước khi tới Front-end

Ở bài này ta sẽ giả sử user có một số quyền nhất định có thể thêm Task. Vì thế ta tạo thêm model Task nhé.

Sau đó ở file model Task các bạn sửa lại chút như sau:

Ở file migration của Task các bạn sửa lại như sau:

Sau đó chạy: php artisan migrate nhé. Tiếp theo ta tạo TaskController nhé:

Ở file TaskController các bạn sửa lại một số hàm như sau:

Toàn bộ code ở trên đã self-explained tức tự nó đã nói lên nó làm gì các bạn đọc nếu thắc mắc thì comment cho mình nhé.

Chúng ta tạo luôn UserController với nội dung như sau nhé:

VueJS

Ở resources/js/components ta xoá đi file Example Component. Sau đó tạo lần lượt 3 file sau:App.vue:

Task.vue

User.vue

Code của 3 file trên khá đơn giản, chủ yếu CRUD, các bạn tự ngâm cứu nếu có gì thắc mắc thì comment bên dưới nhé.

Tiếp theo ở folder resources/js (cùng cấp với app.js) ta tạo file routes.js với nội dung:

Ở file app.js ta thêm vào:

Các bạn chú ý bên trên mình có user: window.__user__. Ở đây khi làm dự án thật, ta muốn khởi tạo sẵn 1 số giá trị trước khi Vue được khởi tạo, ví dụ ở đây ta muốn lấy sẵn user login ngay khi Vue được khởi tạo. Và thường các giá trị này được trả về cùng với view Laravel. Ta quay trở lại file routes/web.php sửa lại như sau:

Ở trên các bạn có thể thấy khi trả về view welcome ta trả ra luôn cả user vừa login. Ở file welcome.blade.php ta có:

Các bạn chú ý dòng window.__user__ = @json($user), đó chính là dữ liệu ta khởi tạo trước khi Vue được gọi đến, ta phải đặt nó trước <script src="/js/app.js"></script> nhé 😃

Testing

Phù…Nói mãi cũng xong. Ta cùng chạy app lên xem có gì nhé. Các bạn chạy:

Đăng nhập với 1 trong các account ta seed ở bên trên. Ở đây ta chọn account dev_create nhé. Ở đây các bạn để ý ta có thể thêm thoải mái task, nhưng khi thử xoá thì báo lỗi:

Vue_permission

Tương tự khi chuyển sang tab Usersta lại thấy thông báo sau:

Vue_permission

Bởi vì account này chỉ có duy nhất quyền create-tasks nên ta không đươc làm các thao tác khác

Các bạn có thể test với các account khác để thấy rõ kết quả hơn nhé.

Scope Vue Router

Tiếp theo ta muốn giới hạn, chỉ có user có role nhất định mới được truy cập vào route nhất định. Ví dụ chỉ có dev và manager truy cập được vào Tasks nhưng chỉ có manager được truy cập vào Usersdev truy cập vào sẽ bị điều hướng lại trang ban đầu

Ta sửa lại file routes.js một chút nhé:

Ở trên các bạn thấy mỗi route mình thêm trường meta bên trong require user phải có các quyền nhất định mới được truy cập vào.

Bên dưới mình check, trước khi đi vào mỗi route. Nếu như user có quyền phù hợp với route đó thì chạy tiếp, còn không thì chuyển về route '/' (thực ra về route này lại bị check tiếp, nhưng với DB hiện tại thì user nào cũng vào được route này 😃)

Sau đó, các bạn thử đang nhập với tài khoản dev bất kì và thử truy cập vào route danh sách user xem nhé:

Vue_router

Sau khi click vào alert các bạn sẽ được redirect lại route '/' ngay lập tức.

Kết thúc

Bài của mình đến đây là kết thúc. Hi vọng qua bài này các bạn có thể biết được cách phân quyền theo role và permission trong Vue và Laravel, để áp dụng vào công việc của mình.

À có bạn có thể thắc mắc. Cần role làm gì, hoặc permission làm gì, sao không gộp chung làm 1 thôi. Thì các bạn có thể liên hệ thực tế: mục đích của việc này là phân nhóm/phân cấp các vai trò của các cá nhân trong 1 tổ chức, hệ thống, mỗi cá nhân có 1 chức vụ và 1 số quyền hạn nhất định, chứ không đồng nhất chức vụ của tất cả mọi người sau đó mới gán cho mỗi người 1 vài quyền 😃.

Cám ơn các bạn đã theo dõi, nếu có gì thắc mắc thì comment bên dưới để mình được biết nhé. Hẹn gặp lại các bạn ở các bài sau ❤.

Techtalk via Viblo

CHIA SẺ