Laravel 5.6

Security: Authorization

Authorization

Authorization

Introduction

Ngoài việc cung cấp các dịch vụ xác thực (authentication), Laravel cũng cung cấp một cách đơn giản để ủy quyền cho các hành động của người dùng đối với một tài nguyên nhất định. Giống như xác thực, cách tiếp cận ủy quyền của Laravel rất đơn giản và có hai cách chính để ủy quyền hành động: gatespolicies.

Cũng giống như Route và Controller. Gate cung cấp cấp phương thức đóng để xác thực trong khi đó Policies thì giống như controller, nó gộp các logic xoay quanh 1 model hay resource cụ thể.

Bạn không cần phải chọn giữa sử dụng Gate hoặc sử dụng Policy khi xây dựng ứng dụng. Hầu hết các ứng dụng rất có thể dùng được cả GatePolicy, và điều đó là hoàn toàn tốt! Gate áp dụng nhiều nhất cho các hành động không liên quan đến bất kỳ model hay resource nào, chẳng hạn như xem bảng điều khiển của quản trị viên. Ngược lại, policy nên được sử dụng khi bạn muốn ủy quyền cho một hành động cho một model hoặc resource nào đó cụ thể.

Gates

Writing Gates

Trước tiên ta xem lại về Closure là gì để hiểu rõ hơn cách viết của Gate

Closure class được sử dụng để thể hiện anonymos function trong php, Anonymous function cho phép tạo các function mà không cần phải có tên. Chúng có ích nhất như các callback parameter, nhưng chúng còn nhiều hữu dụng khác.

ví dụ


<?php
echo preg_replace_callback('~-([a-z])~', function ($match) {
return strtoupper($match[1]);
}, 'hello-world');
// outputs helloWorld
?>

 

GateClosure được xác định nếu 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 được xác định trong class App\Providers\AuthServiceProvider bằng cách xử dụng  Gate facade. Gate luôn nhận một cá thể  như một đối số đầu tiên và có thể tùy chọn nhận thêm các đối số như Eloquent model liên quan:

/**
 * Register any authentication / authorization services.
 *
 * @return void
 */
public function boot()
{
    $this->registerPolicies();

    Gate::define('update-post', function ($user, $post) {
        return $user->id == $post->user_id;
    });
}

Gate có thể được xác định bằng cách sử dụng  Class@method dạng callback string, giống như controller

/**
 * Register any authentication / authorization services.
 *
 * @return void
 */
public function boot()
{
    $this->registerPolicies();

    Gate::define('update-post', 'App\Policies\[email protected]');
}

Resource Gates

Bạn cũng có thể xác định nhiều Gate cùng 1 lúc bằng phương thức resource

Gate::resource('posts', 'App\Policies\PostPolicy');

Lệnh trên tương đương với việc bạn khai báo thủ công với 4 phương thức sau:

Gate::define('posts.view', 'App\Policies\[email protected]');
Gate::define('posts.create', 'App\Policies\[email protected]');
Gate::define('posts.update', 'App\Policies\[email protected]');
Gate::define('posts.delete', 'App\Policies\[email protected]');

 

Mặc định thì viewcreateupdate, và delete được định nghĩa. Bạn có thể ghi đè bằng cách truyền 1 mảng làm đối số thứ ba và phương thức resource. Các key trong mảng  định nghĩa tên của các khả năng (abilities) trong khi đó giá trị được dùng để xác định các phương thức (method).

Ví dụ, đoạn mã sau để tạo định nghĩa Gate mới là posts.imageposts.photo

Gate::resource('posts', 'PostPolicy', [
    'image' => 'updateImage',
    'photo' => 'updatePhoto',
]);

Authorizing Actions – hành động ủy quyền

Để cho phép một hành động sử dụng Gate, bạn sử dụng phương thức allows hoặc denies. Laravel sẽ auto truyền người dùng đang authenticated vào trong gate Closure:

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...
}

Nếu bạn muốn xác định xem một người dùng cụ thể 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 Gate facade:

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...
}

 

Intercepting Gate Checks (bỏ qua việc check Gate)

Đôi khi, bạn có thể muốn cấp tất cả quyền cho một người dùng cụ thể. Bạn có thể sử dụng phương thức before để chạy trước việc check authorization

Sometimes, you may wish to grant all abilities to a specific user. You may use the beforemethod to define a callback that is run before all other authorization checks:

Gate::before(function ($user, $ability) {
    if ($user->isSuperAdmin()) {
        return true;
    }
});

