> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 授权（Gate 与 Policy）

> 介绍如何使用 Laravel 的 Gate 和 Policy 来管理用户的操作权限。

## 认证与授权的区别

**认证（Authentication）** 用来确认用户「是谁」。登录处理就是典型例子。
**授权（Authorization）** 用来判断该用户「能做什么」。例如只能编辑自己发的帖子、只有管理员能修改设置等。

在通过[认证](./authentication)实现登录功能后，下一步就是授权。

<Info>
  Laravel 的授权功能提供 Gate 与 Policy 两种方式。选择哪种取决于用途。
</Info>

**选择依据**

| 场景              | 推荐     |
| --------------- | ------ |
| 与具体模型无关的简单判断    | Gate   |
| 针对模型的 CRUD 权限管理 | Policy |
| 管理后台访问控制        | Gate   |
| 博客文章的发布、编辑、删除权限 | Policy |

## Gate

Gate 是基于闭包的简单授权检查，适合与特定模型无关的权限判断。

### Gate 的授权流程

```mermaid theme={null}
flowchart TD
    A["AppServiceProvider::boot()<br>(App\\Providers\\AppServiceProvider)"] --> B["Gate::define('ability', fn)<br>注册授权逻辑"]
    C["从控制器进行授权检查"] --> D{"是否配置了<br>Gate::before()？"}
    D -->|"是"| E{"before() 的返回值"}
    E -->|"true / false"| F["以 before() 的结果作为最终判定"]
    E -->|"null"| G["执行 define() 的闭包"]
    D -->|"否"| G
    G --> H{"闭包的返回值"}
    H -->|"true"| I["✓ 允许访问"]
    H -->|"false"| J["✗ 拒绝访问"]
    F -->|"true"| I
    F -->|"false"| J
    J -->|"Gate::allows()"| K["返回 false<br>→ 需手动调用 abort(403)"]
    J -->|"Gate::authorize()"| L["AuthorizationException<br>→ 自动返回 403 Forbidden"]
```

### 定义 Gate

Gate 定义在 `App\Providers\AppServiceProvider` 的 `boot` 方法中，使用 `Gate::define()`。

```php theme={null}
// app/Providers/AppServiceProvider.php

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

public function boot(): void
{
    Gate::define('update-post', function (User $user, Post $post) {
        return $user->id === $post->user_id;
    });
}
```

Gate 闭包的第一个参数总是当前已认证用户。第二个及以后的参数可以传入目标模型等额外信息。

### 使用 Gate 检查权限

在控制器中使用 Gate 检查权限可用 `Gate::allows()` 或 `Gate::denies()`。

```php theme={null}
use Illuminate\Support\Facades\Gate;

public function update(Request $request, Post $post): RedirectResponse
{
    if (! Gate::allows('update-post', $post)) {
        abort(403);
    }

    // 更新文章的处理...

    return redirect('/posts');
}
```

### 使用 Gate::authorize() 抛出异常

无权限时自动返回 403 响应可以使用 `Gate::authorize()`，省去手写 `abort(403)`。

```php theme={null}
public function update(Request $request, Post $post): RedirectResponse
{
    Gate::authorize('update-post', $post);

    // 有权限时到达此处
    // 更新文章的处理...

    return redirect('/posts');
}
```

<Tip>
  当权限不足时，`Gate::authorize()` 会抛出 `Illuminate\Auth\Access\AuthorizationException`。Laravel 会自动将其转换为 403 响应。
</Tip>

### 管理员绕过（before 方法）

若想给管理员用户所有权限，可以使用 `Gate::before()`。

```php theme={null}
use App\Models\User;
use Illuminate\Support\Facades\Gate;

public function boot(): void
{
    // $ability 是正在尝试的 gate 名（例如 'update-post'）
    Gate::before(function (User $user, string $ability) {
        if ($user->isAdministrator()) {
            return true;
        }
    });

    Gate::define('update-post', function (User $user, Post $post) {
        return $user->id === $post->user_id;
    });
}
```

若 `before` 闭包返回非 `null` 值，将其作为最终结果。返回 `null` 或无返回值时会继续执行正常的 Gate 判定。

