Skip to content

授权

介绍

除了提供开箱即用的认证服务外,Laravel 还提供了一种简单的方法来组织授权逻辑和控制对资源的访问。我们将介绍多种方法和助手来帮助您组织授权逻辑,并在本文档中介绍每种方法。

lightbulb

授权功能是在 Laravel 5.1.11 中添加的,请在将这些功能集成到您的应用程序之前参考升级指南

定义能力

确定用户是否可以执行给定操作的最简单方法是使用 Illuminate\Auth\Access\Gate 类定义一个“能力”。Laravel 附带的 AuthServiceProvider 是定义应用程序所有能力的便捷位置。例如,让我们定义一个 update-post 能力,该能力接收当前的 User 和一个 Post 模型。在我们的能力中,我们将确定用户的 id 是否与帖子的 user_id 匹配:

php
<?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;
        });
    }
}

请注意,我们没有检查给定的 $user 是否为 NULL。当没有经过身份验证的用户或未使用 forUser 方法指定特定用户时,Gate 将自动为所有能力返回 false

基于类的能力

除了注册 Closures 作为授权回调外,您还可以通过传递包含类名和方法的字符串来注册类方法。在需要时,类将通过服务容器解析:

php
$gate->define('update-post', 'Class@method');

拦截授权检查

有时,您可能希望为特定用户授予所有能力。在这种情况下,使用 before 方法定义一个在所有其他授权检查之前运行的回调:

php
$gate->before(function ($user, $ability) {
    if ($user->isSuperAdmin()) {
        return true;
    }
});

如果 before 回调返回非空结果,则该结果将被视为检查的结果。

您可以使用 after 方法定义一个在每次授权检查后执行的回调。但是,您不能从 after 回调中修改授权检查的结果:

php
$gate->after(function ($user, $ability, $result, $arguments) {
    //
});

检查能力

通过 Gate Facade

一旦定义了能力,我们可以通过多种方式“检查”它。首先,我们可以使用 Gate facade 上的 checkallowsdenies 方法。所有这些方法都接收能力的名称和应传递给能力回调的参数。您不需要将当前用户传递给这些方法,因为 Gate 会自动将当前用户添加到传递给回调的参数中。因此,当检查我们之前定义的 update-post 能力时,我们只需将一个 Post 实例传递给 denies 方法:

php
<?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);
    	}

    	// 更新帖子...
    }
}

当然,allows 方法只是 denies 方法的反义词,如果操作被授权则返回 truecheck 方法是 allows 方法的别名。

检查特定用户的能力

如果您希望使用 Gate facade 检查当前身份验证用户以外的用户是否具有给定能力,可以使用 forUser 方法:

php
if (Gate::forUser($user)->allows('update-post', $post)) {
	//
}

传递多个参数

当然,能力回调可以接收多个参数:

php
Gate::define('delete-comment', function ($user, $post, $comment) {
	//
});

如果您的能力需要多个参数,只需将参数数组传递给 Gate 方法:

php
if (Gate::allows('delete-comment', [$post, $comment])) {
	//
}

通过用户模型

或者,您可以通过 User 模型实例检查能力。默认情况下,Laravel 的 App\User 模型使用一个 Authorizable trait,该 trait 提供两个方法:cancannot。这些方法可以类似于 Gate facade 上的 allowsdenies 方法使用。因此,使用我们之前的示例,我们可以这样修改代码:

php
<?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);
    	}

    	// 更新帖子...
    }
}

当然,can 方法只是 cannot 方法的反义词:

php
if ($request->user()->can('update-post', $post)) {
	// 更新帖子...
}

在 Blade 模板中

为了方便起见,Laravel 提供了 @can Blade 指令,以快速检查当前身份验证用户是否具有给定能力。例如:

php
<a href="/post/{{ $post->id }}">查看帖子</a>

@can('update-post', $post)
	<a href="/post/{{ $post->id }}/edit">编辑帖子</a>
@endcan

您还可以将 @can 指令与 @else 指令结合使用:

php
@can('update-post', $post)
	<!-- 当前用户可以更新帖子 -->
@else
	<!-- 当前用户不能更新帖子 -->
@endcan

在表单请求中

您还可以选择在表单请求authorize 方法中利用您的 Gate 定义的能力。例如:

php
/**
 * 确定用户是否有权发出此请求。
 *
 * @return bool
 */
public function authorize()
{
    $postId = $this->route('post');

    return Gate::allows('update', Post::findOrFail($postId));
}

策略

创建策略