Nếu kế quả trả về từ before là khác null thì kết quả đó được xem như là kết quả kiểm tra

Bạn có thể sử dụng phương thức after để xác định việc trả về được thực thi sau mỗi khi check authorization. Tuy nhiên, bạn có thể không thay đổi được kết quả của việc check authorization từ trả về của after

Gate::after(function ($user, $ability, $result, $arguments) {
    //
});

Tạo Policiy

Generating Policies

Policy là các lớp mà được dùng tổ chức các logic uỷ quyền xung quanh 1 model hay 1 resource cụ thể nào đó. Ví dụ, nếu ứng dụng của bạn là 1 blog, bạn có 1 model Post và tương ứng PostPolicy để ủy quyền cho các hành động của người dùng như tạo mới hay chỉnh sửa bài viết.

Bạn tạo policy bằng lệnh:

php artisan make:policy PostPolicy

Lệnh make:policy sẽ tạo một lớp policy rỗng. Để tạo các phương thức đi kèm với policy CRUD khi tạo model bạn thêm –model khi thực hiện lệnh tạo policy

php artisan make:policy PostPolicy --model=Post

All policies are resolved via the Laravel service container, allowing you to type-hint any needed dependencies in the policy’s constructor to have them automatically injected.

Registering Policies (Đăng ký Policy)

Khi một Policy tồn tại, bạn cần phải đăng ký nó. AuthServiceProvider bên trong 1 ứng dụng Laravel chứa các thuộc tính policy, các thuộc tính này được ánh xạ tới Eloquent model tới các policy tương ứng của chúng. Chúng ta sẽ xem việc đăng ký sử dụng policy đối với một model nhất định:

<?php

namespace App\Providers;

use App\Post;
use App\Policies\PostPolicy;
use Illuminate\Support\Facades\Gate;
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.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies();

        //
    }
}

 

Writing Policies

Policy Methods

Khi Policy được đăng ký, bạn có thể thêm các phương thức cho mỗi action để nó thực hiện việc ủy quyền.

Ví dụ, để cho phép update trong PostPolicy – policy được xác định cấp quyền cho User có thể cập nhật post

Phương thức update sẽ nhận instance Userinstance Post như các đối số, và trả ra giá trị true hoặc false để chỉ ra liệu User có được cấp quyền update Post hay không. Trong ví dụ, hãy xác định id ủa người dùng có khớp với user_id trong 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 xác định các phương thức bổ sung trong policy khi cần cho các hành động khác nhau mà nó ủy quyền. Ví dụ: bạn có thể xác định chế độ view hoặc delete để ủy quyền cho các hành động khác nhau của Post, nhưng hãy nhớ rằng bạn có thể tự do đưa ra các phương thức policy của mình cho bất kỳ tên nào bạn muốn.

If you used the --model option when generating your policy via the Artisan console, it will already contain methods for the viewcreateupdate, and deleteactions.

 

Methods Without Models (Phương thức mà không cần model)

Đôi khi các phương thức của policy chỉ nhận từ người dùng đã được xác thực (đã login) và không có instance của model ủy quyền. Tình huống này là phổ biến nhất khi ủy quyền của 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 có được phép tạo bất kì bài đăng nào không

Khi xác định các phương thức của policy sẽ không nhận một model instance, như kiểu phương thức create, nó sẽ không nhận một model instance. Thay vào đó, bạn nên định nghĩa phương thức là chỉ người dùng được xác thực (authenticated):

/**
 * Determine if the given user can create posts.
 *
 * @param  \App\User  $user
 * @return bool
 */
public function create(User $user)
{
    //
}

Policy Filters (bộ lọc Policy)

Đối với 1 số người dùng nhất định, bạn muốn trao cho họ toàn bộ các quyền mà policy cung cấp. Để làm việc này, định nghĩa phương thức before trong policy. Phương thức before sẽ thực thi trước tất cả các phương thức trong policy, cho phép bạn thực hiện các hành động trước các phương thức bên trong policy. Điều này được sử dụng phổ biển khi ta trao quyền cho user có quyền là administrator:

public function before($user, $ability)
{
    if ($user->isSuperAdmin()) {
        return true;
    }
}

Nếu bạn muốn từ chối ủy quyền cho user, bạn nên trả về false tại phương thức before. Nếu giá trị null được trả về, ủy quyền sẽ thuộc về các phương thức trong policy