### Blade 中的 @can / @cannot

在模板中使用 Gate，可用 `@can` 与 `@cannot` 指令：

```blade theme={null}
@can('update-post', $post)
    <a href="{{ route('posts.edit', $post) }}">编辑</a>
@endcan

@cannot('update-post', $post)
    <p>你无权编辑该文章。</p>
@endcannot
```

## Policy

Policy 将某模型相关的授权逻辑归入一个类。对 `Post` 模型的创建、查看、编辑、删除等权限，用 Policy 比 Gate 更合适。

### Policy 的授权流程

```mermaid theme={null}
flowchart TD
    A["HTTP 请求"] --> B["控制器"]
    B --> C["Gate::authorize('update', $post)"]
    C --> D{"是否定义了<br>PostPolicy::before()？"}
    D -->|"是"| E{"before() 的返回值"}
    E -->|"true / false"| F["以 before() 的结果作为最终判定"]
    E -->|"null"| G["执行 PostPolicy::update(User, Post)"]
    D -->|"否"| G
    G --> H{"$user->id === $post->user_id?"}
    H -->|"true"| I["✓ 继续处理"]
    H -->|"false"| J["✗ AuthorizationException<br>→ 403 Forbidden"]
    F -->|"true"| I
    F -->|"false"| J
```

### 生成 Policy

使用 `make:policy` Artisan 命令生成 Policy 类。

```shell theme={null}
php artisan make:policy PostPolicy
```

若想生成包含对应模型 CRUD 方法的完整骨架，使用 `--model`：

```shell theme={null}
php artisan make:policy PostPolicy --model=Post
```

会生成 `app/Policies/PostPolicy.php`。

### 模型与 Policy 的自动识别

Laravel 默认根据命名约定自动识别 Policy：

* 模型：`app/Models/Post.php`
* Policy：`app/Policies/PostPolicy.php`

只要遵循命名约定，就无需手动注册。

<Info>
  若不遵循命名约定或希望手动注册，可在 `AppServiceProvider` 的 `boot` 中使用 `Gate::policy()`：

  ```php theme={null}
  use App\Models\Post;
  use App\Policies\PostPolicy;
  use Illuminate\Support\Facades\Gate;

  Gate::policy(Post::class, PostPolicy::class);
  ```

  在 Laravel 13 中，也可以通过 `#[UsePolicy]` 属性在模型上进行声明式注册。

  ```php theme={null}
  use App\Policies\PostPolicy;
  use Illuminate\Database\Eloquent\Attributes\UsePolicy;

  #[UsePolicy(PostPolicy::class)]
  class Post extends Model {}
  ```
</Info>

### 实现 Policy 方法

通过 `--model` 生成的 Policy 已经带有标准 CRUD 方法。

```php theme={null}
<?php

namespace App\Policies;

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

class PostPolicy
{
    /** 是否可查看列表 */
    public function viewAny(User $user): bool
    {
        return true;
    }

    /** 是否可查看某一条 */
    public function view(User $user, Post $post): bool
    {
        return true;
    }

    /** 是否可创建 */
    public function create(User $user): bool
    {
        return true;
    }

    /** 是否可更新 */
    public function update(User $user, Post $post): bool
    {
        return $user->id === $post->user_id;
    }

    /** 是否可删除 */
    public function delete(User $user, Post $post): bool
    {
        return $user->id === $post->user_id;
    }
}
```

### 管理员绕过（before 方法）

Policy 中也可通过 `before` 给管理员所有权限：

```php theme={null}
/**
 * 在其他 policy 方法之前调用
 * $ability 为 policy 方法名（'update'、'delete' 等）
 */
public function before(User $user, string $ability): bool|null
{
    if ($user->isAdministrator()) {
        return true;
    }

    return null; // 返回 null 则继续正常 policy 判断
}
```

<Warning>
  `before` 只会在 policy 类中存在对应方法时才会被调用。例如 `update` 方法不存在，`before` 就不会被调用。
</Warning>

### before 回调的执行顺序

