Authorization

Introduction

除了提供内置authentication 服务,Laravel 还提供了一种简单的方法来授权用户对给定资源的操作。例如,即使用户通过了身份验证,他们也可能无权更新或删除您的应用程序管理的某些 Eloquent 模型或数据库记录。 Laravel 的授权功能提供了一种简单、有组织的方式来管理这些类型的授权检查。

Laravel 提供了两种主要的授权操作方式:gatespolicies.想想像路由和控制器这样的门和策略。 Gates 提供了一种简单的、基于闭包的授权方法,而策略(如控制器)围绕特定模型或资源对逻辑进行分组。在本文档中,我们将首先探索门,然后检查策略。

在构建应用程序时,您无需在只使用门或只使用策略之间做出选择。大多数应用程序很可能包含一些门和策略的混合体,这很好! Gates 最适用于与任何模型或资源无关的操作,例如查看管理员仪表板。相反,当您希望为特定模型或资源授权操作时,应使用策略。

Gates

写作门

Warning
Gates 是学习 Laravel 授权功能基础知识的好方法;然而,在构建健壮的 Laravel 应用程序时,你应该考虑使用policies 组织您的授权规则。

门只是闭包,用于确定用户是否有权执行给定操作。通常,门定义在boot 的方法App\Providers\AuthServiceProvider 类使用Gate 正面。盖茨总是接收一个用户实例作为他们的第一个参数,并且可以选择接收其他参数,例如相关的 Eloquent 模型。

在此示例中,我们将定义一个门来确定用户是否可以更新给定的App\Models\Post 模型。门将通过比较用户的id 反对这user_id 创建帖子的用户:

use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Gate;

/**
 * Register any authentication / authorization services.
 */
public function boot(): void
{
    Gate::define('update-post', function (User $user, Post $post) {
        return $user->id === $post->user_id;
    });
}

像控制器一样,门也可以使用类回调数组来定义:

use App\Policies\PostPolicy;
use Illuminate\Support\Facades\Gate;

/**
 * Register any authentication / authorization services.
 */
public function boot(): void
{
    Gate::define('update-post', [PostPolicy::class, 'update']);
}

授权操作

要使用门授权一个动作,你应该使用allows 或者denies 提供的方法Gate 正面。请注意,您不需要将当前经过身份验证的用户传递给这些方法。 Laravel 将自动处理将用户传递到门关闭中的问题。在执行需要授权的操作之前,通常会在应用程序的控制器中调用门授权方法:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Models\Post;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;

class PostController extends Controller
{
    /**
     * Update the given post.
     */
    public function update(Request $request, Post $post): RedirectResponse
    {
        if (! Gate::allows('update-post', $post)) {
            abort(403);
        }

        // Update the post...

        return redirect('/posts');
    }
}

如果您想确定当前经过身份验证的用户以外的用户是否有权执行某项操作,您可以使用forUser 上的方法Gate 正面:

if (Gate::forUser($user)->allows('update-post', $post)) {
    // The user can update the post...
}

if (Gate::forUser($user)->denies('update-post', $post)) {
    // The user can't update the post...
}

您可以使用any 或者none 方法:

if (Gate::any(['update-post', 'delete-post'], $post)) {
    // The user can update or delete the post...
}

if (Gate::none(['update-post', 'delete-post'], $post)) {
    // The user can't update or delete the post...
}

授权或抛出异常

如果你想尝试授权一个动作并自动抛出一个Illuminate\Auth\Access\AuthorizationException 如果不允许用户执行给定的操作,您可以使用Gate 门面的authorize 方法。的实例AuthorizationException Laravel 的异常处理程序自动将其转换为 403 HTTP 响应:

Gate::authorize('update-post', $post);

// The action is authorized...

提供额外的上下文

授权能力的门方法(allows,denies,check,any,none,authorize,can,cannot) 和授权刀片指令(@can,@cannot,@canany) 可以接收一个数组作为它们的第二个参数。这些数组元素作为参数传递给门闭包,并且可以在做出授权决策时用于其他上下文:

use App\Models\Category;
use App\Models\User;
use Illuminate\Support\Facades\Gate;

Gate::define('create-post', function (User $user, Category $category, bool $pinned) {
    if (! $user->canPublishToGroup($category->group)) {
        return false;
    } elseif ($pinned && ! $user->canPinPosts()) {
        return false;
    }

    return true;
});

if (Gate::check('create-post', [$category, $pinned])) {
    // The user can create the post...
}

门响应

到目前为止,我们只检查了返回简单布尔值的门。但是,有时您可能希望返回更详细的响应,包括错误消息。为此,您可以返回一个Illuminate\Auth\Access\Response 从你的门:

use App\Models\User;
use Illuminate\Auth\Access\Response;
use Illuminate\Support\Facades\Gate;

Gate::define('edit-settings', function (User $user) {
    return $user->isAdmin
                ? Response::allow()
                : Response::deny('You must be an administrator.');
});

即使您从您的门返回授权响应,Gate::allows 方法仍然会返回一个简单的布尔值;但是,您可以使用Gate::inspect 获取门返回的完整授权响应的方法:

$response = Gate::inspect('edit-settings');

if ($response->allowed()) {
    // The action is authorized...
} else {
    echo $response->message();
}

当使用Gate::authorize 方法,它抛出一个AuthorizationException 如果操作未被授权,则授权响应提供的错误消息将传播到 HTTP 响应:

Gate::authorize('edit-settings');

// The action is authorized...

自定义 HTTP 响应状态

当一个动作被门拒绝时,一个403 返回 HTTP 响应;然而,有时返回一个替代的 HTTP 状态代码是有用的。您可以自定义为失败的授权检查返回的 HTTP 状态代码,使用denyWithStatus 上的静态构造函数Illuminate\Auth\Access\Response 班级:

use App\Models\User;
use Illuminate\Auth\Access\Response;
use Illuminate\Support\Facades\Gate;

Gate::define('edit-settings', function (User $user) {
    return $user->isAdmin
                ? Response::allow()
                : Response::denyWithStatus(404);
});

因为通过隐藏资源404 响应是 Web 应用程序的常见模式,denyAsNotFound 为方便起见提供方法:

use App\Models\User;
use Illuminate\Auth\Access\Response;
use Illuminate\Support\Facades\Gate;

Gate::define('edit-settings', function (User $user) {
    return $user->isAdmin
                ? Response::allow()
                : Response::denyAsNotFound();
});

拦截门检查

有时,您可能希望将所有能力授予特定用户。您可以使用before 定义在所有其他授权检查之前运行的闭包的方法:

use App\Models\User;
use Illuminate\Support\Facades\Gate;

Gate::before(function (User $user, string $ability) {
    if ($user->isAdministrator()) {
        return true;
    }
});

如果before 闭包返回一个非空结果,该结果将被视为授权检查的结果。

您可以使用after 定义在所有其他授权检查之后执行的闭包的方法:

use App\Models\User;

Gate::after(function (User $user, string $ability, bool|null $result, mixed $arguments) {
    if ($user->isAdministrator()) {
        return true;
    }
});

类似于before 方法,如果after 闭包返回一个非空结果,该结果将被视为授权检查的结果。

内联授权

有时,您可能希望确定当前经过身份验证的用户是否有权执行给定操作,而无需编写与该操作相对应的专用门。 Laravel 允许你通过Gate::allowIfGate::denyIf 方法:

use App\Models\User;
use Illuminate\Support\Facades\Gate;

Gate::allowIf(fn (User $user) => $user->isAdministrator());

Gate::denyIf(fn (User $user) => $user->banned());

如果该操作未被授权或当前没有用户经过身份验证,Laravel 将自动抛出一个Illuminate\Auth\Access\AuthorizationException 例外。的实例AuthorizationException Laravel 的异常处理程序会自动将其转换为 403 HTTP 响应。

