Ngoài việc mặc định cung cấp các dịch vụ authentication, Laravel cũng cung cấp một cách đơn giản để authorize cho các hành động của người dùng đối với các resource đã có. Giống như authentication, cách tiếp cận authorize của Laravel cũng rất đơn giản và có hai cách chính để authorize một hành động đó là: gates và policies.
Hãy nghĩ về các gates và các policies như là các routes và các controllers. Gates cung cấp một cách tiếp cận đơn giản dựa trên Closure để authorization, trong khi các policies giống như là các controllers, là một nhóm logic sẽ liên quan đến một model hoặc một resource cụ thể. Chúng ta sẽ khám phá các gates trước và sau đó sẽ đến các policies.
Bạn không cần phải chọn giữa sử dụng gates hoặc sử dụng policies khi xây dựng application. Hầu hết các application rất có thể sẽ chứa hỗn hợp cả gates và policies, và điều đó là hoàn toàn tốt! Gates áp dụng cho các hành động không liên quan đến bất kỳ model hoặc resource nào, chẳng hạn như xem dashboard của administrator. Ngược lại, các policies nên được sử dụng khi bạn muốn authorize một hành động cho một model hoặc resource cụ thể.
Gates là các Closure để xác định xem người dùng có được phép thực hiện một hành động nhất định hay không và thường sẽ được định nghĩa trong class App\Providers\AuthServiceProvider
bằng cách sử dụng facade Gate
. Gates luôn nhận một instance user làm tham số đầu tiên của nó và có thể tùy chọn nhận thêm các tham số như Eloquent model có liên quan:
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Gate::define('edit-settings', function ($user) {
return $user->isAdmin;
});
Gate::define('update-post', function ($user, $post) {
return $user->id === $post->user_id;
});
}
Gates cũng có thể được định nghĩa dưới dạng là chuỗi callback Class@method
, giống như cách gọi controller trong route:
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Gate::define('update-post', 'App\Policies\PostPolicy@update');
}
Để authorize cho một hành động thông qua sử dụng gate, bạn cần sử dụng các phương thức allows
hoặc denies
. Lưu ý rằng bạn không cần phải truyền user mà đang login cho các phương thức này. Laravel sẽ tự động truyền user đó vào gate Closure này:
if (Gate::allows('edit-settings')) {
// The current user can edit settings
}
if (Gate::allows('update-post', $post)) {
// The current user can update the post...
}
if (Gate::denies('update-post', $post)) {
// The current user can't update the post...
}
Còn nếu bạn muốn kiểm tra một user cụ thể nào đó có được phép thực hiện một hành động hay không, bạn có thể sử dụng phương thức forUser
trên facade Gate
:
if (Gate::forUser($user)->allows('update-post', $post)) {
// The user can update the post...
}
if (Gate::forUser($user)->denies('update-post', $post)) {
// The user can't update the post...
}
Bạn có thể cấp phép cho nhiều hành động cùng một lúc bằng phương thức any
hoặc none
:
if (Gate::any(['update-post', 'delete-post'], $post)) {
// The user can update or delete the post
}
if (Gate::none(['update-post', 'delete-post'], $post)) {
// The user cannot update or delete the post
}
Nếu bạn muốn thử authorize cho một action và tự động đưa ra một Illuminate\Auth\Access\AuthorizationException
nếu người dùng đó không được phép thực hiện action đã cho, bạn có thể sử dụng phương thức Gate::authorize
. Instance của AuthorizationException
sẽ được tự động chuyển đổi thành response HTTP 403`:
Gate::authorize('update-post', $post);
// The action is authorized...
Các phương thức của gate để authorize các quyền (allows
, denies
, check
, any
, none
, authorize
, can
, cannot
) và các lệnh Blade về authorization (@can
, @cannot
, @canany
) có thể nhận vào một mảng làm tham số thứ hai. Các phần tử trong mảng này được truyền vào dưới dạng tham số cho gate và có thể được sử dụng để thêm thông tin khi đưa ra quyết định authorization:
Gate::define('create-post', function ($user, $category, $extraFlag) {
return $category->group > 3 && $extraFlag === true;
});
if (Gate::check('create-post', [$category, $extraFlag])) {
// The user can create the post...
}
Hiện tại, chúng ta mới chỉ kiểm tra các gate trả về giá trị boolean đơn giản. Tuy nhiên, đôi khi bạn có thể muốn trả về một response chi tiết hơn, chứa cả một thông báo lỗi. Để làm như vậy, bạn có thể trả về Illuminate\Auth\Access\Response
từ gate của bạn:
use Illuminate\Auth\Access\Response;
use Illuminate\Support\Facades\Gate;
Gate::define('edit-settings', function ($user) {
return $user->isAdmin
? Response::allow()
: Response::deny('You must be a super administrator.');
});
Khi trả về một response authorization từ gate của bạn, phương thức Gate::allows
sẽ vẫn trả về một giá trị boolean đơn giản; tuy nhiên, bạn có thể sử dụng phương thức Gate::inspect
để nhận được response authorization đầy đủ do gate trả về:
$response = Gate::inspect('edit-settings', $post);
if ($response->allowed()) {
// The action is authorized...
} else {
echo $response->message();
}
Tất nhiên, khi sử dụng phương thức Gate::authorize
để đưa ra một AuthorizationException
nếu action không được authorize, thông báo lỗi mà được cung cấp bởi response authorize sẽ được truyền tới response HTTP:
Gate::authorize('edit-settings', $post);
// The action is authorized...
Thỉnh thoảng, bạn có thể muốn cho phép tất cả các hành động cho một người dùng cụ thể. Bạn có thể sử dụng phương thức before
để định nghĩa một callback sẽ được chạy trước khi tất cả các authorization khác được check:
Gate::before(function ($user, $ability) {
if ($user->isSuperAdmin()) {
return true;
}
});
Nếu callback before
trả về một kết quả khác null thì kết quả đó sẽ được coi là kết quả của việc kiểm tra.
Bạn có thể sử dụng phương thức after
để định nghĩa một callback sẽ được thực thi sau tất cả các lần authorization check.
Gate::after(function ($user, $ability, $result, $arguments) {
if ($user->isSuperAdmin()) {
return true;
}
});
Tương tự như callback before
, nếu callback after
trả về một kết quả khác null thì kết quả đó sẽ được coi là kết quả của việc kiểm tra.
Các Policy là các class tổng hợp các logic authorization liên quan đến một model hoặc resource cụ thể. Ví dụ: nếu application của bạn là một trang blog, bạn có thể có model Post
và PostPolicy
tương ứng để authorization cho các hành động của người dùng như tạo hoặc cập nhật bài đăng.
Bạn có thể tạo một policy bằng cách sử dụng lệnh artisan make:policy
. Policy được tạo ra sẽ được lưu vào trong thư mục app/Policies
. Nếu thư mục này không tồn tại trong application của bạn, Laravel sẽ tạo nó cho bạn:
php artisan make:policy PostPolicy
Lệnh make:policy
sẽ tạo ra một class policy trống. Nếu bạn muốn tạo ra với một class có các phương thức policy "CRUD" cơ bản, thì bạn có thể thêm một option --model
khi thực hiện lệnh trên:
php artisan make:policy PostPolicy --model=Post
{tip} Tất cả các policy được resolve thông qua Laravel service container, cho phép bạn khai báo bất kỳ phụ thuộc nào mà bạn cần trong hàm constructor của policy để chúng có thể được inject vào cho bạn.
Sau khi policy đã tồn tại, bạn cần phải đăng ký nó. AuthServiceProvider
đi kèm với application Laravel có chứa thuộc tính policies
dùng để ánh xạ các Eloquent model của bạn tới các policy tương ứng với chúng. Đăng ký policy sẽ hướng dẫn cho Laravel khi nào nên sử dụng một policy cho một hành động của một model nhất định:
<?php
namespace App\Providers;
use App\Policies\PostPolicy;
use App\Post;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
Post::class => PostPolicy::class,
];
/**
* Register any application authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
//
}
}
Thay vì đăng ký policy cho model theo cách thủ công, Laravel có thể tự động đăng ký các policy miễn là các model và policy tuân theo một quy tắc đặt tên theo tiêu chuẩn Laravel. Cụ thể, các policy phải nằm trong thư mục Policies
bên trong thư mục chứa các model. Vì vậy, ví dụ: các model có thể được lưu trong thư mục app
trong khi các policy có thể được lưu trong thư mục app/Policies
. Ngoài ra, tên policy phải khớp với tên của model và có hậu tố Policy
. Vì vậy, một model User
sẽ tương ứng với một class policy như sau: UserPolicy
.
Nếu bạn muốn tự cung cấp logic đăng ký policy theo cách của bạn, bạn có thể đăng ký một callback tùy biến bằng cách sử dụng phương thức Gate::guessPolicyNamesUsing
. Thông thường, phương thức này sẽ được gọi từ phương thức boot
trong AuthServiceProvider
trong ứng dụng của bạn:
use Illuminate\Support\Facades\Gate;
Gate::guessPolicyNamesUsing(function ($modelClass) {
// return policy class name...
});
{note} Bất kỳ policy nào được ánh xạ trong
AuthServiceProvider
cũng sẽ được ưu tiên hơn các policy khác được đăng ký tự động.
Khi policy đã được đăng ký, bạn có thể thêm các phương thức cho các hành động mà bạn cần authorize. Ví dụ: hãy định nghĩa thêm một phương thức update
trên PostPolicy
để kiểm tra xem User
hiện tại có được phép cập nhật một Post
đã cho hay không.
Phương thức update
sẽ nhận vào một User
và một Post
làm tham số của nó và sẽ trả về giá trị true
hoặc false
cho biết liệu người dùng đó có được phép cập nhật Post
đã cho hay không. Vì vậy, trong ví dụ dưới đây, nó sẽ kiểm tra bằng cách id
của người dùng đưa vào có khớp với user_id
của bài đăng đã cho hay không:
<?php
namespace App\Policies;
use App\Post;
use App\User;
class PostPolicy
{
/**
* Determine if the given post can be updated by the user.
*
* @param \App\User $user
* @param \App\Post $post
* @return bool
*/
public function update(User $user, Post $post)
{
return $user->id === $post->user_id;
}
}
Bạn có thể tiếp tục định nghĩa thêm các phương thức mà bạn cần authorize cho các hành động khác. Ví dụ: bạn có thể định nghĩa thêm authorize các phương thức view
hoặc delete
dành cho một Post
, ngoài ra bạn cũng có thể tạo thêm bất kỳ các phương thức policy với bất kỳ cái tên nào mà bạn mong muốn.
{tip} Nếu bạn đã sử dụng option
--model
khi tạo policy thông qua Artisan console, thì nó sẽ chứa sẵn các phương thức cho các hành độngviewAny
,view
,create
,update
,delete
,restore
, vàforceDelete
.
Hiện tại, chúng ta mới chỉ kiểm tra các phương thức policy trả về giá trị boolean đơn giản. Tuy nhiên, đôi khi bạn có thể muốn trả về một response chi tiết hơn, chứa cả một thông báo lỗi. Để làm như vậy, bạn có thể trả về Illuminate\Auth\Access\Response
từ phương thức policy của bạn:
use Illuminate\Auth\Access\Response;
/**
* Determine if the given post can be updated by the user.
*
* @param \App\User $user
* @param \App\Post $post
* @return \Illuminate\Auth\Access\Response
*/
public function update(User $user, Post $post)
{
return $user->id === $post->user_id
? Response::allow()
: Response::deny('You do not own this post.');
}
Khi trả về một response authorization từ policy của bạn, phương thức Gate::allows
sẽ vẫn trả về một giá trị boolean đơn giản; tuy nhiên, bạn có thể sử dụng phương thức Gate::inspect
để nhận được response authorization đầy đủ do gate trả về:
$response = Gate::inspect('update', $post);
if ($response->allowed()) {
// The action is authorized...
} else {
echo $response->message();
}
Tất nhiên, khi sử dụng phương thức Gate::authorize
để đưa ra một AuthorizationException
nếu action không được authorize, thông báo lỗi mà được cung cấp bởi response authorize sẽ được truyền tới response HTTP:
Gate::authorize('update', $post);
// The action is authorized...
Có một số phương thức policy chỉ nhận vào user hiện tại đang được authenticate mà không nhận thêm model ở tham số thứ hai. Tình huống này rất phổ biến, nhất là khi authorize cho các hành động create
. Ví dụ: nếu bạn đang tạo một blog, bạn có thể muốn kiểm tra xem người dùng này có được phép tạo một bài đăng hay không.
Khi định nghĩa các phương thức policy không nhận vào tham số thứ hai, ví dụ như phương thức create
, nó sẽ không nhận vào tham số thứ hai. Bạn nên định nghĩa phương thức đó chỉ chấp nhận một user đang được authenticate:
/**
* Determine if the given user can create posts.
*
* @param \App\User $user
* @return bool
*/
public function create(User $user)
{
//
}
Mặc định, tất cả các gate và policy sẽ tự động trả về false
nếu request đó không được tạo bởi một người dùng đã được authenticate. Tuy nhiên, nếu bạn muốn, bạn cũng có thể cho phép các request này đi qua các gate và policy của bạn bằng cách khai báo thêm "optional" hoặc cung cấp giá trị null
mặc định cho định nghĩa tham số user:
<?php
namespace App\Policies;
use App\Post;
use App\User;
class PostPolicy
{
/**
* Determine if the given post can be updated by the user.
*
* @param \App\User $user
* @param \App\Post $post
* @return bool
*/
public function update(?User $user, Post $post)
{
return optional($user)->id === $post->user_id;
}
}
Đối với một số user, bạn có thể muốn cho phép pass qua tất cả các hành động có trong một policy. Để thực hiện điều này, hãy định nghĩa một phương thức before
trong policy. Phương thức before
sẽ được thực thi trước, và sau đó mới đến các phương thức khác có trong policy, nó cho phép bạn pass qua tất cả các hành động, trước khi một phương thức policy được gọi. Tính năng này được sử dụng để cho phép một quản trị viên có thể thực hiện bất kỳ hành động nào mà họ muốn:
public function before($user, $ability)
{
if ($user->isSuperAdmin()) {
return true;
}
}
Nếu bạn muốn từ chối tất cả các quyền cho một user, bạn nên trả về false
từ phương thức before
. Nếu null
được trả về, thì authorization sẽ chuyển tiếp sang phương thức policy.
{note} Phương thức
before
của policy sẽ không được gọi nếu policy đó không chứa phương thức nào mà có tên khớp với tên của hành động đang được kiểm tra.
Model User
đi kèm trong ứng dụng Laravel của bạn có chứa sẵn hai phương thức hữu ích để authorize cho các hành động là: can
và cant
. Phương thức can
nhận vào các hành động mà bạn muốn cho phép và các model có liên quan. Ví dụ: hãy kiểm tra xem người dùng đó có được phép cập nhật model Post
hay không:
if ($user->can('update', $post)) {
//
}
Nếu một policy đã được đăng ký cho một model đã cho, thì phương thức can
sẽ được tự động gọi policy đó và trả về kết quả boolean. Nếu không có policy nào đăng ký cho một model, phương thức can
sẽ cố gắng gọi đến Closure dựa trên Gate khớp với tên hành động đã cho.
Hãy nhớ rằng, một số hành động như create
có thể không yêu cầu tham số thứ hai. Trong những tình huống như này, bạn có thể truyền vào tên của một class cho phương thức can
. Tên class sẽ được sử dụng để xác định policy nào sẽ được sử dụng trong khi authorize cho các hành động:
use App\Post;
if ($user->can('create', Post::class)) {
// Executes the "create" method on the relevant policy...
}
Laravel có chứa sẵn một middleware có thể authorize cho các hành động trước khi request đến được với các route hoặc controller của bạn. Mặc định, middleware Illuminate\Auth\Middleware\Authorize
sẽ được gán với key can
trong class App\Http\Kernel
của bạn. Hãy xem một ví dụ về việc sử dụng middleware can
để authorize một user cập nhật một bài đăng trên blog:
use App\Post;
Route::put('/post/{post}', function (Post $post) {
// The current user may update the post...
})->middleware('can:update,post');
Trong ví dụ trên, chúng ta đã truyền hai tham số vào middleware can
. Đầu tiên là tên của hành động mà chúng ta muốn authorize và thứ hai là tham số route mà chúng ta muốn truyền cho phương thức policy. Trong ví dụ trên, vì chúng ta đang sử dụng liên kết model ẩn, nên một model Post
sẽ được truyền vào phương thức policy. Nếu người dùng hiện tại không được phép thực hiện các hành động đã được truyền vào, thì một HTTP response có status code 403
sẽ được tạo ra và trả vể bởi middleware.
Một lần nữa, một số hành động như create
sẽ không yêu cầu một model. Trong những tình huống như thế này, bạn có thể truyền vào tên một class cho middleware. Tên class này sẽ được sử dụng để xác định policy nào sẽ được sử dụng khi authorize cho các hành động:
Route::post('/post', function () {
// The current user may create posts...
})->middleware('can:create,App\Post');
Ngoài các phương thức hữu ích được cung cấp cho model User
, Laravel còn cung cấp phương thức authorize
cho bất kỳ controller nào mà được extend từ class App\Http\Controllers\Controller
. Giống như phương thức can
, phương thức này chấp nhận tên của một hành động mà bạn muốn authorize và một model ở tham số thứ hai. Nếu hành động không được authorize, phương thức authorize
sẽ tạo ra một Illuminate\Auth\Access\AuthorizationException
, mà trình xử lý exception mặc định của Laravel sẽ chuyển exception đó thành một HTTP response có status code 403
:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
/**
* Update the given blog post.
*
* @param Request $request
* @param Post $post
* @return Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
// The current user can update the blog post...
}
}
Như đã thảo luận ở phía trên, một số hành động như create
có thể không yêu cầu một model ở tham số thứ hai. Trong những tình huống này, bạn nên truyền vào tên của một class cho phương thức authorize
. Tên class sẽ được sử dụng để xác định policy nào sẽ được sử dụng khi authorize cho các hành động:
/**
* Create a new blog post.
*
* @param Request $request
* @return Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create(Request $request)
{
$this->authorize('create', Post::class);
// The current user can create blog posts...
}
Nếu bạn đang sử dụng resource controller, bạn có thể sử dụng phương thức authorizeResource
trong hàm constructor của controller đó. Phương thức này sẽ gán một định nghĩa middleware can
thích hợp cho các phương thức trong resource controller đó.
Phương thức authorizeResource
sẽ nhận tên class của model làm tham số đầu tiên và tên của tham số route chứa ID của model làm tham số thứ hai của nó. Bạn nên đảm bảo resource controller của bạn cũng được tạo cùng với một flag --model
để yêu cầu các phương thức bắt buộc và khai báo thêm cho loại model đó:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function __construct()
{
$this->authorizeResource(Post::class, 'post');
}
}
Các phương thức controller sau sẽ được ánh xạ tới các phương thức policy tương ứng với chúng:
Controller Method | Policy Method |
---|---|
index | viewAny |
show | view |
create | create |
store | create |
edit | update |
update | update |
destroy | delete |
{tip} Bạn có thể sử dụng lệnh
make:policy
với tùy chọn--model
để tạo nhanh một class policy cho một model nhất định:php artisan make:policy PostPolicy --model=Post
.
Khi viết template Blade, bạn có thể hiển thị một phần của trang web cho những người dùng đã được authorize để thực hiện một số hành động nhất định. Ví dụ: bạn có thể muốn hiển thị một form cập nhật cho một bài đăng chỉ khi người dùng đó thực sự có quyền cập nhật bài đăng. Trong những tình huống như thế này, bạn có thể sử dụng lệnh @can
và @cannot
:
@can('update', $post)
<!-- The Current User Can Update The Post -->
@elsecan('create', App\Post::class)
<!-- The Current User Can Create New Post -->
@endcan
@cannot('update', $post)
<!-- The Current User Cannot Update The Post -->
@elsecannot('create', App\Post::class)
<!-- The Current User Cannot Create A New Post -->
@endcannot
Các lệnh này là các shortcut thuận tiện để không phải viết các câu lệnh như @if
và @unless
. Các câu lệnh @can
và @cannot
ở trên có thể lần lượt được dịch sang các câu lệnh if như sau:
@if (Auth::user()->can('update', $post))
<!-- The Current User Can Update The Post -->
@endif
@unless (Auth::user()->can('update', $post))
<!-- The Current User Cannot Update The Post -->
@endunless
Bạn cũng có thể kiểm tra xem người dùng có những quyền nào từ một danh sách các quyền. Để thực hiện việc này, hãy sử dụng lệnh @canany
:
@canany(['update', 'view', 'delete'], $post)
// The current user can update, view, or delete the post
@elsecanany(['create'], \App\Post::class)
// The current user can create a post
@endcanany
Giống như hầu hết các phương thức authorization khác, bạn có thể truyền tên class cho các lệnh @can
và @cannot
nếu hành động đó không yêu cầu một model:
@can('create', App\Post::class)
<!-- The Current User Can Create Posts -->
@endcan
@cannot('create', App\Post::class)
<!-- The Current User Can't Create Posts -->
@endcannot
Khi authorize các action bằng policy, bạn có thể truyền một mảng làm tham số thứ hai cho hàm và các helper authorize khác nhau. Phần tử đầu tiên trong mảng sẽ được sử dụng để xác định policy nào sẽ được gọi, trong khi phần tử còn lại của mảng được truyền dưới dạng tham số cho phương thức policy và có thể được sử dụng để thêm thông tin khi đưa ra quyết định authorize. Ví dụ: hãy xem xét định nghĩa phương thức PostPolicy
sau đây có chứa tham số thêm $category
bổ sung:
/**
* Determine if the given post can be updated by the user.
*
* @param \App\User $user
* @param \App\Post $post
* @param int $category
* @return bool
*/
public function update(User $user, Post $post, int $category)
{
return $user->id === $post->user_id &&
$category > 3;
}
Khi thử xác định xem người dùng hiện tại có thể cập nhật một bài đăng nhất định hay không, chúng ta có thể gọi phương thức policy này như sau:
/**
* Update the given blog post.
*
* @param Request $request
* @param Post $post
* @return Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(Request $request, Post $post)
{
$this->authorize('update', [$post, $request->input('category')]);
// The current user can update the blog post...
}
entry