Laravel 文档阅读:授权
简介
除了开箱提供的认证服务,在 Laravel 中对用户授权某个资源的操作权限也非常容易实现。Laravel 授权的方式主要分两种:Gates 和策略(Policies)。
Gates 和策略的关系像路由和控制器的。Gates 提供基于路由的授权方式;而策略将针对某个资源的操作逻辑组织起来,放在一个地方管理。我们会先介绍 Gates,然后再介绍策略。
我们不需要在「项目里的授权,是选择使用 Gates,还是选择使用策略呢?」这个问题上纠结。许多项目里,都会混用这两种授权方式,而且运行良好。基本上,使用 Gates 的地方都是资源不相关的,比如查看管理员面板页;相反,策略是资源相关的,当你是具体授权某个资源的操作权限时,使用策略是没错啦。
Gates
我们通常在 App\Provider\AuthServiceProvider
类中使用 Gate
门面来定义 Gates。已经说过,Gates 是基于闭包判断用户是否有操作权限的,回调闭包的第一个参数是用户实例,额外可选的第二个参数是相关的 Eloquent Model 实例:
/**
* 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;
});
}
Gates 也可以以 Class@method
的形式定义授权逻辑,像控制器一样:
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Gate::define('update-post', 'PostPolicy@update');
}
在上面的例子中,我们称在 Gates 定义中的 'update-post'
叫「能力」(Ability)。
资源 Gates
使用 Gate::resource
方法一次定义多个 Gates:
Gate::resource('posts', 'PostPolicy');
这一句相当于手动定义了下面的 Gates:
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
方法的第三个数组参数。数组的 Key 是能力名,Value 是方法名。下面的例子里,我们定义了两个 Gates 能力:posts.image
和 posts.photo
:
Gate::resource('photos', 'PostPolicy', [
'image' => 'updateImage',
'photo' => 'updatePhoto',
]);
授权操作
使用 Gates 授权操作,是用到 allows
和 denies
方法。需要注意的是,你不需要传递当前的认证用户实例给这两个方法,因为 Laravel 会自动为我们的 Gates 传递用户实例的:
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...
}
如果需要判断一个指定用户账号是否有进行某个操作的权限,那么在使用 forUser
方法吧!
if (Gate::forUser($user)->allows('update-post', $post) {
// The current user can update the post...
})
if (Gate::forUser($user)->denies('update-post', $post)) {
// The current user can't update the post...
}
创建策略
生成策略
策略是针对特定模型或资源、方便得组织它们的授权逻辑在一个地方的类。例如,在一个博客系统中,我们用 Post
模型对应的策略类 PostPolicy
(注意,这里的跟上面的不一样)来授权认证用户创建和更新博客相关的权限。
你可以使用 Artisan 命令 make:policy
生成策略。生成的策略放在 app/Policies
目录下,如果这个目录不存在,它会在第一次执行此命令时创建:
php artisan make:policy PostPolicy
make:policy
命令会生成一个空的策略类。如果需要生成带有基本「CRUD」策略方法的策略类。那么在执行 make:policy
命令是跟上 --model
选择吧:
php artisan make:policy PostPolicy --model=Post
提示:-D 所有的策略都是通过 Laravel 的服务容器解析的,因此你可以在策略类的构造函数里添加任何你需要依赖注入的实例。
注册策略
策略类生成完毕后需要注册它。AuthServiceProvider
的 policies
数组属性就是干这个的,在这里为你的 Eloquent Model 添加对应的策略类映射。注册后,Laravel 在对给定 Model 资源授权的时候,就知道来找哪个策略类了:
<?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();
//
}
}
写策略类
策略方法
策略类注册完毕后,再来看看怎样为每个操作添加对应的策略方法。在接下来的例子里,我们为 PostPolicy
定义一个 update
方法,用来判断是否指定的用户拥有更新博客的权限。
update
方法接受两个参数:User
实例和 Post
实例,我们最终返回 true
或者 false
来说明用户是否有权限进行操作。在此,我们使用的是用户的 id
字段来匹配博客的 user_id
字段的。
<?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;
}
}
你可以继续为你的其他操作添加对应策略方法,比如 view
或者 delete
啦,还有一点,策略方法的名字你是可以自由取的。
提示:-D 在控制台执行使用
--model
选项生成策略类的时候,会自动生成针对增删改查操作的create
、delete
、update
和view
策略方法。
无 Model 策略方法
「无模型策略方法」是指策略方法里不需要传入模型实例的情况。最常见的场景就是创建操作,我们假设它对应 create
策略方法。例如,你可能会判断一个用户是否有创建博客的权限,这时就不需要有博客实例对象了,只要给个认证用户就行了:
/**
* Determine if the given user can create posts.
*
* @param \App\User $user
* @return bool
*/
public function create(User $user)
{
//
}
策略过滤
对于一些用户,我们会碰到无论如何要给他整个策略类中所有方法的操作权限(想想管理员)。为了实现这个功能,在策略类中定义一个 before
方法就 OK。before
方法会在所有策略方法调用之前调用:
public function before($user, $ability)
{
if ($user->isSuperAdmin()) {
return true;
}
}
如果要拒绝用户的所有操作权限,直接返回 false
。如果返回 null
的话,就跟没设置 before
方法一样一样的。
重点提示( ⊙ o ⊙ ) 如果 Gates 能力对应的策略方法在策略类中未定义,
before
方法是不会被调用的。
使用策略授权操作
通过 User Model
Laravel 中的 User Model 中包含两个有用的授权方法:can
和 cant
。can
方法接收两个参数,第一个参数是操作名,第二个就是相关 Model 实例了。下面的例子里,我们判断用户是否有更新指定 Post
Model 实例的权限:
if ($user->can('update', $post)) {
//
}
can
方法会自动调用正确的策略类的 update
方法并返回一个布尔值。如果没有知道的话,就会去 Gates 中找有没有一个能力叫 update
的。
无 Model 操作
前面已经说过,对于创建这一类授权呢,定义时是不需要给 Model 实例的。那么在使用这样的一类授权方法时,怎么写呢?这时候,给 can
方法传递对应 Model 类名就 OK。
use App\Post;
if ($user->can('create'), Post::class) {
// Executes the "create" method on the relevant policy...
}
通过中间件
Laravel 提供了一个授权操作的中间件(Illuminate\Auth\Middleware\Authorize
),它会在你的请求到大路由或者控制器的时候,判断用户是否有相关权限。这个中间件已经在 App\Http\Kernel
类中设置了,并且赋予了 can
这个 Key。我们来看下,针对更新博客的操作,咱们怎么去使用它:
use App\Post;
Route::put('/posts/{post}', function (Post $post) {
// The current user may update the post...
})->middleware('can:update,post');
can
冒号后面用逗号隔开的就是两个参数。第一个参数是操作名,第二个参数是我们传递给策略方法的路由参数,在这里我们使用了隐式模型绑定,所以一个 Post
模型实例会被传递给策略方法。如果用户授权未通过,一个带有 403
状态码的 HTTP 响应就会通过该中间件生成。
无 Model 操作
一样的,对于不需要给 Model 实例的授权操作(比如创建)判断方法,我们需要给中间件传递的第二个参数是类名:
Route::post('/post', function () {
// The current user may create posts...
})->middleware('can:create,App\Post');
通过控制器辅助函数
除了 User
Model 提供的辅助函数,Laravel 为所有继承了 App\Http\Controllers\Controller
基类的控制器都提供了 auhtorize
辅助方法。类似 can
方法,authorize
方法接受操作名和相关 Model 实例对象作为参数。如果操作经过验证未授权,authorize
方法会抛出一个 Illuminate\Auth\Access\AuthorizationException
异常,Laravel 默认的异常处理器会把它转换为一个带有 403 状态码的 HTTP 响应:
<?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
*/
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
// The current user can update the blog post...
}
}
无 Model 操作
再一次,对于不需要给 Model 实例的授权操作判断方法,我们需要给 authorize
方法传递的第二个参数是类名:
/**
* Create a new blog post.
*
* @param Request $request
* @return Response
*/
public function create(Request $request)
{
$this->authorize('create', Post::class);
// The current user can create blog posts...
}
通过 Blade 模板
在 Blade 模板中,提供了两个用来判断用户是否具有操作权限的指令:@can
和 cannot
。它们在判断页面是否该显示一些内容上比较有用,使用方式如下:
@can('update', $post)
<!-- The Current User Can Update The Post -->
@elsecan('create', $post)
<!-- The Current User Can Create New Post -->
@endcan
@cannot('update', $post)
<!-- The Current User Can't Update The Post -->
@elsecannot('create', $post)
<!-- The Current User Can't Create New Post -->
@endcannot
上面的指令其实是 @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
无 Model 操作
你可以用 @can
和 @cannot
指令处理无 Model 操作的权限判断场景,需要传递的是相关类名:
@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
本作品采用《CC 协议》,转载必须注明作者和本文链接