授权
介绍
除了提供开箱即用的认证服务外,Laravel 还提供了一种简单的方法来组织授权逻辑和控制对资源的访问。我们将介绍各种方法和助手来帮助您组织授权逻辑,并在本文档中介绍每种方法。
定义能力
确定用户是否可以执行给定操作的最简单方法是使用 Illuminate\Auth\Access\Gate
类定义一个“能力”。Laravel 附带的 AuthServiceProvider
是定义应用程序所有能力的便捷位置。例如,让我们定义一个 update-post
能力,该能力接收当前的 User
和一个 Post
模型。在我们的能力中,我们将确定用户的 id
是否与帖子的 user_id
匹配:
<?php
namespace App\Providers;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* 注册任何应用程序认证/授权服务。
*
* @param \Illuminate\Contracts\Auth\Access\Gate $gate
* @return void
*/
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
$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
14
15
16
17
18
19
20
21
22
23
24
请注意,我们没有检查给定的 $user
是否为 NULL
。当没有经过身份验证的用户或未使用 forUser
方法指定特定用户时,Gate
将自动为所有能力返回 false
。
基于类的能力
除了将 Closures
注册为授权回调外,您还可以通过传递包含类名和方法的字符串来注册类方法。在需要时,类将通过服务容器解析:
$gate->define('update-post', 'Class@method');
拦截授权检查
有时,您可能希望为特定用户授予所有能力。在这种情况下,使用 before
方法定义一个在所有其他授权检查之前运行的回调:
$gate->before(function ($user, $ability) {
if ($user->isSuperAdmin()) {
return true;
}
});
2
3
4
5
如果 before
回调返回非空结果,则该结果将被视为检查的结果。
您可以使用 after
方法定义一个在每次授权检查后执行的回调。但是,您不能从 after
回调中修改授权检查的结果:
$gate->after(function ($user, $ability, $result, $arguments) {
//
});
2
3
检查能力
通过 Gate Facade
一旦定义了能力,我们可以通过多种方式“检查”它。首先,我们可以使用 Gate
facade 上的 check
、allows
或 denies
方法。所有这些方法都接收能力的名称和应传递给能力回调的参数。您不需要将当前用户传递给这些方法,因为 Gate
会自动将当前用户添加到传递给回调的参数中。因此,当检查我们之前定义的 update-post
能力时,我们只需将一个 Post
实例传递给 denies
方法:
<?php
namespace App\Http\Controllers;
use Gate;
use App\User;
use App\Post;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* 更新给定的帖子。
*
* @param int $id
* @return Response
*/
public function update($id)
{
$post = Post::findOrFail($id);
if (Gate::denies('update-post', $post)) {
abort(403);
}
// 更新帖子...
}
}
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
当然,allows
方法只是 denies
方法的反义词,如果操作被授权则返回 true
。check
方法是 allows
方法的别名。
检查特定用户的能力
如果您希望使用 Gate
facade 检查当前经过身份验证的用户以外的用户是否具有给定能力,可以使用 forUser
方法:
if (Gate::forUser($user)->allows('update-post', $post)) {
//
}
2
3
传递多个参数
当然,能力回调可以接收多个参数:
Gate::define('delete-comment', function ($user, $post, $comment) {
//
});
2
3
如果您的能力需要多个参数,只需将参数数组传递给 Gate
方法:
if (Gate::allows('delete-comment', [$post, $comment])) {
//
}
2
3
通过用户模型
或者,您可以通过 User
模型实例检查能力。默认情况下,Laravel 的 App\User
模型使用一个 Authorizable
trait,该 trait 提供两个方法:can
和 cannot
。这些方法可以类似于 Gate
facade 上的 allows
和 denies
方法使用。因此,使用我们之前的示例,我们可以像这样修改我们的代码:
<?php
namespace App\Http\Controllers;
use App\Post;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* 更新给定的帖子。
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return Response
*/
public function update(Request $request, $id)
{
$post = Post::findOrFail($id);
if ($request->user()->cannot('update-post', $post)) {
abort(403);
}
// 更新帖子...
}
}
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
当然,can
方法只是 cannot
方法的反义词:
if ($request->user()->can('update-post', $post)) {
// 更新帖子...
}
2
3
在 Blade 模板中
为了方便起见,Laravel 提供了 @can
Blade 指令来快速检查当前经过身份验证的用户是否具有给定能力。例如:
<a href="/post/{{ $post->id }}">查看帖子</a>
@can('update-post', $post)
<a href="/post/{{ $post->id }}/edit">编辑帖子</a>
@endcan
2
3
4
5
您还可以将 @can
指令与 @else
指令结合使用:
@can('update-post', $post)
<!-- 当前用户可以更新帖子 -->
@else
<!-- 当前用户不能更新帖子 -->
@endcan
2
3
4
5
在表单请求中
您还可以选择在表单请求的 authorize
方法中利用您的 Gate
定义的能力。例如:
/**
* 确定用户是否有权发出此请求。
*
* @return bool
*/
public function authorize()
{
$postId = $this->route('post');
return Gate::allows('update', Post::findOrFail($postId));
}
2
3
4
5
6
7
8
9
10
11
策略
创建策略
由于在 AuthServiceProvider
中定义所有授权逻辑可能会在大型应用程序中变得繁琐,Laravel 允许您将授权逻辑拆分为“策略”类。策略是基于它们授权的资源分组授权逻辑的普通 PHP 类。
首先,让我们生成一个策略来管理我们的 Post
模型的授权。您可以使用 make:policy
artisan 命令生成策略。生成的策略将放置在 app/Policies
目录中:
php artisan make:policy PostPolicy
注册策略
策略存在后,我们需要将其注册到 Gate
类中。AuthServiceProvider
包含一个 policies
属性,该属性将各种实体映射到管理它们的策略。因此,我们将指定 Post
模型的策略是 PostPolicy
类:
<?php
namespace App\Providers;
use App\Post;
use App\Policies\PostPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* 应用程序的策略映射。
*
* @var array
*/
protected $policies = [
Post::class => PostPolicy::class,
];
/**
* 注册任何应用程序认证/授权服务。
*
* @param \Illuminate\Contracts\Auth\Access\Gate $gate
* @return void
*/
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
}
}
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
编写策略
一旦策略生成并注册,我们可以为其授权的每个能力添加方法。例如,让我们在我们的 PostPolicy
上定义一个 update
方法,该方法将确定给定的 User
是否可以“更新”一个 Post
:
<?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
您可以根据需要继续在策略上定义其他方法,以授权其授权的各种能力。例如,您可以定义 show
、destroy
或 addComment
方法来授权各种 Post
操作。
所有策略都是通过 Laravel 服务容器解析的,这意味着您可以在策略的构造函数中类型提示任何需要的依赖项,并且它们将被自动注入。
拦截所有检查
有时,您可能希望为策略上的特定用户授予所有能力。在这种情况下,在策略上定义一个 before
方法。此方法将在策略上的所有其他授权检查之前运行:
public function before($user, $ability)
{
if ($user->isSuperAdmin()) {
return true;
}
}
2
3
4
5
6
如果 before
方法返回非空结果,则该结果将被视为检查的结果。
检查策略
策略方法的调用方式与基于 Closure
的授权回调完全相同。您可以使用 Gate
facade、User
模型、@can
Blade 指令或 policy
助手。
通过 Gate Facade
Gate
将通过检查传递给其方法的参数的类来自动确定使用哪个策略。因此,如果我们将一个 Post
实例传递给 denies
方法,Gate
将利用相应的 PostPolicy
来授权操作:
<?php
namespace App\Http\Controllers;
use Gate;
use App\User;
use App\Post;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* 更新给定的帖子。
*
* @param int $id
* @return Response
*/
public function update($id)
{
$post = Post::findOrFail($id);
if (Gate::denies('update', $post)) {
abort(403);
}
// 更新帖子...
}
}
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
通过用户模型
User
模型的 can
和 cannot
方法也会在给定参数可用时自动利用策略。这些方法为您应用程序检索的任何 User
实例提供了一种方便的方式来授权操作:
if ($user->can('update', $post)) {
//
}
if ($user->cannot('update', $post)) {
//
}
2
3
4
5
6
7
在 Blade 模板中
同样,@can
Blade 指令将在给定参数可用时利用策略:
@can('update', $post)
<!-- 当前用户可以更新帖子 -->
@endcan
2
3
通过策略助手
全局 policy
助手函数可用于检索给定类实例的 Policy
类。例如,我们可以将一个 Post
实例传递给 policy
助手,以获取我们相应的 PostPolicy
类的实例:
if (policy($post)->update($user, $post)) {
//
}
2
3
控制器授权
默认情况下,Laravel 附带的基类 App\Http\Controllers\Controller
使用 AuthorizesRequests
trait。此 trait 提供 authorize
方法,可用于快速授权给定操作,并在操作未被授权时抛出 AuthorizationException
。
authorize
方法与其他各种授权方法(如 Gate::allows
和 $user->can()
)共享相同的签名。因此,让我们使用 authorize
方法快速授权更新 Post
的请求:
<?php
namespace App\Http\Controllers;
use App\Post;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* 更新给定的帖子。
*
* @param int $id
* @return Response
*/
public function update($id)
{
$post = Post::findOrFail($id);
$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
如果操作被授权,控制器将继续正常执行;然而,如果 authorize
方法确定操作未被授权,将自动抛出一个 AuthorizationException
,该异常生成一个 HTTP 响应,状态码为 403 Not Authorized
。正如您所看到的,authorize
方法是一种方便、快速的方法,可以用一行代码授权操作或抛出异常。
AuthorizesRequests
trait 还提供 authorizeForUser
方法,以授权对非当前经过身份验证用户的操作:
$this->authorizeForUser($user, 'update', $post);
自动确定策略方法
通常,策略的方法将与控制器的方法对应。例如,在上面的 update
方法中,控制器方法和策略方法共享相同的名称:update
。
因此,Laravel 允许您只需将实例参数传递给 authorize
方法,并且将根据调用函数的名称自动确定被授权的能力。在此示例中,由于 authorize
是从控制器的 update
方法调用的,因此 PostPolicy
上的 update
方法也将被调用:
/**
* 更新给定的帖子。
*
* @param int $id
* @return Response
*/
public function update($id)
{
$post = Post::findOrFail($id);
$this->authorize($post);
// 更新帖子...
}
2
3
4
5
6
7
8
9
10
11
12
13
14