创建策略

生成策略

策略是围绕特定模型或资源组织授权逻辑的类。例如,如果您的应用程序是一个博客,您可能有一个App\Models\Post 模型和相应的App\Policies\PostPolicy 授权用户操作,例如创建或更新帖子。

您可以使用make:policy 工匠命令。生成的策略将放置在app/Policies 目录。如果这个目录在你的应用程序中不存在,Laravel 会为你创建它:

php artisan make:policy PostPolicy

make:policy 命令将生成一个空的策略类。如果您想生成一个类,其中包含与查看、创建、更新和删除资源相关的示例策略方法,您可以提供--model 执行命令时的选项:

php artisan make:policy PostPolicy --model=Post

注册政策

创建策略类后,需要对其进行注册。注册策略是我们如何通知 Laravel 在针对给定模型类型授权操作时使用哪个策略。

App\Providers\AuthServiceProvider 包含在新的 Laravel 应用程序中包含一个policies 将 Eloquent 模型映射到相应策略的属性。注册一个策略将指示 Laravel 在授权针对给定 Eloquent 模型的操作时使用哪个策略:

<?php

namespace App\Providers;

use App\Models\Post;
use App\Policies\PostPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * The policy mappings for the application.
     *
     * @var array
     */
    protected $policies = [
        Post::class => PostPolicy::class,
    ];

    /**
     * Register any application authentication / authorization services.
     */
    public function boot(): void
    {
        // ...
    }
}

策略自动发现

只要模型和策略遵循标准的 Laravel 命名约定,Laravel 就可以自动发现策略,而不是手动注册模型策略。具体来说,政策必须在Policies 包含您的模型的目录或之上的目录。因此,例如,模型可以放置在app/Models 目录,而策略可以放在app/Policies 目录。在这种情况下,Laravel 将检查策略app/Models/Policies然后app/Policies.此外,策略名称必须与模型名称相匹配,并且具有Policy 后缀。所以,一个User 模型将对应于UserPolicy 政策类。

如果您想定义自己的策略发现逻辑,您可以使用注册自定义策略发现回调Gate::guessPolicyNamesUsing 方法。通常,应从boot 你的应用程序的方法AuthServiceProvider:

use Illuminate\Support\Facades\Gate;

Gate::guessPolicyNamesUsing(function (string $modelClass) {
    // Return the name of the policy class for the given model...
});

Warning
明确映射到您的任何政策AuthServiceProvider 将优先于任何可能自动发现的策略。

编写策略

政策方法

注册策略类后,您可以为其授权的每个操作添加方法。例如,让我们定义一个update 我们的方法PostPolicy 这决定了给定的App\Models\User 可以更新给定的App\Models\Post 实例。

update 方法将收到一个User 和一个Post 实例作为它的参数,并且应该返回true 或者false 表明用户是否被授权更新给定的Post.所以,在这个例子中,我们将验证用户的id 匹配user_id 在帖子上:

<?php

namespace App\Policies;

use App\Models\Post;
use App\Models\User;

class PostPolicy
{
    /**
     * Determine if the given post can be updated by the user.
     */
    public function update(User $user, Post $post): bool
    {
        return $user->id === $post->user_id;
    }
}

您可以根据需要继续为策略授权的各种操作定义其他方法。例如,您可以定义view 或者delete 授权各种方法Post 相关操作,但请记住,您可以自由地为您的策略方法指定任何您喜欢的名称。

如果您使用了--model 通过 Artisan 控制台生成策略时的选项,它已经包含了用于viewAny,view,create,update,delete,restore, 和forceDelete 动作。

Note
所有政策都通过 Laravel 解决服务容器,允许您在策略的构造函数中对任何需要的依赖项进行类型提示,以自动注入它们。

政策回应

