如何在 Laravel 中使用用户授权: gate, policy 还是 role 和 permission?

本文探讨了Laravel框架中Gate、Policy、Role及Permission等授权工具的使用场景与最佳实践,通过示例展示了如何根据业务需求选择合适的授权方式。

如何在 Laravel 中使用用户授权: gate, policy 还是 role 和 permission

     Laravel 文档描述了授权访问应用程序的多种工具,并介绍了如何创建、构造和应用这些授权机制。 然而,它只笼统地给出了一些说明和示例, 这是因为每个应用程序的业务逻辑不尽相同,具体采用哪种授权方式进行授权可能是很主观的选择。 本文稍后介绍的一个软件包—— Spatie 的 laravel-permission —— 也是如此, 它确保能与 Laravel 整合,并提供强大的功能,但通常仅会提供一些指导。

      那么在实际开发中,到底如何决定采用哪种授权方式呢? 是使用 Laravel 内置的 gate 或者 policy,还是必须安装第三方软件包才能获得所需的功能?

      这个问题略显复杂,让我们尝试去解答一下。

Laravel 中目前提供的用户授权工具

      Laravel 目前提供了开箱即用的 GatePolicy。 您可以阅读 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 就很不错:

  1. 这是一个一般的操作,我们关注的问题即是否能访问业务流程。在这里,即是否可以访问兑换链接,必要的条件就是至少100或者更多的点数。

  2. 上述问题适用于当前的用户,一个Eloquent模型,但它并不适用于另一个模型,我们没有检查其他的资源或模型的点数;相反,我们仅仅在获取当前用户的状态,所以此处不需要用 policy。

  3. 同时,我们仅仅需要做一个比较,所以此时也不需要角色和权限。

此处使用 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() 助手函数的控制器。 但是,让我们通过直接在 BookControllerupdate() 方法中应用它,以一种更详尽的方式来演示这一点:

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
	}
}

     现在,如果用户以 salessupport 角色访问 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

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值