Laravel 授权方式 Gate 和 Policy

定义授权

Laravel 在 app/Providers/AuthServiceProvider.phpboot 方法里定义授权,有两种形式。

形式一

1
2
3
Gate::define('update articles', function ($user, $article) {
return $user->id == $article->user_id;
});

形式二

1
Gate::define('update articles', 'ArticlePolicy@update');

手动创建 ArticlePolicy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

namespace App\Policies;

use App\User;
use App\Models\Article;

class ArticlePolicy
{
public function update(User $user, Article $article)
{
return $user->id == $article->user_id;
}
}

这两种形式的区别在于,授权逻辑是写在闭包里,还是 PHP 类方法里。闭包或方法最终都返回一个布尔值,true 表示有权限,false 表示没权限。

使用授权

有五种使用授权的方式:

  1. Gate 门面:
1
Gate::allows('update articles', $article);


1
Gate::denies('update articles', $article);

  1. Controller:

    1
    $this->authorize('update articles', $article);
  2. Blade 模板:

    1
    2
    3
    @can('update articles', $article)
    <!--如果定义时候没有第二个参数-->
    @can('system')


1
@cannot('update articles', $article)


1
2
3
4
5
@can('update', $post)
<!-- 当前用户可以更新博客 -->
@elsecan('create', $post)
<!-- 当前用户可以新建博客 -->
@endcan

指令。

  1. User Model 实例:
    1
    $user->can('update articles', $article);


1
$user->cannot('update articles', $article);

  1. 路由
    1
    2
    3
    4
    <!--定义时候没有第二个参数-->
    Route::group(['middleware' => 'can:system'], function(){

    });

两种定义授权对比

有些时候,直接用基于闭包的形式定义授权很不方便,特别是对某个资源(比如文章)基本的增、删、改、查操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Gate::define('view articles', function ($user, $article) {
return true;
});

Gate::define('create articles', function ($user) {
return true;
});

Gate::define('delete articles', function ($user, $article) {
return $user->id == $article->user_id;
});

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

如果再有一个视频资源的话,又得定义这四个方法了,这些方法堆在一起,很长。那么如果改成基于 PHP 类方法的形式,就好看一些了。

1
2
3
4
Gate::define('view articles',  'ArticlePolicy@view');
Gate::define('create articles', 'ArticlePolicy@create');
Gate::define('delete articles', 'ArticlePolicy@delete');
Gate::define('update articles', 'ArticlePolicy@update');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php

namespace App\Policies;

use App\User;
use App\Models\Article;

class ArticlePolicy
{
public function view(User $user, Article $article)
{
return true;
}

public function create(User $user)
{
return true;
}

public function delete(User $user, Article $article)
{
return $user->id == $article->author_id;
}

public function update(User $user, Article $article)
{
return $user->id == $article->author_id;
}
}

因为分类写了,所以定义授权的地方,看起来就比较清爽了。

更简洁的定义授权形式

为了能更加清爽地定义和使用授权,Laravel 引入了 Policy。在 AuthServiceProviderpolicies 数组属性里添加授权映射关系即可!

1
2
3
4
5
6
7
8
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
Article::class => ArticlePolicy::class,
];

所有与 Article Model 有关的授权逻辑都写在 ArticlePolicy 里,你可以用 make:policy Artisan 命令生成他。Policy 只是在普通 PHP 类基础上添加了一个 HandlesAuthorization trait。

创建策略类

1
php artisan make:policy ArticlePolicy

定义策略类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php

namespace App\Policies;

use App\User;
use App\Models\Article;
use Illuminate\Auth\Access\HandlesAuthorization;

class ArticlePolicy
{
use HandlesAuthorization;

public function view(User $user, Article $article)
{
return true;
}

public function create(User $user)
{
return true;
}

public function delete(User $user, Article $article)
{
return $user->id == $article->author_id;
}

public function update(User $user, Article $article)
{
return $user->id == $article->author_id;
}
}

而且使用起来也非常方便。

1
2
3
4
$user->can('view', $article);
$user->can('create', Article::class);
$user->can('update', $article);
$user->can('delete', $article);

$article 是 Article Model 实例对象, Laravel 发现是对 Article Model 资源做授权判断,根据在 $policies 中定义的授权映射关系,自动找到 ArticlePolicy 下的 viewcreateupdatedelete 方法。

当然,授权方法可自定义。比如我在 ArticlePolicy 下自定义了一个方法 xxx,那么就可以直接用了。

1
$user->can('xxx', $article);

但有一个方法是特殊的 ——before,在 Policy 中会在所有方法执行前调用,经常用到的地方就是处理管理员授权逻辑。

1
2
3
4
5
6
public function before($user, $ability)
{
if ($user->isSuperAdmin()) {
return true;
}
}