到目前为止,我们只检查了返回简单布尔值的策略方法。但是,有时您可能希望返回更详细的响应,包括错误消息。为此,您可以返回一个Illuminate\Auth\Access\Response 来自您的策略方法的实例:

use App\Models\Post;
use App\Models\User;
use Illuminate\Auth\Access\Response;

/**
 * Determine if the given post can be updated by the user.
 */
public function update(User $user, Post $post): Response
{
    return $user->id === $post->user_id
                ? Response::allow()
                : Response::deny('You do not own this post.');
}

从您的策略返回授权响应时,Gate::allows 方法仍然会返回一个简单的布尔值;但是,您可以使用Gate::inspect 获取门返回的完整授权响应的方法:

use Illuminate\Support\Facades\Gate;

$response = Gate::inspect('update', $post);

if ($response->allowed()) {
    // The action is authorized...
} else {
    echo $response->message();
}

当使用Gate::authorize 方法,它抛出一个AuthorizationException 如果操作未被授权,则授权响应提供的错误消息将传播到 HTTP 响应:

Gate::authorize('update', $post);

// The action is authorized...

自定义 HTTP 响应状态

当通过策略方法拒绝操作时,403 返回 HTTP 响应;然而,有时返回一个替代的 HTTP 状态代码是有用的。您可以自定义为失败的授权检查返回的 HTTP 状态代码,使用denyWithStatus 上的静态构造函数Illuminate\Auth\Access\Response 班级:

use App\Models\Post;
use App\Models\User;
use Illuminate\Auth\Access\Response;

/**
 * Determine if the given post can be updated by the user.
 */
public function update(User $user, Post $post): Response
{
    return $user->id === $post->user_id
                ? Response::allow()
                : Response::denyWithStatus(404);
}

因为通过隐藏资源404 响应是 Web 应用程序的常见模式,denyAsNotFound 为方便起见提供方法:

use App\Models\Post;
use App\Models\User;
use Illuminate\Auth\Access\Response;

/**
 * Determine if the given post can be updated by the user.
 */
public function update(User $user, Post $post): Response
{
    return $user->id === $post->user_id
                ? Response::allow()
                : Response::denyAsNotFound();
}

没有模型的方法

一些策略方法只接收当前经过身份验证的用户的实例。这种情况在授权时最常见create 动作。例如,如果您正在创建博客,您可能希望确定用户是否有权创建任何帖子。在这些情况下,您的策略方法应该只期望接收一个用户实例:

/**
 * Determine if the given user can create posts.
 */
public function create(User $user): bool
{
    return $user->role == 'writer';
}

来宾用户

默认情况下,所有门和策略自动返回false 如果传入的 HTTP 请求不是由经过身份验证的用户发起的。但是,您可以通过声明“可选”类型提示或提供一个null 用户参数定义的默认值:

<?php

namespace App\Policies;

use App\Models\Post;
use App\Models\User;

class PostPolicy
{
    /**
     * Determine if the given post can be updated by the user.
     */
    public function update(?User $user, Post $post): bool
    {
        return $user?->id === $post->user_id;
    }
}

政策过滤器

对于某些用户,您可能希望授权给定策略中的所有操作。为此,定义一个before 策略上的方法。这before 方法将在策略的任何其他方法之前执行,让您有机会在实际调用预期的策略方法之前授权操作。此功能最常用于授权应用程序管理员执行任何操作:

use App\Models\User;

/**
 * Perform pre-authorization checks.
 */
public function before(User $user, string $ability): bool|null
{
    if ($user->isAdministrator()) {
        return true;
    }

    return null;
}

如果您想拒绝对特定类型用户的所有授权检查,那么您可以返回false 来自before 方法。如果null 返回时,授权检查将落入策略方法。

Warning
before 如果类不包含名称与被检查的能力名称匹配的方法,则不会调用策略类的方法。

使用策略授权操作

通过用户模型

