授权
介绍
除了提供开箱即用的认证服务外,Laravel还提供了一种简单的方法来对用户操作进行授权。与认证类似,Laravel的授权方法也很简单,主要有两种授权操作的方法:门卫和策略。
可以将门卫和策略视为路由和控制器。门卫提供了一种简单的、基于闭包的授权方法,而策略则像控制器一样,将其逻辑围绕特定的模型或资源进行分组。我们将首先探讨门卫,然后研究策略。
在构建应用程序时,您不需要在门卫和策略之间做出选择。大多数应用程序可能会同时包含门卫和策略的混合使用,这完全没有问题!门卫最适用于与任何模型或资源无关的操作,例如查看管理员仪表板。相反,当您希望为特定模型或资源授权操作时,应使用策略。
门卫
编写门卫
门卫是确定用户是否被授权执行给定操作的闭包,通常在App\Providers\AuthServiceProvider
类中使用Gate
facade定义。门卫总是接收一个用户实例作为其第一个参数,并可以选择接收其他参数,例如相关的Eloquent模型:
/**
* 注册任何认证/授权服务。
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Gate::define('update-post', function ($user, $post) {
return $user->id == $post->user_id;
});
}
2
3
4
5
6
7
8
9
10
11
12
13
门卫也可以使用Class@method
样式的回调字符串定义,类似于控制器:
/**
* 注册任何认证/授权服务。
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Gate::define('update-post', 'App\Policies\PostPolicy@update');
}
2
3
4
5
6
7
8
9
10
11
资源门卫
您还可以使用resource
方法一次定义多个门卫能力:
Gate::resource('posts', 'App\Policies\PostPolicy');
这与手动定义以下门卫定义相同:
Gate::define('posts.view', 'App\Policies\PostPolicy@view');
Gate::define('posts.create', 'App\Policies\PostPolicy@create');
Gate::define('posts.update', 'App\Policies\PostPolicy@update');
Gate::define('posts.delete', 'App\Policies\PostPolicy@delete');
2
3
4
默认情况下,将定义view
、create
、update
和delete
能力。您可以通过将数组作为第三个参数传递给resource
方法来覆盖默认能力。数组的键定义能力的名称,而值定义方法名称。例如,以下代码将仅创建两个新的门卫定义 - posts.image
和posts.photo
:
Gate::resource('posts', 'PostPolicy', [
'image' => 'updateImage',
'photo' => 'updatePhoto',
]);
2
3
4
通过门卫授权操作
要使用门卫授权操作,您应该使用allows
或denies
方法。请注意,您不需要将当前认证的用户传递给这些方法。Laravel会自动处理将用户传递给门卫闭包:
if (Gate::allows('update-post', $post)) {
// 当前用户可以更新帖子...
}
if (Gate::denies('update-post', $post)) {
// 当前用户不能更新帖子...
}
2
3
4
5
6
7
如果您想确定特定用户是否被授权执行某个操作,可以在Gate
facade上使用forUser
方法:
if (Gate::forUser($user)->allows('update-post', $post)) {
// 用户可以更新帖子...
}
if (Gate::forUser($user)->denies('update-post', $post)) {
// 用户不能更新帖子...
}
2
3
4
5
6
7
拦截门卫检查
有时,您可能希望为特定用户授予所有能力。您可以使用before
方法定义一个在所有其他授权检查之前运行的回调:
Gate::before(function ($user, $ability) {
if ($user->isSuperAdmin()) {
return true;
}
});
2
3
4
5
如果before
回调返回非空结果,该结果将被视为检查的结果。
您可以使用after
方法定义一个在所有其他授权检查之后执行的回调:
Gate::after(function ($user, $ability, $result, $arguments) {
if ($user->isSuperAdmin()) {
return true;
}
});
2
3
4
5
与before
检查类似,如果after
回调返回非空结果,该结果将被视为检查的结果。
创建策略
生成策略
策略是围绕特定模型或资源组织授权逻辑的类。例如,如果您的应用程序是一个博客,您可能有一个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
属性,该属性将您的Eloquent模型映射到其相应的策略。注册策略将指示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();
//
}
}
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
编写策略
策略方法
策略注册后,您可以为其授权的每个操作添加方法。例如,让我们在PostPolicy
上定义一个update
方法,该方法确定给定的User
是否可以更新给定的Post
实例。
update
方法将接收一个User
和一个Post
实例作为其参数,并应返回true
或false
,指示用户是否被授权更新给定的Post
。因此,对于此示例,让我们验证用户的id
是否与帖子的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;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
您可以根据需要继续在策略上定义其他方法,以授权其授权的各种操作。例如,您可以定义view
或delete
方法来授权各种Post
操作,但请记住,您可以自由地为策略方法命名。
如果您在通过Artisan控制台生成策略时使用了--model
选项,它将已经包含view
、create
、update
、delete
、restore
和forceDelete
操作的方法。
无模型的方法
某些策略方法仅接收当前认证的用户,而不接收其授权的模型实例。这种情况在授权create
操作时最为常见。例如,如果您正在创建一个博客,您可能希望检查用户是否被授权创建任何帖子。
在定义不会接收模型实例的策略方法时,例如create
方法,它将不会接收模型实例。相反,您应该将方法定义为仅期望认证用户:
/**
* 确定给定用户是否可以创建帖子。
*
* @param \App\User $user
* @return bool
*/
public function create(User $user)
{
//
}
2
3
4
5
6
7
8
9
10
访客用户
默认情况下,如果传入的HTTP请求不是由认证用户发起的,所有门卫和策略将自动返回false
。但是,您可以通过声明“可选”类型提示或为用户参数定义提供null
默认值来允许这些授权检查通过到您的门卫和策略:
<?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;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
策略过滤器
对于某些用户,您可能希望在给定策略中授权所有操作。为此,请在策略上定义一个before
方法。before
方法将在策略上的任何其他方法之前执行,给您一个机会在实际调用预期的策略方法之前授权操作。此功能最常用于授权应用程序管理员执行任何操作:
public function before($user, $ability)
{
if ($user->isSuperAdmin()) {
return true;
}
}
2
3
4
5
6
如果您希望为用户拒绝所有授权,您应该从before
方法返回false
。如果返回null
,授权将通过到策略方法。
如果类不包含与被检查能力名称匹配的方法,策略类的before
方法将不会被调用。
使用策略授权操作
通过用户模型
Laravel应用程序中包含的User
模型提供了两个用于授权操作的有用方法:can
和cant
。can
方法接收您希望授权的操作和相关模型。例如,让我们确定用户是否被授权更新给定的Post
模型:
if ($user->can('update', $post)) {
//
}
2
3
如果为给定模型注册了策略,can
方法将自动调用适当的策略并返回布尔结果。如果没有为模型注册策略,can
方法将尝试调用与给定操作名称匹配的基于闭包的门卫。
不需要模型的操作
请记住,某些操作如create
可能不需要模型实例。在这些情况下,您可以将类名传递给can
方法。类名将用于确定在授权操作时使用哪个策略:
use App\Post;
if ($user->can('create', Post::class)) {
// 执行相关策略上的“create”方法...
}
2
3
4
5
通过中间件
Laravel包含一个中间件,可以在传入请求到达您的路由或控制器之前授权操作。默认情况下,Illuminate\Auth\Middleware\Authorize
中间件在您的App\Http\Kernel
类中分配了can
键。让我们探讨一个使用can
中间件授权用户可以更新博客帖子的示例:
use App\Post;
Route::put('/post/{post}', function (Post $post) {
// 当前用户可以更新帖子...
})->middleware('can:update,post');
2
3
4
5
在此示例中,我们将can
中间件传递了两个参数。第一个是我们希望授权的操作名称,第二个是我们希望传递给策略方法的路由参数。在这种情况下,由于我们使用了隐式模型绑定,一个Post
模型将被传递给策略方法。如果用户无权执行给定操作,中间件将生成一个带有403
状态码的HTTP响应。
不需要模型的操作
同样,某些操作如create
可能不需要模型实例。在这些情况下,您可以将类名传递给中间件。类名将用于确定在授权操作时使用哪个策略:
Route::post('/post', function () {
// 当前用户可以创建帖子...
})->middleware('can:create,App\Post');
2
3
通过控制器助手
除了提供给User
模型的有用方法外,Laravel还为扩展App\Http\Controllers\Controller
基类的任何控制器提供了一个有用的authorize
方法。与can
方法一样,此方法接受您希望授权的操作名称和相关模型。如果操作未被授权,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
{
/**
* 更新给定的博客帖子。
*
* @param Request $request
* @param Post $post
* @return Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
// 当前用户可以更新博客帖子...
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
不需要模型的操作
如前所述,某些操作如create
可能不需要模型实例。在这些情况下,您可以将类名传递给authorize
方法。类名将用于确定在授权操作时使用哪个策略:
/**
* 创建一个新的博客帖子。
*
* @param Request $request
* @return Response
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create(Request $request)
{
$this->authorize('create', Post::class);
// 当前用户可以创建博客帖子...
}
2
3
4
5
6
7
8
9
10
11
12
13
授权资源控制器
如果您正在使用资源控制器,您可以在控制器的构造函数中使用authorizeResource
方法。此方法将适当的can
中间件定义附加到资源控制器的方法。
authorizeResource
方法接受模型的类名作为其第一个参数,以及将包含模型ID的路由/请求参数的名称作为其第二个参数:
<?php
namespace App\Http\Controllers;
use App\Post;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
public function __construct()
{
$this->authorizeResource(Post::class, 'post');
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
您可以使用make:policy
命令和--model
选项快速为给定模型生成策略类:php artisan make:policy PostPolicy --model=Post
。
通过Blade模板
在编写Blade模板时,您可能希望仅在用户被授权执行给定操作时显示页面的一部分。例如,您可能希望仅在用户可以实际更新帖子时显示博客帖子的更新表单。在这种情况下,您可以使用@can
和@cannot
指令系列:
@can('update', $post)
<!-- 当前用户可以更新帖子 -->
@elsecan('create', App\Post::class)
<!-- 当前用户可以创建新帖子 -->
@endcan
@cannot('update', $post)
<!-- 当前用户不能更新帖子 -->
@elsecannot('create', App\Post::class)
<!-- 当前用户不能创建新帖子 -->
@endcannot
2
3
4
5
6
7
8
9
10
11
这些指令是编写@if
和@unless
语句的便捷快捷方式。上面的@can
和@cannot
语句分别转换为以下语句:
@if (Auth::user()->can('update', $post))
<!-- 当前用户可以更新帖子 -->
@endif
@unless (Auth::user()->can('update', $post))
<!-- 当前用户不能更新帖子 -->
@endunless
2
3
4
5
6
7
不需要模型的操作
与大多数其他授权方法一样,如果操作不需要模型实例,您可以将类名传递给@can
和@cannot
指令:
@can('create', App\Post::class)
<!-- 当前用户可以创建帖子 -->
@endcan
@cannot('create', App\Post::class)
<!-- 当前用户不能创建帖子 -->
@endcannot
2
3
4
5
6
7