```mermaid theme={null}
flowchart TD
    A["开始授权检查<br>authorize('update', $post)"] --> B["执行 PostPolicy::before()"]
    B --> C{"返回值是 null？"}
    C -->|"非 null（true / false）"| D["以 before() 的结果作为最终判定"]
    C -->|"null"| E["执行常规 policy 方法<br>update() / delete() 等"]
    E --> F{"policy 方法返回值"}
    F -->|"true"| G["✓ 允许访问"]
    F -->|"false"| H["✗ 拒绝访问 → 403"]
    D -->|"true（管理员绕过）"| G
    D -->|"false"| H
```

## 在控制器中使用 Policy

### authorize() 方法

Laravel 控制器提供了 `authorize()` 帮助方法（若未使用基础控制器，可使用 `Gate::authorize()`）。

<Tabs>
  <Tab title="Gate::authorize()">
    ```php theme={null}
    <?php

    namespace App\Http\Controllers;

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

    class PostController extends Controller
    {
        public function update(Request $request, Post $post): RedirectResponse
        {
            Gate::authorize('update', $post);

            // 更新文章的处理...

            return redirect()->route('posts.index');
        }

        public function store(Request $request): RedirectResponse
        {
            Gate::authorize('create', Post::class);

            // 创建文章的处理...

            return redirect()->route('posts.index');
        }

        public function destroy(Post $post): RedirectResponse
        {
            Gate::authorize('delete', $post);

            $post->delete();

            return redirect()->route('posts.index');
        }
    }
    ```
  </Tab>

  <Tab title="User::can() / cannot()">
    ```php theme={null}
    <?php

    namespace App\Http\Controllers;

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

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

            // 更新文章的处理...

            return redirect()->route('posts.index');
        }
    }
    ```
  </Tab>
</Tabs>

### 使用 authorizeResource() 一次注册 RESTful Policy

在构造器中调用 `authorizeResource()`，可以将控制器各个 action 自动映射到对应 policy 方法。

```php theme={null}
<?php

namespace App\Http\Controllers;

use App\Models\Post;

class PostController extends Controller
{
    public function __construct()
    {
        $this->authorizeResource(Post::class, 'post');
    }

    // index()、show()、create()、store()、edit()、update()、destroy() 会自动应用对应 policy 方法
}
```

action 与 policy 方法的对应关系：

| 控制器 action         | policy 方法 |
| ------------------ | --------- |
| `index`            | `viewAny` |
| `show`             | `view`    |
| `create` / `store` | `create`  |
| `edit` / `update`  | `update`  |
| `destroy`          | `delete`  |

<Tip>
  使用 `authorizeResource()` 可以省去在每个 action 单独调用 `authorize()`。与 RESTful 资源控制器配合尤其方便。
</Tip>

## 在中间件中授权

在路由级做授权检查，使用 `can` 中间件。

```php theme={null}
use App\Models\Post;

// 只允许具备更新权限的用户访问
Route::put('/posts/{post}', [PostController::class, 'update'])
    ->middleware('can:update,post');

// 只允许具备创建权限的用户访问
Route::post('/posts', [PostController::class, 'store'])
    ->middleware('can:create,App\Models\Post');
```

也可以用 `can` 方法写得更简洁：

```php theme={null}
Route::put('/posts/{post}', [PostController::class, 'update'])
    ->can('update', 'post');
```

## 在 Blade 中使用 Policy

注册好 Policy 之后，Blade 的 `@can` / `@cannot` 指令也会自动使用 Policy。

```blade theme={null}
{{-- 只对有权限的用户显示编辑按钮 --}}
@can('update', $post)
    <a href="{{ route('posts.edit', $post) }}" class="btn">编辑</a>
@endcan

{{-- 删除按钮同理 --}}
@can('delete', $post)
    <form method="POST" action="{{ route('posts.destroy', $post) }}">
        @csrf
        @method('DELETE')
        <button type="submit">删除</button>
    </form>
@endcan

{{-- 新建按钮 --}}
@can('create', App\Models\Post::class)
    <a href="{{ route('posts.create') }}">新建文章</a>
@endcan
```

## 实用示例：博客文章权限管理

在博客应用中实现「作者才能编辑或删除自己的文章」：

