
Laravel 文档描述了授权访问应用程序的多种工具,并介绍了如何创建、构造和应用这些授权机制。 然而,它只笼统地给出了一些说明和示例, 这是因为每个应用程序的业务逻辑不尽相同,具体采用哪种授权方式进行授权可能是很主观的选择。 本文稍后介绍的一个软件包—— Spatie 的 laravel-permission —— 也是如此, 它确保能与 Laravel 整合,并提供强大的功能,但通常仅会提供一些指导。
那么在实际开发中,到底如何决定采用哪种授权方式呢? 是使用 Laravel 内置的 gate 或者 policy,还是必须安装第三方软件包才能获得所需的功能?
这个问题略显复杂,让我们尝试去解答一下。
Laravel 中目前提供的用户授权工具
Laravel 目前提供了开箱即用的 Gate 和 Policy。 您可以阅读 Laravel 官方文档中关于授权这部分以了解更为详细的内容。 让我们具体谈谈每一种授权方式以及它们的最佳用法。
Gate 更高级,更通用。 虽然它们可以应用于特定的对象(或 Eloquent 模型),但它们往往更面向业务流程或更抽象。 我喜欢把它想象成俱乐部的门卫,你必须通过门卫才能进入俱乐部。 而在俱乐部中,当你与里边的某个人互动时,情况就完全不同了,这更符合 Policy。
通常此处还有另外一种说法,页面级权限和按钮级权限 —— 译者注
Policy(策略)往往与基本的 CRUD (创建、读取、更新、删除)操作能很好地进行匹配,它从设计上为这些操作提供了范例。
Policy 倾向应用于某个资源,如某个 eloquent 模型,以确定用户是否可以对该资源执行 CRUD 中的某个操作。 然而,它却并不局限于 CRUD,可以使用自定义的操作进行扩展,并用于关系授权(Laravel Nova使用已注册的策略来确定是否可以创建模型或将模型附加到关系中) 。
Gate 和 Policy 提供了细粒度和抽象授权的混合,但它们缺乏层次结构或分组功能。 这就是 Spatie 的 laravel-permission 包的亮点所在。 它提供了一个 gate 和 policy 都可以利用的分层(分层处理复杂问题是计算机科学中常见的方法之一)。 该扩展包的功能包括对角色、权限和团队的管理。
Permission(权限) 用来描述用户是否可以在一般的、非特定的场景中执行某个操作。 Role(角色) 则用来描述用户在应用程序中可能扮演的角色。 一个用户可以拥有多个角色,权限应用于角色(权限也可以直接赋于用户,但我不建议这样做) 。最后,可选地,团队是一组用户,团队可以包含许多角色。(即 RBAC)
综上所述,针对同一个问题,似乎有多重方法可以实现,这难免使人感到困惑(过于灵活是一件糟糕的事情)。希望在后文的实践中,能使问题逐渐明晰起来。
Laravel gate 示例
在当前场景中,我想确保用户已经获得至少100个点数,才可以查看兑换链接。此处直接在 user 模型上存储点数。
让我们看看如何定义 gate:
use App\Models\User;
use Illuminate\Support\Facades\Gate;
// 此处定义了一个gate,名为 access-redemptions
Gate::define('access-redemptions', function (User $user) {
return $user->points >= 100;
});
在前端代码中应用 gate:
<nav>
<a href="{{ route('dashboard') }}">Dashboard</a>
<!-- 这里采用了 Laravel Blade 模板中的 @can 指令去检查当前用户是否有查看兑换链接的权限 -->
@can('access-redemptions')
<a href="{{ route('redemptions') }}">Redemptions</a>
@endcan
</nav>
为了完成完整的检查,我们可能会在访问 redemptions 路由的类方法顶部添加如下的内容:
use Illuminate\Support\Facades\Gate;
Gate::authorize('access-redemptions');
如果当前用户没有 access-redemptions 权限,此时会抛出一个授权异常。
那么,让我们来分析一下,为什么上述示例中应用 gate 就很不错:
-
这是一个一般的操作,我们关注的问题即是否能访问业务流程。在这里,即是否可以访问兑换链接,必要的条件就是至少100或者更多的点数。
-
上述问题适用于当前的用户,一个Eloquent模型,但它并不适用于另一个模型,我们没有检查其他的资源或模型的点数;相反,我们仅仅在获取当前用户的状态,所以此处不需要用 policy。
-
同时,我们仅仅需要做一个比较,所以此时也不需要角色和权限。
此处使用 gate 只是提供一个二元判断是否允许某个操作。
Laravel policy 示例
为了演示 Policy(策略)的用法,让我们选择一个简单点的例子。 我只允许图书的所有者有权修改它。 每本书都有一个 user_id 字段,表示所有者。
定义一个 Policy:
namespace App\Policies;
use App\Models\Book;
use App\Models\User;
class BookPolicy{
public function update(User $user, Book $book): bool
{
return $book->user_id === $user->id;
}
}
现在,我想授权我的控制器方法。 通常我建议使用具有 authorizeResource() 助手函数的控制器。 但是,让我们通过直接在 BookController 的 update() 方法中应用它,以一种更详尽的方式来演示这一点:
namespace App\Http\Controllers;
class BookController extends Controller
{
public function update(Book $book, Request $request){
$this->authorize('update', $book);
// ... code to apply updates
}
}
BookController::authorize() 方法,或者说授权助手,将把当前用户和 $book 的更新实例一起传递给BookPolicy::update() 方法。 如果 Policy 方法返回 false ,则会抛出一个授权异常。
为什么这里选择 Policy 作为授权工具? 首先,我们正在处理一种特定类型的动作:我们有一个名词,同时想要做某件事情。 上述示例中,我们有一个 book 对象,并想修改它。 其次,由于它是一个特定的 Eloquent 模型,Policy 是处理单个对象的最佳方式。 最后,因为这是一个 CRUD 类型的操作,并且我们已经遵循了在控制器中以操作命名方法的范例(见 资源控制器),这里暗示我们应该在以该模型命名的策略中使用相同的方法名。
Laravel role 和 permission 示例
为了演示 Role 和 Permission 的用法,让我们假设一个有部门的公司。 在这家公司,有一个销售(Sales)团队和一个支持(Support)团队。 销售团队可以看到客户账单信息,但不能更改它;支持团队可以查看和更新客户端账单信息。
为了实现上述需求,我需要两个权限和两个角色:
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
$sales = Role::create(['name' => 'sales']);
$support = Role::create(['name' => 'support']);
$seeClientBilling = Permission::create(['name' => 'see-client-billing']);
$updateClientBilling = Permission::create(['name' => 'update-client-billing']);
$sales->givePermissionTo($seeClientBilling);
$support->givePermissionTo($seeClientBilling);
$support->givePermissionTo($updateClientBilling);
我们已经注册了两个角色,并为每个角色赋予了适当的权限。 现在,拥有这些角色的用户也将继承这些权限。
接下来,让我们看看客户账单控制器中的几个方法:
namespace App\Http\Controllers;
use App\Models\Client;
use Illuminate\Http\Request;
class ClientBillingController extends Controller
{
public function show(Client $client, Request $request)
{
abort_unless($request->user()->can('see-client-billing'), 403);
return view('client.billing', ['client' => $client]);
}
public function update(Client $client, Request $request)
{
abort_unless($request->user()->can('update-client-billing'), 403);
// code to update billing information
}
}
现在,如果用户以 sales 或 support 角色访问 ClientBillingController::show(),他们都有权(see-client-billing)查看账单信息,但只有被赋予了 update-client-billing 权限的具有 support 角色的用户才能提交对账单的修改。
为什么这个示例中采用 Roles 和 Permissions 方式是最优解? 你当然可以通过 Gate 甚或 Policy 来实现同样的事情。 但是,采用 Roles 和 Permissions 方式不仅更容易理解,也更容易在一个集中的位置进行权限的管理。 假设将来您希望具有 sales 角色的用户也能够修改账单信息,您只需向 sales 角色添加 update-client -billing 权限即可。 你不需要检查大量的 gate 或 policy。 这种类型的操作并不一定是某些特定模型所特有的,但它提供了访问或授权的级别,采用 Roles 和 Permissions 方式就成为了完美的解决方案。
结语:我到底应该使用哪种授权机制?
Gate 用于标准 CRUD 机制之外的特定功能,它们非常适合于对整个部分或模块的广泛访问。 Policy 最适合 CRUD 范式,授权给特定的对象或 eloquent 模型。 而当每个小组或部门需要完成特定的操作,此时 RBAC 就很有效。 在功能上,每种工具都有很多重叠或者相似的部分,所以您可能会发现自己在进行不同工具的混合和分配。 当然,您可以将其中一种授权机制集成到另一个中,例如将权限检查(permission checking)与某个 policy 中所有权验证相结合。
Ref: How to use authorization in Laravel: Gates, policies, roles and permissions
本文探讨了Laravel框架中Gate、Policy、Role及Permission等授权工具的使用场景与最佳实践,通过示例展示了如何根据业务需求选择合适的授权方式。
159

被折叠的 条评论
为什么被折叠?



