1. 程式人生 > >laravel5.5授權系統

laravel5.5授權系統

我們 服務 providers nds test 用戶授權 array ger time

[toc]
背景:

帖子屬於某個人(擁有屬性user_id),如果這是個私密帖子,只有自己才可以看到,傳統的做法是

class PostsController extends Controller
{
    public function show($id)
    {
        $post = Post::findOrFail($id);

        //只有登錄的用戶id和帖子的所屬user_id相同才可以通過
        if (auth()->id() != $post->user_id) {
            // 403 權限錯誤
            abort(403, 'Sorry, not sorrry.'); 
        }
        
        return $post->title;
    }
}

以上做法問題不大,但是有沒有更優雅的做法呢,今天要講的就是laravel的用戶授權,Laravel 有 2 種主要方式來實現用戶授權:gates 和策略。

1. Gates

1.1 一個簡單的使用Gates的例子

  1. 準備工作:

創建遷移文件

php artisan make:migration create_post_table --create=posts

文件內容

    Schema::create('posts', function (Blueprint $table) {
        $table->increments('id');
        $table->integer('user_id')->unsigned()->index();
        $table->string('title');
        $table->text('body');
        $table->timestamps();
    }); 

    \DB::table('posts')->insert(
        array(
            'user_id' => 1,
            'title' => '學習用戶授權',
            'body' => '兩種方式,Gates、策略',
        )   
    );  

開始遷移

php artisan migration

我們主要使用user表(laravel自帶遷移文件)和posts表,其它創建Model等工作自行完成

  1. 註冊Gates,在服務提供器中註冊

服務提供器 App\Providers\AuthServiceProvider.php

/**
 * 註冊任意用戶認證、用戶授權服務。
 *
 * @return void
 */
public function boot()
{
    $this->registerPolicies();

    Gate::define('update-post', function ($user, $post) {
        return $user->id == $post->user_id;
    });
}
  1. 授權判定,可以在控制器中進行權限的判定

控制器 app/Http/Controllers/PostManager.php

public function show($id)
{
    $post = Post::findOrFail($id);

    if (Gate::allows('update-post', $post)) {
        // 指定用戶可以更新博客...
    }
    
    abort(403, 'Sorry, Permission denied'); 
}

以上就是一個簡單的用戶授權,是不是很簡單,下面我們理解下更多用法

1.2 編寫Gates

典型的做法是在 App\Providers\AuthServiceProvider 類中使用 Gate facade 定義。Gates 接受一個用戶實例作為第一個參數,並且可以接受可選參數。

  1. 匿名函數

    public function boot()
    {
    $this->registerPolicies();
    
    Gate::define('update-post', function ($user, $post) {
        return $user->id == $post->user_id;
    });
    }
  2. 還可以使用Class@method風格字符串,比如控制器

    Gate::define('update-post', 'PostPolicy@update');
  3. 使用資源Gates

一次性定義多個Gates

Gate::resource('posts', 'PostPolicy');

等同於定義了下面4個功能

Gate::define('posts.view', 'PostPolicy@view');
Gate::define('posts.create', 'PostPolicy@create');
Gate::define('posts.update', 'PostPolicy@update');
Gate::define('posts.delete', 'PostPolicy@delete');

還可以傳遞第三個參數給resource方法,以增加功能

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

1.3 授權動作

