
Chào các "coder nhí" và cả những "lão làng" đang "vật lộn" với code! Anh Creyt đây, hôm nay chúng ta sẽ cùng "mổ xẻ" một khái niệm cực kỳ quan trọng trong thế giới lập trình web nói chung và Laravel nói riêng: Authorization – hay còn gọi là Phân quyền. Đừng nhầm lẫn với Authentication (Xác thực) nhé, đó là câu chuyện "Bạn là ai?", còn Authorization là "Bạn được làm gì?".
Authorization là gì và tại sao chúng ta cần nó?
Tưởng tượng thế này: Bạn có một căn nhà "full nội thất" (ứng dụng web của bạn đó). Authentication giống như việc bạn có chìa khóa để mở cửa chính – nó xác định bạn là chủ nhà, chứ không phải một "kẻ đột nhập". Nhưng một khi đã vào nhà, bạn có được phép "đập phá bức tường", "thay đổi thiết kế nội thất" hay chỉ được "ngồi xem TV" thôi? Đó chính là lúc Authorization "ra tay"!
Authorization trong Laravel là cơ chế cho phép chúng ta kiểm soát ai có quyền truy cập vào một tài nguyên nhất định hoặc thực hiện một hành động cụ thể. Nó giống như một "bộ phận an ninh nội bộ" của ứng dụng, đảm bảo rằng chỉ những người dùng có "quyền hạn" phù hợp mới được phép "đụng chạm" vào những thứ "nhạy cảm" hay thực hiện các "tác vụ đặc biệt".
Trong Laravel, chúng ta có hai "công cụ" chính để triển khai Authorization một cách "thanh lịch" và "hiệu quả": Gates và Policies.
1. Gates: "Người gác cổng" đa năng
Gates (Cổng) là những "người gác cổng" linh hoạt, dùng để định nghĩa các quyền hạn đơn giản, không gắn liền trực tiếp với một model cụ thể. Chúng hoạt động dựa trên các closures (hàm ẩn danh) và rất tiện lợi cho các quyền hạn "chung chung" hoặc khi bạn cần kiểm tra một điều kiện phức tạp mà không muốn tạo hẳn một class riêng.
Để làm gì?
- Kiểm tra quyền truy cập vào một tính năng tổng thể (ví dụ: "có thể quản lý người dùng").
- Kiểm tra một điều kiện đặc biệt không liên quan đến một model cụ thể.
Cách dùng:
Bạn định nghĩa Gates trong file app/Providers/AuthServiceProvider.php, bên trong phương thức boot().
// app/Providers/AuthServiceProvider.php
use Illuminate\Support\Facades\Gate;
use App\Models\User;
use App\Models\Post;
class AuthServiceProvider extends ServiceProvider
{
// ...
public function boot()
{
$this->registerPolicies();
// Định nghĩa một Gate đơn giản: 'edit-settings'
// Chỉ cho phép user có role 'admin' chỉnh sửa cài đặt
Gate::define('edit-settings', function (User $user) {
return $user->role === 'admin';
});
// Định nghĩa một Gate phức tạp hơn: 'update-post'
// Cho phép user cập nhật bài viết nếu họ là chủ bài viết đó
Gate::define('update-post', function (User $user, Post $post) {
return $user->id === $post->user_id;
});
}
}
Sử dụng Gate:
Trong Controller:
// app/Http/Controllers/SettingsController.php
use Illuminate\Support\Facades\Gate;
class SettingsController extends Controller
{
public function edit()
{
// Cách 1: Sử dụng Gate::allows()
if (Gate::allows('edit-settings')) {
// Người dùng có quyền chỉnh sửa cài đặt
return view('settings.edit');
}
// Cách 2: Sử dụng Gate::denies() hoặc abort(403)
// Gate::denies('edit-settings') là ngược lại của Gate::allows()
// abort(403) sẽ tự động ném ra lỗi 403 Forbidden nếu user không có quyền
Gate::authorize('edit-settings'); // Dễ hơn nhiều!
return view('settings.edit');
}
public function update(Post $post)
{
// Dùng Gate với tham số model
Gate::authorize('update-post', $post);
// ... logic cập nhật bài viết ...
return redirect()->route('posts.show', $post)->with('success', 'Bài viết đã được cập nhật!');
}
}
Trong Blade (View):
<!-- resources/views/settings/edit.blade.php -->
@can('edit-settings')
<a href="#" class="btn btn-primary">Chỉnh sửa cài đặt hệ thống</a>
@endcan
<!-- resources/views/posts/show.blade.php -->
@can('update-post', $post)
<a href="{{ route('posts.edit', $post) }}" class="btn btn-warning">Sửa bài viết</a>
@endcan
@cannot('update-post', $post)
<p>Bạn không có quyền sửa bài viết này.</p>
@endcannot