The before method of a policy class will not be called if the class doesn’t contain a method with a name matching the name of the ability being checked.

 

Authorizing Actions Using Policies

Via The User Model

Model User trong ứng dụng Laravel có 2 phương thức rất hữu ích cho việc ủy quyền hành động là : cancant. Phương thức can nhận vào các hành động bạn muốn úy quyền và model liên quan

Ví dụ: hãy xác định xem người dùng có được phép cập nhật Post không:

if ($user->can('update', $post)) {
    //
}

Nếu một policy được đăng ký cho 1 model, phương thức can sẽ tự động gọi policy phù hợp và trả về kết quả boolean. Nếu không có policy nào được đăng ký cho model, phương thức can sẽ cố gắng gọi Gate khớp với tên hành động đã cho.

Hành động không yêu cầu Model

Nhớ rằng, đôi khi các hành động như create có thể không yêu cầu trong một instance model. Trong những trường hợp này, bạn phải truyền tên class và phương thức can. Tên lớp sẽ được xử dụng để xác định policy nào được xử dụng trong việc trao quyền cho hành động:

use App\Post;

if ($user->can('create', Post::class)) {
    // Executes the "create" method on the relevant policy...
}

Qua Middleware

Middleware trong laravel có thể được ủy quyền các hành động trước khi request đến controller. Mặc định, middleware Illuminate\Auth\Middleware\Authorize được gán key can trong  lớp App\Http\Kernel . Cùng xem ví dụ sử dụng middleware để ủy quyền cho user có thể cập nhật blog post

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 vào middleware 2 biến. Biến thứ nhất là tên của hành động bạn muốn ủy quyền và thứ 2 là route parameter mà bạn muốn truyền tới phương thức policy. Trong trường hợp này, vì larvel sử dụng implicit model binding (ràng buộc model ngầm), model Post được truyền vào trong phương thức policy. Nếu user không được ủy quyền để thực hiện các hành động, phản hồi HTTP sẽ là 403 được tạo từ middleware

Actions That Don’t Require Models (hành động không yêu cầu model)

Một lần nữa, một số hành động như create có thể không yêu cầu một instance model. Trong những tình huống này, bạn có thể chuyển một tên lớp cho middleware. Tên lớp sẽ được sử dụng để xác định policy nào sẽ được sử dụng khi ủy quyền cho hành động:

Route::post('/post', function () {
    // The current user may create posts...
})->middleware('can:create,App\Post');

Thông qua Controller Helpers

Ngoài các phương thức được cung cấp trong model User, Laravel còn cung cấp phương thức authorize tới bất kì controller nào được extend từ App\Http\Controllers\Controller . Gióng như phương thức can, phương thwcsnafy chấp nhận tên của hành động bạn muốn cho quyền và model liên quan. Nếu hành động không được cấp quyền, việc authorize sẽ đưa vào 1 xử lý ngoại lệ thông qua Illuminate\Auth\Access\AuthorizationException và được phản hồi theo HTTP là code lỗi 403

<?php

namespace App\Http\Controllers;

use App\Post;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

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...
    }
}

Hành động không yêu cầu theo model

Cũng giống như phần trước ta đã nói, đôi khi việc tạo các hành động như create không cần phải có một model instance. Trong trường hợp như vậy, bạn có thể truyền tên lớp tới phương thức authorize . Tên của class sẽ được sử dụng để xác định policy nào được xử dụng cho hành động này

/**
 * 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...
}

Thông qua Blade Templates

Khi viết trong Blade, bạn muốn hiển thị một phần khác nhau đối với những hành động của người dùng được phân quyền. Ví dụ, bạn hiển thị form update blog chỉ cho người có quyền update. Trong trường hợp này bạn dùng @can@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 Can't Update The Post -->
@elsecannot('create', App\Post::class)
    <!-- The Current User Can't Create New Post -->
@endcannot

Việc sử dụng @can@cannot là viết tắt của việc dùng @ìf và @unless

Các bạn cũng có thể dùng @if@unless

@if (Auth::user()->can('update', $post))
    <!-- The Current User Can Update The Post -->
@endif

@unless (Auth::user()->can('update', $post))
    <!-- The Current User Can't Update The Post -->
@endunless

Hành động không cần yêu cầu model

Giống như hầu hết các phương thức authorization, bạn có thể truyền tên lớp vào @can@cannot nếu hành động đó không yêu cầu 1 model instance

@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

Tags

Leave a Reply

Your email address will not be published. Required fields are marked *

Close