权限

昨天在设定完身分验证的架构後已经可以在路由上加 auth 中介层来验证使用者的身分是否允许访问。

Route::get('/flights', function () {
    // 通过 admin 检查的用户才能进入路由
})->middleware('auth:admin');

不过除了只看身分外,Laravel 可以设定更详细的权限检查,像是检查使用者是否拥有这笔 Todo ,是的话才能进行删除等等。

定义权限的方式分为 Guard 跟 Policy ,两者相较下 Policy 的架构会比较好管理跟维护,所以这里介绍的重点会放在 Policy 上,Guard 就只稍微介绍点,不过其实 Policy 也是 Guard 的应用就是。

Guard

Guard 就像路由,一笔笔的定义某个行径对应的检查函式,一般都定义在 AuthServiceProvider 中。

/app/Providers/AuthServiceProvider.php

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

public function boot()
{
    $this->registerPolicies();

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

进行 update-todo 检查时,会检查传入的 user 跟 todo ,若 todo 是属於 user 的则回传 true 表示权限许可,否则为 false。

use Illuminate\Support\Facades\Gate;

public function update(Request $request, Todo $todo)
{

    if (! Gate::allows('update-todo', $todo)) {
        abort(403);
    }

    // continue update process ...
}

接着就能用 Gate::allows 进行检查,这边要注意我们并没有传入 $user 参数,因为 Gate Facade 会自动从 Auth 取得目前登入的用户代入。

如果想验证的不是目前登入的使用者,加上 forUser 方法指定要验证的使用者

if (! Gate::forUser($user)->allows('update-todo', $todo)) {
  abort(403);
}

Policy

Policy 用於制定使用者对於某个 Model 的操作权限,其本质就是一组针对特定 Model 的 Gate 。

如果用指令创建 Model 的时候选择了全餐 -a ,当中就会包含对应的 Policy ,如果没有的话,就另外建立。

sail artisan make:policy TodoPolicy --model Todo

如果在建立 Policy 时有指定 Model 的话,就会预先产生一些方法像是 view,create,update 等。

注册 Policy

Policy 产生後要在 AuthServiceProvider 中登记对特定 Model 进行权限检查时要用哪个 Policy 。

/app/Providers/.php

<?php

namespace App\Providers;

use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Auth; 

class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [
        App\Models\Todo::class => App\Policies\TodoPolicy::class,
    ];

    //...
}

除了这里之外也能动态的用 Gate::policy() 定义,例如:

$user = Auth::user();

if($user instanceof User){
    \Gate::policy( Todo::class, TodoUserPolicy::class); 
}else if($user instanceof Admin){         
    \Gate::policy( Todo::class, TodoAdminPolicy::class); 
}

如果有复数身分 Model 可以考虑这麽做,不过一般都建议不要用多个身分验证模型提高复杂度。

自动侦测

如果完全没有注册 Model 对应的 Policy, Guard 会试图自动找出对应的 Policy ,寻找步骤如下:

  1. 从 Model 所在目录向上查询名为 Policies 的目录。
  2. 在 Policies 目录中找名称以 Policy 结尾的档案,找是否有 Policy 前的字串与 Model 名称相同的。

可以看到用指令产生的 Policy 都符合上述的步骤,所以一般其实不用特地到 AuthServiceProvider 登记 Policy。

撰写 Policy

就跟写 Guard 的 callback 函式相同,第一个参数传入 User ,之後看要传入哪个 Model。

public function update(User $user, Todo $todo)
{
      return $user->id === $todo->user_id;
}

$user 要视验证的身分标记对应的 Model ,一般来说是 User ,不过如果 Policy 用来对应 Admin 模型的验证的话就要改成 Admin。

public function update(Admin $admin, Todo $todo)
{
      return $admin->id === $todo->user_id;
}

Responses

权限检查除了回传 true / false 以外,也可以回传带有更多资讯的 Response 物件。注意是用 Auth 的 Response 。

use Illuminate\Auth\Access\Response;

public function update(User $user, Todo $todo)
{
      return $user->id === $todo->user_id
            ? Response::allow()
            : Response::deny('You do not own this post.');
}

回传 Response 後如果用 can , allows 等方法,回传还是一般的 true / false,要看到 Response 的内容的话要用 Gate 的 inspect 方法。

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

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

Policy Filter

Policy 中有特别的函式 before ,顾名思义会在进行任何检查前执行,如果 before 回传 null 以外的值的话该值会被当成这次检查的回传值。

public function before(User $user, $ability)
{
    // 如果用户是 admin 就跳过检查一律回传 true
    if ($user->isAdministrator()) {
        return true;
    }
}

用 Policy 验证

写好之後终於可以用啦,用法跟 Guard 相似不过是从 User 出发进行检查,并且不是用 allows 而是 can 跟 cannot 等方法检查。

allows/ denies/ inspect 是 Guard 类别的方法
can/ cannot 是 Authorizable 类别(User 继承的类别)的方法
注意别用错了

if ($request->user()->cannot('update', $todo)) {
    abort(403);
}

另外当验证的方法不用除了 User 以外的资料时,需要带入 Model 名称才知道要找哪个 Policy 进行验证。

if ($request->user()->cannot('create', Todo::class)) {
    abort(403);
}

<<:  【Side Project】 (老板)订单清单UX功能实作2-ChcekBox

>>:  JavaScript物件初步概念

JS AJAX基础实作(4) DAY29

昨天我们已经将 gotop按钮实做出来 但有时候我们不想要它一直出现 而是使用者滚轮滑到下面 它才会...

学习javascript前...CSS2

1.display显示模式设定:可控制 HTML 标签的显示模式,主要分为 block 与 inli...

Day 15 : 特徵工程 tf.Tramsform 介绍

特徵工程是机械学习相当重要的一环,有处理数据以及实行 ML/DL 任务经验者对特徵工程一定不陌生,...

【Day 24】Google Apps Script - API Blueprint 篇 - Google Docs 转换 API Blueprint 格式(2)

继续介绍昨天主流程里的副程序吧。 今日要点: 》Google Docs 转换 API Bluepr...

day4 : k8s建置(下)

昨天成功的用terraform创建gcp的instance出来,也透过terraform自动的把in...