Laravel 的用户授权系统
简介
除了内置开箱即用的 用户认证 服务外,Laravel 还提供一种更简单的方式来处理用户授权动作。类似用户认证,Laravel 有 2 种主要方式 来实现用户授权:gates 和策略。
可以把 gates 和策略类比于路由和控制器。 Gates 提供了一个简单的、基于闭包的方式来授权认证。策略则和控制器类似,在特定的模型或者资源中通过分组来实现授权认证的逻辑。我们先来看看 gates,然后再看策略。
在你的应用中,不要将 gates 和策略当作相互排斥的方式。大部分应用很可能同时包含 gates 和策略,并且能很好的工作。Gates 大部分应用在模型和资源无关的地方,比如查看管理员的面板。与之相反,策略应该用在特定的模型或者资源中。
Gates
编写 Gates
Gates 是用来决定用户是否授权执行给定的动作的闭包函数,并且典型的做法是在 App\Providers\AuthServiceProvider
类中使用 Gate
facade 定义。Gates 接受一个用户实例作为第一个参数,并且可以接受可选参数,比如 相关的 Eloquent 模型:
/**
* 注册任意用户认证、用户授权服务。
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Gate::define('update-post', function ($user, $post) {
return $user->id == $post->user_id;
});
}
Gates 也可以使用 Class@method
风格的回调字符串来定义,比如控制器:
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Gate::define('update-post', 'PostPolicy@update');
}
资源 Gates
你还可以使用 resource
方法一次性定义多个 Gate 功能:
Gate::resource('posts', 'PostPolicy');
这与手动编写以下 Gate 定义相同:
Gate::define('posts.view', 'PostPolicy@view');
Gate::define('posts.create', 'PostPolicy@create');
Gate::define('posts.update', 'PostPolicy@update');
Gate::define('posts.delete', 'PostPolicy@delete');
默认情况下将会定义 view
, create
, update
,和 delete
功能。 通过将数组作为第三个参数传递给 resource
方法,您可以覆盖或添加新功能到默认的功能。 数组的键定义能力的名称,而值定义方 法名称。 例如,以下代码将创建两个新的Gate定义: posts.image
和 posts.photo
:
Gate::resource('posts', 'PostPolicy', [
'image' => 'updateImage',
'photo' => 'updatePhoto',
]);
授权动作
使用 gates 来授权动作时,应使用 allows
或 denies
方法。注意你并不需要传递当前认证通过的用户给这些方法。Laravel 会自动处理好传入的用户,然后传递给 gate 闭包函数:
if (Gate::allows('update-post', $post)) {
// 指定用户可以更新博客...
}
if (Gate::denies('update-post', $post)) {
// 指定用户不能更新博客...
}
如果需要指定一个特定用户是否可以访问某个动作,可以使用 Gate
facade 中的 forUser
方法 :
if (Gate::forUser($user)->allows('update-post', $post)) {
// 指定用户可以更新博客...
}
if (Gate::forUser($user)->denies('update-post', $post)) {
// 指定用户不能更新博客...
}
创建策略
生成策略
策略是在特定模型或者资源中组织授权逻辑的类。例如,如果你的应用是一个博客,会有一个 Post
模型和一个相应的 PostPolicy
来授权用户动作,比如创建或者更新博客。
可以使用 make:policy
artisan 命令 来生成策略。生成的策略将放置在 app/Policies
目录。如果在你的应用中不存在这个目录,那么 Laravel 会自动创建:
php artisan make:policy PostPolicy
make:policy
会生成空的策略类。如果希望生成的类包含基本的「CRUD」策略方法, 可以在使用命令时指定 --model
选项:
php artisan make:policy PostPolicy --model=Post
所有授权策略会通过 Laravel 服务容器 解析,意指你可以在授权策略的构造器对任何需要的依赖使用类型提示,它们将会被自动注入。
注册策略
一旦该授权策略存在,需要将它进行注册。新的 Laravel 应用中包含的 AuthServiceProvider
包含了一个 policies
属性,可将各种模型对应至管理它们的授权策略。注册一个策略将引导 Laravel 在授权动作访问指定模型时使用何种策略:
<?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
{
/**
* 应用的策略映射。
*
* @var array
*/
protected $policies = [
Post::class => PostPolicy::class,
];
/**
* 注册任意用户认证、用户授权服务。
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
//
}
}
编写策略
策略方法
一旦授权策略被生成且注册,我们就可以为授权的每个动作添加方法。例如,让我们在 PostPolicy
中定义一个 update
方法,它会判断指定的 User
是否可以更新指定的 Post
实例。
update
方法接受 User
和 Post
实例作为参数,并且应当返回 true
或 false
来指明用户是否授权更新指定的 Post
。因此,这个例子中,我们判断用户的 id
是否和 post 中的 user_id
匹配:
<?php
namespace App\Policies;
use App\User;
use App\Post;
class PostPolicy
{
/**
* 判断指定博客能否被用户更新。
*
* @param \App\User $user
* @param \App\Post $post
* @return bool
*/
public function update(User $user, Post $post)
{
return $user->id === $post->user_id;
}
}
你可以继续为此授权策略定义额外的方法,作为各种权限所需要的授权。例如,你可以定义 view
或 delete
方法来授权 Post
的多种行为。可以为自定义策略方法使用自己喜欢的名字。
如果在 Artisan 控制台生成策略时使用 --model
选项,会自动包含view
、create
、update
和 delete
动作。
不包含模型方法
一些策略方法只接受当前认证通过的用户作为参数而不用传入授权相关的模型实例。最普遍的应用场景就是授权 create
动作。例如,如果正在创建一篇博客,你可能希望检查一下当前用户是否有权创建博客。
当定义一个不需要传入模型实 例的策略方法时,比如 create
方法,你需要定义这个方法只接受已授权的用户作为参数:
/**
* 判断指定用户是否可以创建博客。
*
* @param \App\User $user
* @return bool
*/
public function create(User $user)
{
//
}
策略过滤器
对特定用户,你可能希望通过指定的策略授权所有动作。 要达到这个目的,可以在策略中定义一个 before
方法。before
方法会在策略中其它所有方法之前执行,这样提供了一种方式来授权动作而不是指定的策略方法来执行判断。这个功能最常见的场景是授权应用的管理员可以访问所有动作:
public function before($user, $ability)
{
if ($user->isSuperAdmin()) {
return true;
}
}
如果你想拒绝用户所有的授权,你应该在 before
方法中返回 false
。如果返回的是 null
,则通过其它的策略方法来决定授权与否。