<Steps>
  <Step title="生成 Policy">
    ```shell theme={null}
    php artisan make:policy PostPolicy --model=Post
    ```
  </Step>

  <Step title="实现 Policy 方法">
    ```php theme={null}
    <?php

    namespace App\Policies;

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

    class PostPolicy
    {
        /** 给管理员所有权限 */
        public function before(User $user, string $ability): bool|null
        {
            if ($user->is_admin) {
                return true;
            }

            return null;
        }

        public function viewAny(User $user): bool
        {
            return true;
        }

        public function view(User $user, Post $post): bool
        {
            return true;
        }

        public function create(User $user): bool
        {
            return true;
        }

        public function update(User $user, Post $post): bool
        {
            return $user->id === $post->user_id;
        }

        public function delete(User $user, Post $post): bool
        {
            return $user->id === $post->user_id;
        }
    }
    ```
  </Step>

  <Step title="在控制器中添加 authorizeResource()">
    ```php theme={null}
    <?php

    namespace App\Http\Controllers;

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

    class PostController extends Controller
    {
        public function __construct()
        {
            $this->authorizeResource(Post::class, 'post');
        }

        public function index(): View
        {
            $posts = Post::latest()->paginate(10);
            return view('posts.index', compact('posts'));
        }

        public function store(Request $request): RedirectResponse
        {
            $post = $request->user()->posts()->create(
                $request->validate([
                    'title' => ['required', 'string', 'max:255'],
                    'body'  => ['required', 'string'],
                ])
            );

            return redirect()->route('posts.show', $post);
        }

        public function update(Request $request, Post $post): RedirectResponse
        {
            $post->update(
                $request->validate([
                    'title' => ['required', 'string', 'max:255'],
                    'body'  => ['required', 'string'],
                ])
            );

            return redirect()->route('posts.show', $post);
        }

        public function destroy(Post $post): RedirectResponse
        {
            $post->delete();

            return redirect()->route('posts.index');
        }
    }
    ```
  </Step>

  <Step title="在 Blade 模板中添加权限判断">
    ```blade theme={null}
    {{-- resources/views/posts/show.blade.php --}}
    <h1>{{ $post->title }}</h1>
    <p>{{ $post->body }}</p>
    <p>作者：{{ $post->user->name }}</p>

    @can('update', $post)
        <a href="{{ route('posts.edit', $post) }}">编辑</a>
    @endcan

    @can('delete', $post)
        <form method="POST" action="{{ route('posts.destroy', $post) }}">
            @csrf
            @method('DELETE')
            <button type="submit">删除</button>
        </form>
    @endcan
    ```
  </Step>
</Steps>

## 小结

<AccordionGroup>
  <Accordion title="Gate 与 Policy 的选择">
    * **Gate**：与特定模型无关的简单权限判断。例如管理后台访问、全局设置修改权限等。
    * **Policy**：针对模型的 CRUD 权限。为 `Post`、`Order`、`Comment` 等资源分别建立 policy 类。
  </Accordion>

  <Accordion title="常用 API 汇总">
    ```php theme={null}
    // 通过 Gate 判定
    Gate::allows('update-post', $post);      // true/false
    Gate::denies('update-post', $post);      // true/false
    Gate::authorize('update-post', $post);   // 失败时抛异常

    // 通过用户模型判定
    $user->can('update', $post);     // true/false
    $user->cannot('update', $post);  // true/false

    // 控制器中
    Gate::authorize('update', $post);        // 失败时 403

    // Blade 中
    @can('update', $post) ... @endcan
    @cannot('update', $post) ... @endcannot
    ```
  </Accordion>

  <Accordion title="常用 Artisan 命令">
    ```shell theme={null}
    # 生成空 policy
    php artisan make:policy PostPolicy

    # 生成与模型对应的 CRUD 方法的 policy
    php artisan make:policy PostPolicy --model=Post
    ```
  </Accordion>
</AccordionGroup>


## Related topics

- [加密（Encryption）](/zh/encryption.md)
- [Laravel Telescope](/zh/telescope.md)
- [广播](/zh/broadcasting.md)
- [Laravel Horizon](/zh/horizon.md)
- [BlueskyManager 与 HasShortHand](/zh/packages/laravel-bluesky/bluesky-manager.md)