2. Policies: "Sổ tay quy tắc" cho từng đối tượng
Policies (Chính sách) là những "sổ tay quy tắc" chuyên biệt, được thiết kế để quản lý quyền hạn cho một model cụ thể. Nếu Gates là "người gác cổng" tổng quát, thì Policies giống như một "bộ quy tắc nội bộ" chi tiết cho từng loại tài sản (ví dụ: một bộ quy tắc cho Post, một bộ khác cho User, v.v.). Chúng rất phù hợp cho các thao tác CRUD (Create, Read, Update, Delete) trên các tài nguyên.
Để làm gì?
- Kiểm soát quyền truy cập và thao tác trên một model cụ thể (ví dụ: ai có thể xem, tạo, sửa, xóa một
Post). - Giúp code sạch sẽ, dễ đọc và dễ bảo trì hơn khi bạn có nhiều quyền hạn liên quan đến một model.
Cách dùng:
Bước 1: Tạo Policy
Bạn dùng Artisan để tạo một Policy. Ví dụ, với model Post:
php artisan make:policy PostPolicy --model=Post
Lệnh này sẽ tạo file app/Policies/PostPolicy.php với các phương thức CRUD cơ bản đã được "nhúng" sẵn.
Bước 2: Đăng ký Policy
Bạn cần "nói" cho Laravel biết model nào sẽ được quản lý bởi Policy nào. Điều này cũng được thực hiện trong app/Providers/AuthServiceProvider.php:
// app/Providers/AuthServiceProvider.php
use App\Models\Post;
use App\Policies\PostPolicy;
class AuthServiceProvider extends ServiceProvider
{
/**
* The model to policy mappings for the application.
*
* @var array<class-string, class-string>
*/
protected $policies = [
Post::class => PostPolicy::class,
];
public function boot()
{
$this->registerPolicies();
// ... các Gate khác nếu có ...
}
}
Bước 3: Viết logic trong Policy
Trong PostPolicy.php, bạn sẽ định nghĩa các phương thức tương ứng với các hành động. Mỗi phương thức sẽ nhận User và Post (hoặc chỉ User nếu là hành động create):
// app/Policies/PostPolicy.php
use App\Models\User;
use App\Models\Post;
class PostPolicy
{
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
// Mọi người đều có thể xem danh sách bài viết
return true;
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Post $post): bool
{
// Mọi người đều có thể xem một bài viết cụ thể
return true;
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
// Chỉ user đã đăng nhập mới có thể tạo bài viết
return (bool) $user->id;
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Post $post): bool
{
// Chỉ chủ bài viết mới có thể cập nhật
return $user->id === $post->user_id;
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Post $post): bool
{
// Chỉ chủ bài viết mới có thể xóa
return $user->id === $post->user_id;
}
/**
* Optional: "Super Admin" override (trước khi các phương thức khác được gọi)
*/
public function before(User $user, string $ability)
{
if ($user->role === 'super-admin') {
return true; // Super admin có thể làm mọi thứ!
}
}
}
Sử dụng Policy:
Trong Controller:
Laravel cung cấp một trait AuthorizesRequests cho các controller, giúp bạn dễ dàng sử dụng Policies.
// app/Http/Controllers/PostController.php
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function __construct()
{
// Sử dụng middleware 'can' để bảo vệ toàn bộ controller hoặc từng phương thức
// 'create' là tên phương thức trong Policy, 'post' là tên tham số route (nếu có)
$this->middleware('auth')->except(['index', 'show']); // Đảm bảo user đã đăng nhập
$this->middleware('can:create,App\Models\Post')->only('create', 'store');
$this->middleware('can:update,post')->only('edit', 'update');
$this->middleware('can:delete,post')->only('destroy');
}
public function index()
{
$posts = Post::all();
return view('posts.index', compact('posts'));
}
public function create()
{
// Không cần gọi $this->authorize('create', Post::class) nếu đã dùng middleware
return view('posts.create');
}
public function edit(Post $post)
{
// Laravel sẽ tự động gọi PostPolicy@update và truyền $user, $post vào
$this->authorize('update', $post);
return view('posts.edit', compact('post'));
}
public function destroy(Post $post)
{
$this->authorize('delete', $post);
$post->delete();
return redirect()->route('posts.index')->with('success', 'Bài viết đã bị xóa!');
}
}
Trong Blade (View):
Cũng tương tự như Gate, bạn dùng @can với tên phương thức của policy và model:
<!-- resources/views/posts/index.blade.php -->
@can('create', App\Models\Post::class)
<a href="{{ route('posts.create') }}" class="btn btn-success">Tạo bài viết mới</a>
@endcan
<!-- resources/views/posts/show.blade.php -->
@can('update', $post)
<a href="{{ route('posts.edit', $post) }}" class="btn btn-warning">Sửa bài viết</a>
@endcan
@can('delete', $post)
<form action="{{ route('posts.destroy', $post) }}" method="POST" style="display:inline;">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger" onclick="return confirm('Bạn có chắc chắn muốn xóa bài viết này?')">Xóa bài viết</button>
</form>
@endcan
Mẹo vặt và Best Practices từ "lão làng" Creyt:
-
Khi nào dùng Gate, khi nào dùng Policy?
- Policies là "công cụ vàng" khi bạn cần kiểm soát quyền hạn cho một model cụ thể (ví dụ:
Post,User,Product). Hãy nghĩ đến các thao tác CRUD. Policies giúp code của bạn gọn gàng, có tổ chức hơn nhiều. - Gates là lựa chọn "tiện lợi" cho các quyền hạn không gắn liền với model nào, hoặc các quyền hạn "chung chung" của hệ thống (ví dụ: "có quyền truy cập trang admin", "có thể xem báo cáo tài chính"). Chúng cũng hữu ích khi bạn cần kiểm tra điều kiện rất đơn giản, chỉ cần một hàm closure là đủ.
- Policies là "công cụ vàng" khi bạn cần kiểm soát quyền hạn cho một model cụ thể (ví dụ:
-
Sử dụng
beforetrong Policy: Nếu bạn có "super admin" (quản trị viên tối cao) có quyền làm mọi thứ, hãy dùng phương thứcbeforetrong Policy. Nó sẽ được gọi trước bất kỳ phương thức kiểm tra quyền nào khác, và nếu nó trả vềtrue, quyền sẽ được cấp ngay lập tức mà không cần kiểm tra thêm. Tiết kiệm thời gian xử lý! -
Hạn chế logic trong Controller: Đừng "nhồi nhét" logic kiểm tra quyền vào Controller. Hãy "đẩy" chúng vào Gates hoặc Policies. Controller của bạn sẽ "thon gọn" và dễ đọc hơn rất nhiều.
-
Middleware
can: Đây là "vũ khí bí mật" để bảo vệ các route một cách "thanh lịch". Thay vì gọiGate::authorize()hay$this->authorize()trong mỗi phương thức controller, hãy dùngmiddleware('can:ability,model_parameter')ngay trong constructor của controller hoặc trong fileweb.php. -
Tên quyền rõ ràng: Đặt tên cho Gates và các phương thức trong Policies thật rõ ràng, dễ hiểu (ví dụ:
update-postthay vìup_p).
Ứng dụng thực tế: "Đời sống" của Authorization
Authorization "hiện diện" khắp mọi nơi, từ những "ông lớn" đến những "startup nhỏ bé":
- Hệ thống quản lý nội dung (CMS) / Blog (WordPress, Medium, Laravel Forge): Chỉ tác giả mới được sửa bài viết của họ, biên tập viên mới được duyệt và xuất bản, quản trị viên mới được quản lý người dùng và cài đặt hệ thống.
- Sàn thương mại điện tử (Shopee, Lazada, Tiki): Chỉ người bán mới được thêm, sửa, xóa sản phẩm của họ. Khách hàng chỉ được xem, thêm vào giỏ hàng và đặt mua. Admin có thể quản lý tất cả sản phẩm, đơn hàng, người dùng.
- Mạng xã hội (Facebook, Twitter): Chỉ bạn mới được xóa bài đăng của mình, sửa thông tin cá nhân. Bạn bè chỉ được xem, bình luận.
- Hệ thống quản lý dự án (Jira, Trello): Chỉ thành viên trong dự án mới được xem các task. Chỉ người được giao task mới được thay đổi trạng thái task. Chỉ quản lý dự án mới được thêm/xóa thành viên.
Bạn thấy đó, Authorization không chỉ là một khái niệm "trừu tượng" mà là một "trụ cột" không thể thiếu trong mọi ứng dụng web hiện đại. Nắm vững nó, bạn không chỉ "nâng tầm" kỹ năng lập trình của mình mà còn "bảo vệ" ứng dụng của mình khỏi những "sai lầm" không đáng có. Chúc các bạn "code" vui vẻ và "bảo mật" thành công!
Thuộc Series: Lavarel
Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!