App\Models\User Laravel 应用程序中包含的模型包括两个有用的方法来授权操作:cancannot.这cancannot 方法接收您希望授权的操作的名称和相关模型。例如,让我们确定用户是否有权更新给定的App\Models\Post 模型。通常,这将在控制器方法中完成:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Models\Post;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class PostController extends Controller
{
    /**
     * Update the given post.
     */
    public function update(Request $request, Post $post): RedirectResponse
    {
        if ($request->user()->cannot('update', $post)) {
            abort(403);
        }

        // Update the post...

        return redirect('/posts');
    }
}

如果一个政策已注册 对于给定的模型,can 方法将自动调用适当的策略并返回布尔结果。如果没有为模型注册策略,则can 方法将尝试调用与给定操作名称匹配的基于闭包的 Gate。

不需要模型的操作

请记住,某些操作可能对应于策略方法,例如create 不需要模型实例。在这些情况下,您可以将类名传递给can 方法。类名将用于确定在授权操作时使用哪个策略:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Models\Post;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class PostController extends Controller
{
    /**
     * Create a post.
     */
    public function store(Request $request): RedirectResponse
    {
        if ($request->user()->cannot('create', Post::class)) {
            abort(403);
        }

        // Create the post...

        return redirect('/posts');
    }
}

通过控制器助手

除了提供给App\Models\User 模型,Laravel 提供了一个有用的authorize 任何控制器的方法,它扩展了App\Http\Controllers\Controller 基类。

can 方法,此方法接受您希望授权的操作的名称和相关模型。如果该操作未经授权,则authorize 方法将抛出一个Illuminate\Auth\Access\AuthorizationException Laravel 异常处理程序将自动转换为带有 403 状态代码的 HTTP 响应的异常:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Models\Post;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class PostController extends Controller
{
    /**
     * Update the given blog post.
     *
     * @throws \Illuminate\Auth\Access\AuthorizationException
     */
    public function update(Request $request, Post $post): RedirectResponse
    {
        $this->authorize('update', $post);

        // The current user can update the blog post...

        return redirect('/posts');
    }
}

不需要模型的操作

如前所述,一些策略方法如create 不需要模型实例。在这些情况下,您应该将类​​名传递给authorize 方法。类名将用于确定在授权操作时使用哪个策略:

use App\Models\Post;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

/**
 * Create a new blog post.
 *
 * @throws \Illuminate\Auth\Access\AuthorizationException
 */
public function create(Request $request): RedirectResponse
{
    $this->authorize('create', Post::class);

    // The current user can create blog posts...

    return redirect('/posts');
}

授权资源控制器

如果您正在使用资源控制器, 你可以利用authorizeResource 控制器构造函数中的方法。此方法将附加适当的can 资源控制器方法的中间件定义。

authorizeResource 方法接受模型的类名作为其第一个参数,并将包含模型 ID 的路由/请求参数的名称作为其第二个参数。你应该确保你的资源控制器 是使用创建的--model 标志,使其具有所需的方法签名和类型提示:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Models\Post;
use Illuminate\Http\Request;

class PostController extends Controller
{
    /**
     * Create the controller instance.
     */
    public function __construct()
    {
        $this->authorizeResource(Post::class, 'post');
    }
}

以下控制器方法将映射到它们相应的策略方法。当请求被路由到给定的控制器方法时,相应的策略方法将在控制器方法执行之前被自动调用:

控制器方法 策略方法
index viewAny
show view
create create
store create
edit update
update update
destroy delete

Note
您可以使用make:policy 命令与--model 为给定模型快速生成策略类的选项:php artisan make:policy PostPolicy --model=Post.

通过中间件

Laravel 包含一个中间件,它可以在传入请求到达您的路由或控制器之前授权操作。默认情况下,Illuminate\Auth\Middleware\Authorize 中间件被分配了can 键入你的App\Http\Kernel 班级。让我们探索一个使用can 授权用户可以更新帖子的中间件:

use App\Models\Post;

Route::put('/post/{post}', function (Post $post) {
    // The current user may update the post...
})->middleware('can:update,post');

在这个例子中,我们传递can 中间件两个参数。第一个是我们希望授权的操作的名称,第二个是我们希望传递给策略方法的路由参数。在这种情况下,因为我们正在使用隐式模型绑定, AApp\Models\Post 模型将传递给策略方法。如果用户无权执行给定的操作,中间件将返回带有 403 状态代码的 HTTP 响应。

为方便起见,您还可以附上can 使用中间件到您的路线can 方法:

use App\Models\Post;

Route::put('/post/{post}', function (Post $post) {
    // The current user may update the post...
})->can('update', 'post');

不需要模型的操作

同样,一些策略方法如create 不需要模型实例。在这些情况下,您可以将类名传递给中间件。类名将用于确定在授权操作时使用哪个策略:

Route::post('/post', function () {
    // The current user may create posts...
})->middleware('can:create,App\Models\Post');

在字符串中间件定义中指定整个类名可能会变得很麻烦。因此,您可以选择附上can使用中间件到您的路线can 方法:

use App\Models\Post;

Route::post('/post', function () {
    // The current user may create posts...
})->can('create', Post::class);

通过刀片模板

在编写 Blade 模板时,您可能希望仅在用户被授权执行给定操作时显示页面的一部分。例如,您可能希望仅在用户实际可以更新博文时才显示博文的更新表单。在这种情况下,您可以使用@can@cannot 指令:

@can('update', $post)
    <!-- The current user can update the post... -->
@elsecan('create', App\Models\Post::class)
    <!-- The current user can create new posts... -->
@else
    <!-- ... -->
@endcan

@cannot('update', $post)
    <!-- The current user cannot update the post... -->
@elsecannot('create', App\Models\Post::class)
    <!-- The current user cannot create new posts... -->
@endcannot

这些指令是编写的便捷快捷方式@if@unless 声明。这@can@cannot 上面的语句等同于下面的语句:

@if (Auth::user()->can('update', $post))
    <!-- The current user can update the post... -->
@endif

@unless (Auth::user()->can('update', $post))
    <!-- The current user cannot update the post... -->
@endunless

您还可以确定用户是否有权执行给定操作数组中的任何操作。为此,请使用@canany 指示:

@canany(['update', 'view', 'delete'], $post)
    <!-- The current user can update, view, or delete the post... -->
@elsecanany(['create'], \App\Models\Post::class)
    <!-- The current user can create a post... -->
@endcanany

不需要模型的操作

像大多数其他授权方法一样,您可以将类名传递给@can@cannot 如果操作不需要模型实例,则指令:

@can('create', App\Models\Post::class)
    <!-- The current user can create posts... -->
@endcan

@cannot('create', App\Models\Post::class)
    <!-- The current user can't create posts... -->
@endcannot

提供额外的上下文

当使用策略授权操作时,您可以将数组作为第二个参数传递给各种授权函数和助手。数组中的第一个元素将用于确定应调用哪个策略,而其余数组元素将作为参数传递给策略方法,并可在做出授权决策时用于其他上下文。例如,考虑以下PostPolicy 方法定义,其中包含一个额外的$category 范围:

/**
 * Determine if the given post can be updated by the user.
 */
public function update(User $user, Post $post, int $category): bool
{
    return $user->id === $post->user_id &&
           $user->canUpdateCategory($category);
}

当试图确定经过身份验证的用户是否可以更新给定的帖子时,我们可以像这样调用此策略方法:

/**
 * Update the given blog post.
 *
 * @throws \Illuminate\Auth\Access\AuthorizationException
 */
public function update(Request $request, Post $post): RedirectResponse
{
    $this->authorize('update', [$post, $request->category]);

    // The current user can update the blog post...

    return redirect('/posts');
}
豫ICP备18041297号-2