Authorization trong Laravel
Bài đăng này đã không được cập nhật trong 7 năm
I. Giới thiệu
Ngoài việc cung cấp các service authenticatioin
, Laravel cũng cung cấp một cách đơn giản để tổ chức các logic cấp quyền và điều khiển việc truy cập vào tài nguyên. Có nhiều methods và helpers hỗ trợ bạn trong việc tổ chức việc cấp quyền của bạn và chúng ta sẽ đi qua từng phần của chúng trong tài liệu này.
II. Định nghĩa các Abilities
Cách đơn giản nhất để xác định nếu một user có thể thực hiện một hành động đã cho là định nghĩa ra một "ability" bằng cách sử dụng class Illuminate\Auth\Access\Gate
. AuthServiceProvider
cái mà cùng với Laravel phục vụ như một nơi để định nghĩa tất cả các abilities
cho ứng dụng của bạn. Ví dụ, hãy định nghĩa một update-post
ability nhận User
hiện tại và một Post model
. Với ability này, chúng ta sẽ xác định nếu id của user trùng với user_id của post.
<?php
namespace App\Providers;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* Register any application authentication / authorization services.
*
* @param \Illuminate\Contracts\Auth\Access\Gate $gate
* @return void
*/
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
$gate->define('update-post', function ($user, $post) {
return $user->id == $post->user_id;
});
}
}
Chú ý rằng chúng ta đã không kiểm tra nếu $user
đã cho là không NULL
. Gate sẽ tự động trả về false
cho tất cả abilities
khi có một user chưa được xác thực hoặc một user được chỉ định mà không sử dụng forUser
method.
Class Based Abilities
Ngoài ra để đăng kí Closures
như là authorization callbacks, bạn có thể đăng kí các class methods bằng cách truyền vào một string gồm tên class và method. Khi cần thiết, class sẽ được resolved thông qua service container
:
$gate->define('update-post', 'Class@method');
Bỏ Qua Authorization Checks
Đôi khi, bạn có thể muốn cấp toàn bộ abilities
cho một user nào đó. Trong trường hợp này, sử dụng method before để định nghĩa một callback mà được chạy trước tất cả các authorization checks:
$gate->before(function ($user, $ability) {
if ($user->isSuperAdmin()) {
return true;
}
});
Nếu callback before
trả về kết quả non-null, kết quả đó sẽ được đại diện cho kết quả của việc kiểm tra.
Bạn có thể sử dụng method after
để định nghĩa một callback thực thi sau mỗi authorization check. Tuy nhiên bạn không thể thay đổi kết quả của việc kiểm tra authorization từ after callback
:
$gate->after(function ($user, $ability, $result, $arguments) {
//
});
Kiểm Tra Abilities
Thông Qua Gate Facade
Một khi ability
đã được định nghĩa, chúng ta có thể "kiểm tra" nó bằng nhiều cách khác nhau. Đầu tiên, chúng ta có thể sử dụng check
, allows
hoặc denies methods
trong Gate facade. Tất cả những phương thức này nhận tên của ability và các đối số mà sẽ được truyền vào ability's callback. Bạn không cần truyền vào user hiện tại vào các methods này, khi mà Gate
sẽ tự động thêm user vào trước các đối số được truyền vào callback. Vì vậy khi kiểm tra update-post
ability chúng ta đã định nghĩa lúc trước, chúng ta chỉ cần truyền một Post
instance vào denies method:
<?php
namespace App\Http\Controllers;
use Gate;
use App\User;
use App\Post;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* Update the given post.
*
* @param int $id
* @return Response
*/
public function update($id)
{
$post = Post::findOrFail($id);
if (Gate::denies('update-post', $post)) {
abort(403);
}
// Update Post...
}
}
Tất nhiên, method allows
đơn giản là ngược lại của method denies, và trả về true
nếu hành động được cấp quyền. Method check là một alias
của method allows.
Kiểm Tra Abilities Cho User Xác Định
Nếu bạn muốn sử dụng Gate facade
để kiểm tra một user không phải là user hiện tại đã được xác thực có quyền nào đó hay không, bạn có thể sử dụng forUser
method:
if (Gate::forUser($user)->allows('update-post', $post)) {
//
}
Truyền Nhiều Đối Số
Tất nhiên, các ability callback
có thể nhận nhiều đối số:
Gate::define('delete-comment', function ($user, $post, $comment) {
//
});
Nếu ability
của bạn cần nhiều đối số, đơn giản chỉ cần truyền chúng dưới dạng mảng vào trong Gate methods
:
if (Gate::allows('delete-comment', [$post, $comment])) {
//
}
Thông Qua User Model
Ngoaì ra, bạn có thể kiểm tra abilities
thông qua instance của User model
. Mặc định, App\User
model của Laravel sử dụng Authorizable trait
cái mà cung cấp cho chúng ta 2 methods: can
và cannot
. Những methods này có cách sử dụng tương tự như allows và denies trong Gate facade
. Vì vậy, trong cách ví dụ chúng ta sử dụng trước, có thể thay đổi như sau:
<?php
namespace App\Http\Controllers;
use App\Post;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* Update the given post.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return Response
*/
public function update(Request $request, $id)
{
$post = Post::findOrFail($id);
if ($request->user()->cannot('update-post', $post)) {
abort(403);
}
// Update Post...
}
}
Tất nhiên, can
method chỉ đơn giản là ngược lại của cannot
method:
if ($request->user()->can('update-post', $post)) {
// Update Post...
}
Trong Blade Templates
Để thuận tiện, Laravel cung cấp cho chúng ta @can
Blade directive để nhanh chóng kiểm tra user đã xác thực hiện tại có ability
nào đó hay không. Ví dụ:
<a href="/post/{{ $post->id }}">View Post</a>
@can('update-post', $post)
<a href="/post/{{ $post->id }}/edit">Edit Post</a>
@endcan
Bạn cũng có thể kết hợp @can
với @else
:
@can('update-post', $post)
<!-- The Current User Can Update The Post -->
@else
<!-- The Current User Can't Update The Post -->
@endcan
Trong Form Requests
Bạn cũng có thể chọn việc sử dụng các abilities
đã được định nghĩa trong Gate
từ form request's authorize method. Ví dụ:
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
$postId = $this->route('post');
return Gate::allows('update', Post::findOrFail($postId));
}
Policies
Tạo Policies
Khi mà việc định nghĩa toàn bộ authorization
logic trong AuthServiceProvider
có thể thành trở ngại trong các ứng dụng lớn, Laravel cho phép bạn tách các authorization login thành cách class "Policy"
. Policies là các class thuần PHP mà nhóm các authorization logic dựa trên tài nguyền chúng cấp quyền.
Đầu tiên, tạo một policy để quản lí việc cấp quyền cho Post model
. Bạn có thể tạo một policy thông quan make;policy artisan command
. Policy được tạo ra sẽ ở trong thư mục app/Policies
:
php artisan make:policy PostPolicy
Đăng Kí Policies
Khi đã có policy, chúng ta cần đăng kí nó với `Gate` class. `AuthServiceProvider` bao gồm một thuộc tính `policies` dùng để map nhiều thực thể policies để quản lý chúng. Vì vậy, chúng ta sẽ chỉ định policy của `Post model` là `PostPolicy` class:
<?php
namespace App\Providers;
use App\Post;
use App\Policies\PostPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
Post::class => PostPolicy::class,
];
/**
* Register any application authentication / authorization services.
*
* @param \Illuminate\Contracts\Auth\Access\Gate $gate
* @return void
*/
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
}
}
Viết Policies
Khi policy
được sinh ra và đăng kí, chúng ta cần thêm các method cho mỗi ability mà nó cấp quyền. ví dụ, định nghĩa một update
method trong PostPolicy
để xác định User
có thể "update" Post
hay không:
<?php
namespace App\Policies;
use App\User;
use App\Post;
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 method vào policy
nếu thấy cần thiết. Ví dụ bạn có thể định nghĩa show
, destroy
hoặc addComment
method để cấp quyền cho nhiều hành động Post
.
Bỏ Qua Bộ Kiểm Tra
Đôi khi, bạn có thể muốn cấp toàn bộ abilities
cho một người dùng nào đó. Trong trường hợp này, định nghĩa before method trong policy
. Method này sẽ trả chạy trước toàn bộ các kiểm tra cấp quyền trong policy
:
public function before($user, $ability)
{
if ($user->isSuperAdmin()) {
return true;
}
}
Nếu before
method trả về một kết quả non-null thì kết quả đó sẽ được đại diện cho kết quả của kiểm tra.
Kiểm Tra Policies
Các policy methos được gọi chính xác giống như cách Closure
based authorization callbacks. Bạn có thể sử dụng Gate facade
, User model, @can
Blade directive hoặc policy helper
.
Thông Qua Gate Facade
Gate
sẽ tự động xác định policy
nào để sử dụng kiểm tra với class của các đối số truyền vào method của nó. Vì vậy, nếu chúng ta truyền một Post
instance vào denies method, Gate sẽ tự động lựa chọn đúng PostPolicy
tương ứng thực hiện các hành động cấp quyền:
<?php
namespace App\Http\Controllers;
use Gate;
use App\User;
use App\Post;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* Update the given post.
*
* @param int $id
* @return Response
*/
public function update($id)
{
$post = Post::findOrFail($id);
if (Gate::denies('update', $post)) {
abort(403);
}
// Update Post...
}
}
Thông Qua User Model
Các phương thực can
và cannot
của User model sẽ tự động sử dụng các policies khi chúng có sẵn với các đối số đã cho. Những method này cho chúng ta thuận tiện trong việc cấp quyền các hành động cho bất kì User
instance nào được lấy ra bởi ứng dụng của bạn:
if ($user->can('update', $post)) {
//
}
if ($user->cannot('update', $post)) {
//
}
Trong Blade Templates
Tương tự, @can
Blade directive sẽ sử dụng policies
khi chúng có sẵn cho các đối số:
@can('update', $post)
<!-- The Current User Can Update The Post -->
@endcan
Thông Qua Policy Helper
Global policy
helper function có thể được sử dụng để lấy Policy
class cho một class instance đã cho. Ví dụ, chúng ta có thể truyền một Post instance vào policy helper để nhận được một instance tương ứng PostPolicy
class:
if (policy($post)->update($user, $post)) {
//
}
Controller Authorization
Mặc định, App\Http\Controllers\Controller
class trong Laravel sử dụng AuthorizesRequests
trait. Trait này cung cấp authorize method, mà có thể được sử dụng để nhanh chóng cấp quyền cho một hành động và throw một AuthorizationException
nếu hành động không được cấp quyền.
authorize
method giống các phương thức cấp quyền khác như Gate::allows
và $user->can()
. Vì vậy, hãy sử dụng authorize method để nhanh chóng cấp quyền cho một request thực hiện cập nhật một Post
:
<?php
namespace App\Http\Controllers;
use App\Post;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* Update the given post.
*
* @param int $id
* @return Response
*/
public function update($id)
{
$post = Post::findOrFail($id);
$this->authorize('update', $post);
// Update Post...
}
}
Nếu hành động được cấp quyền, controller
sẽ tiếp tục thực thi bình thường; tuy nhiên, nếu authorize method xác định rằng hành động không được cấp quyền, một AuthorizationException
sẽ tự động được throw cái mà sẽ sinh ra một HTTP response với 403 Not Authorized
status code. Như bạn có thể thấy, authorize method là cách thuận tiện, nhanh chóng để cấp quyền một hành động hoặc throw một exception
với duy nhất một dòng code.
AuthorizesRequests trait
cũng cung cấp authorizeForUser
method để cấp quyền một hành động cho một user
mà hiện tại chưa được xác thực:
$this->authorizeForUser($user, 'update', $post);
Tự Động Xác Định Policy Methods
Các method của một policy
sẽ tương ứng với các method trong controller
. Ví dụ, trong update
method trên, controller
method và policy
method cùng sử dụng chung tên: update
.
Vì lí do này, Laravel
cho phép bạn đơn giản truyền vào các đối số instance vào authorize method, và ability để cấp quyền sẽ tự động được xác định dựa trên tên của hàm gọi. Trong ví dụ này, khi authorize
được gọi từ controller update
method, update method
cũng sẽ được gọi trên PostPolicy
:
/**
* Update the given post.
*
* @param int $id
* @return Response
*/
public function update($id)
{
$post = Post::findOrFail($id);
$this->authorize($post);
// Update Post...
}
Tài liệu tham khảo
All rights reserved