由于在 AuthServiceProvider 中定义所有授权逻辑可能会在大型应用程序中变得繁琐,Laravel 允许您将授权逻辑拆分为“策略”类。策略是基于它们授权的资源分组授权逻辑的普通 PHP 类。

首先,让我们生成一个策略来管理我们的 Post 模型的授权。您可以使用 make:policy artisan 命令生成策略。生成的策略将放置在 app/Policies 目录中:

php
php artisan make:policy PostPolicy

注册策略

策略存在后,我们需要将其注册到 Gate 类中。AuthServiceProvider 包含一个 policies 属性,该属性将各种实体映射到管理它们的策略。因此,我们将指定 Post 模型的策略是 PostPolicy 类:

php
<?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);
    }
}

编写策略

策略生成并注册后,我们可以为其授权的每个能力添加方法。例如,让我们在我们的 PostPolicy 上定义一个 update 方法,该方法将确定给定的 User 是否可以“更新”一个 Post

php
<?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;
    }
}

您可以根据需要继续在策略上定义其他方法,以授权其授权的各种能力。例如,您可以定义 showdestroyaddComment 方法来授权各种 Post 操作。

lightbulb

所有策略都是通过 Laravel 服务容器解析的,这意味着您可以在策略的构造函数中类型提示任何需要的依赖项,并且它们将被自动注入。

拦截所有检查

有时,您可能希望在策略上为特定用户授予所有能力。在这种情况下,在策略上定义一个 before 方法。此方法将在策略上的所有其他授权检查之前运行:

php
public function before($user, $ability)
{
    if ($user->isSuperAdmin()) {
        return true;
    }
}

如果 before 方法返回非空结果,则该结果将被视为检查的结果。

检查策略

策略方法的调用方式与基于 Closure 的授权回调完全相同。您可以使用 Gate facade、User 模型、@can Blade 指令或 policy 助手。

通过 Gate Facade

Gate 将通过检查传递给其方法的参数的类自动确定使用哪个策略。因此,如果我们将一个 Post 实例传递给 denies 方法,Gate 将利用相应的 PostPolicy 来授权操作:

php
<?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);
    	}

    	// 更新帖子...
    }
}

通过用户模型

User 模型的 cancannot 方法在给定参数可用时也会自动利用策略。这些方法为您的应用程序检索的任何 User 实例提供了一种方便的方式来授权操作:

php
if ($user->can('update', $post)) {
	//
}

if ($user->cannot('update', $post)) {
	//
}

在 Blade 模板中

同样,@can Blade 指令将在给定参数可用时利用策略:

php
@can('update', $post)
	<!-- 当前用户可以更新帖子 -->
@endcan

通过策略助手

全局 policy 助手函数可用于检索给定类实例的 Policy 类。例如,我们可以将一个 Post 实例传递给 policy 助手,以获取我们相应的 PostPolicy 类的实例:

php
if (policy($post)->update($user, $post)) {
	//
}

控制器授权

默认情况下,Laravel 附带的基类 App\Http\Controllers\Controller 使用 AuthorizesRequests trait。此 trait 提供 authorize 方法,可用于快速授权给定操作,并在操作未被授权时抛出 HttpException

authorize 方法与其他各种授权方法(如 Gate::allows$user->can())具有相同的签名。因此,让我们使用 authorize 方法快速授权更新 Post 的请求:

php
<?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);

    	// 更新帖子...
    }
}

如果操作被授权,控制器将继续正常执行;但是,如果 authorize 方法确定操作未被授权,将自动抛出一个 HttpException,生成一个 HTTP 响应,状态码为 403 Not Authorized。正如您所看到的,authorize 方法是一种方便、快速的方法,可以用一行代码授权操作或抛出异常。

AuthorizesRequests trait 还提供 authorizeForUser 方法,以授权对非当前身份验证用户的操作:

php
$this->authorizeForUser($user, 'update', $post);

自动确定策略方法

通常,策略的方法将与控制器的方法对应。例如,在上面的 update 方法中,控制器方法和策略方法共享相同的名称:update

因此,Laravel 允许您只需将实例参数传递给 authorize 方法,并且将根据调用函数的名称自动确定被授权的能力。在此示例中,由于 authorize 是从控制器的 update 方法调用的,因此也将在 PostPolicy 上调用 update 方法:

php
/**
 * 更新给定的帖子。
 *
 * @param  int  $id
 * @return Response
 */
public function update($id)
{
	$post = Post::findOrFail($id);

	$this->authorize($post);

	// 更新帖子...
}