註意: 上面我們定義Gate的時候,閉包函數第一個參數為$user, 但是你並不需要傳遞當前認證通過的用戶給這些方法。Laravel 會自動處理好傳入的用戶,然後傳遞給 gate 閉包函數

  1. 基本用法,使用Gate Facades
    ```
    //允許授權
    if (Gate::allows(‘update-post‘, $post)) {
    // 指定用戶可以更新博客...
    }

//否定授權
if (Gate::denies(‘update-post‘, $post)) {
// 指定用戶不能更新博客...
}


2. 不需自動處理用戶,自己指定一個用戶,可以使用Gate Facade的forUser()方法

if (Gate::forUser($user)->allows(‘update-post‘, $post)) {
// 指定用戶可以更新博客...
}

if (Gate::forUser($user)->denies(‘update-post‘, $post)) {
// 指定用戶不能更新博客...
}


# 2. policy策略

顯然Gates簡單易用,但是當我們需要授權的動作過多的時候,就顯得比較臃腫了,管理起來麻煩,laravel提供了我們另一種方式,來實現同樣的功能,就是policy

## 2.1 還是先看個例子

1. 創建策略文件

php artisan make:policy PostPolicy --model=Post


2. 編寫文件

以上命令會在app\Policies\PostPolicy.php文件,該文件已經包含了4個基本的「CRUD」策略方法,我們可以增刪各種方法,這裏只補充view方法
public function view(User $user, Post $post)
{   
    //  
    return $user->id == $post->user_id;
}


public function create(User $user)
{   
    //  
}


public function update(User $user, Post $post)
{
    //
}


public function delete(User $user, Post $post)
{
    //
}

3. 註冊策略

服務提供器 App\Providers\AuthServiceProvider.php, 更改$policies屬性
protected $policies = [
    'App\Post' => 'App\Policies\PostPolicy',
];

4. 授權判定

控制器 app/Http/Controllers/PostManager.php

public function show($id)
{
$post = Post::findOrFail($id);

if ($user->can('views', $post)) {
    // 指定用戶可以 ......
}

abort(403, 'Sorry, Permission denied'); 

}


以上就是使用policy的一個簡單實例,下面還是進行更細致的梳理

## 2.2 編寫策略

生成策略文件後,我們可以自己創建刪除方法,以滿足我們的需求,可以為自定義策略方法使用自己喜歡的名字
public function view(User $user, Post $post)
{   
    //  
    return $user->id == $post->user_id;
}

public function create(User $user)
{   
    //  
}

// 其它更多需要的方法

我們還可以使用策略過濾器

比如我們想要超級管理員有所有權限,可以在策略中定義一個 before 方法

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

如果你想拒絕用戶所有的授權,你應該在 before 方法中返回 false。如果返回的是 null,則通過其它的策略方法來決定授權與否。


## 2.3 授權策略

### 2.3.1 通過用戶模型

1. 指定模型的動作
Laravel 應用內置的 User 模型包含 2 個有用的方法來授權動作:can 和 cant

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

if ($user->cant(‘update‘, $post)) {
//
}


**註意**: 如果指定模型的 策略已被註冊,can 方法會自動調用核實的策略方法並且返回 boolean 值。如果沒有策略註冊到這個模型,can 方法會嘗試調用和動作名相匹配的基於閉包的 Gate。

2. 不指定模型的動作

比較下面的兩個方法
public function view(User $user, Post $post)
{   
    //  
    return $user->id == $post->user_id;
}

public function create(User $user)
{   
    //  
}
試想,如果我們有另個一個VideoPolice, 同樣有view和create方法。
public function view(User $user, Video $video)
{   
    //  
    return $user->id == $video->user_id;
}

public function create(User $user)
{   
    //  
}

不難想象,盡管第一參數都是view, 第二個傳入了模型實例,我們在註冊策略的時候做了模型和策略的映射,這樣就可以區分使用的是PostPolicy還是VideoPolicy

$user->can(‘view‘, $post);
$user->can(‘view‘, $video);


那麽create方法呢, 我們可以傳遞一個類名給 can 方法。當授權動作時,這個類名將被用來判斷使用哪個策略

$user->can(‘create‘, Post::class);
$user->can(‘view‘, Video::class);


### 2.3.2 通過中間件

1. 通過隱式模型綁定,指定模型動作

use App\Post;

Route::put(‘/post/{post}‘, function (Post $post) {
// 當前用戶可以更新博客...
})->middleware(‘can:update,post‘);


關於隱式模型綁定,這裏順便提一下,更詳細的請自己查詢。

首先可以定義這樣一條路由

Route::get(‘/test/{post}‘,‘TestController@test‘);


控制器引入模型文件

use App\Http\Model\Post;


test方法傳入實例,並類型提示
public function test(Post $post)
{   
    http_response_code(500);
    dd($post);
}
我們訪問這樣的一條路由 http://xxxx.com/test/1 ,將dd()出id=1 的post實例,我們並沒有專門的實例化post這個model,但是我們自動返回了id=參數的post實例

2. 不需要指定模型的動作

Route::post(‘/post‘, function () {
// 當前用戶可以創建博客...
})->middleware(‘can:create,App\Post‘);


### 2.3.3 通過控制器輔助函數autorize()

如果動作不被授權,authorize 方法會拋出 Illuminate\Auth\Access\AuthorizationException 異常,然後被 Laravel 默認的異常處理器轉化為帶有 403 狀態碼的 HTTP 響應:

1. 指定模型動作

public function update(Request $request, Post $post)
{
$this->authorize(‘update‘, $post);

// 當前用戶可以更新博客...

}


2. 不指定模型動作

public function create(Request $request)
{
$this->authorize(‘create‘, Post::class);

// 當前用戶可以新建博客...

}


### 2.3.4 通過blade模板

可以使用@can 和 @cannot

1. 指定模型動作

@can(‘update‘, $post)

@elsecan(‘create‘, $post)

@endcan

@cannot(‘update‘, $post)

@elsecannot(‘create‘, $post)

@endcannot


2. 不指定模型動作

@can(‘create‘, App\Post::class)

@endcan

@cannot(‘create‘, App\Post::class)

@endcannot


實際上@can 和@cannot 提供了方便的縮寫, 分別等同於下面寫法

@if (Auth::user()->can(‘update‘, $post))

@endif

@unless (Auth::user()->can(‘update‘, $post))

@endunless
```

laravel5.5授權系統