Laravel 角色和权限:详解 Gates 和 Policies

发布日期:作者:

Laravel Roles and Permissions: Gates and Policies Explained image

在 Laravel 中,角色和权限多年来一直是最令人困惑的话题之一。主要是因为没有关于它的文档:相同的内容在框架中以其他术语“隐藏”,比如“gates”、“policies”、“guards”等等。在这篇文章中,我将尝试用“人类语言”解释所有这些。

Gate 等同于 Permission

我认为,“gate”这个术语是最大的困惑之一。我认为如果它们被称为它们是什么,开发者就会避免很多困惑。

Gates 就是 **Permissions**,只是叫法不同。

我们使用权限需要执行哪些典型操作?

  • 定义权限,例如“manage_users”。
  • 在前端检查权限,例如显示/隐藏按钮。
  • 在后端检查权限,例如是否可以更新数据。

所以是的,将“permission”替换为“gate”,你就能理解所有内容。

一个简单的 Laravel 示例是:

app/Providers/AppServiceProvider.php:

use App\Models\User;
use Illuminate\Support\Facades\Gate;
 
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
// Should return TRUE or FALSE
Gate::define('manage_users', function(User $user) {
return $user->is_admin == 1;
});
}
}

resources/views/navigation.blade.php:

<ul>
<li>
<a href="{{ route('projects.index') }}">Projects</a>
</li>
@can('manage_users')
<li>
<a href="{{ route('users.index') }}">Users</a>
</li>
@endcan
</ul>

routes/web.php:

Route::resource('users', UserController::class)->middleware('can:manage_users');

现在,我知道从技术上讲,Gate 可能不止一个权限。因此,你可以定义类似“admin_area”的内容,而不是“manage_users”。但是,在我见过的绝大多数示例中,Gate 是 Permission 的同义词。

此外,在某些情况下,权限称为“abilities”,例如在 Bouncer 包 中。它也表示相同的意思 - 某些操作的能力/权限。我们将在本文的后面部分谈到这些包。


检查 Gate 权限的各种方法

另一个困惑的来源是检查 Gate 的方式/位置。它非常灵活,因此你可能会找到非常不同的示例。让我们逐一浏览它们。

选项 1. 路由:middleware('can:xxxxxx')

这是上面的示例。你可以在路由/组上直接分配中间件。

Route::post('users', [UserController::class, 'store'])
->middleware('can:create_users');

选项 2. 控制器:can() / cannot()

在控制器方法的第一行,我们可以看到类似这样的内容,使用 can()cannot() 方法,与 Blade 指令相同。

public function store(Request $request)
{
if (!$request->user()->can('create_users'))
abort(403);
}
}

反之亦然是 cannot()

public function store(Request $request)
{
if ($request->user()->cannot('create_users'))
abort(403);
}
}

或者,如果你没有 $request 变量,可以使用 auth() 助手

public function create()
{
if (!auth()->user()->can('create_users'))
abort(403);
}
}

选项 3. Gate::allows() 或 Gate::denies()

另一种方法是使用 Gate 门面

public function store(Request $request)
{
if (!Gate::allows('create_users')) {
abort(403);
}
}

或者,反之亦然

public function store(Request $request)
{
if (Gate::denies('create_users')) {
abort(403);
}
}

或者,使用助手进行更简短的中止操作

public function store(Request $request)
{
abort_if(Gate::denies('create_users'), 403);
}

选项 4. 控制器:authorize()

更简短的选项,也是我个人最喜欢的选项,是在控制器中使用 authorize()。如果失败,它会自动返回 403 页面。

public function store(Request $request)
{
$this->authorize('create_users');
}

选项 5. 表单请求类:

我注意到,许多开发人员生成 表单请求类 只是为了定义验证规则,完全忽略了该类的第一个方法 authorize()

你也可以使用它来检查 gates。这样,你就可以实现关注点分离,这对稳固的代码来说是一种良好的实践,因此控制器不会负责验证,因为它在专门的表单请求类中完成。

public function store(StoreUserRequest $request)
{
// No check is needed in the Controller method
}

然后,在表单请求中

class StoreProjectRequest extends FormRequest
{
public function authorize()
{
return Gate::allows('create_users');
}
 
public function rules()
{
return [
// ...
];
}
}

Policy:基于模型的权限集

如果你的权限可以分配给 Eloquent 模型,在典型的 CRUD 控制器中,你可以在它们周围构建一个 Policy 类。

如果我们运行此命令

php artisan make:policy ProductPolicy --model=Product

它将生成文件 **app/Policies/UserPolicy.php**,其中包含具有解释其用途的注释的默认方法

use App\Models\Product;
use App\Models\User;
 
class ProductPolicy
{
use HandlesAuthorization;
 
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user)
{
//
}
 
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Product $product)
{
//
}
 
/**
* Determine whether the user can create models.
*/
public function create(User $user)
{
//
}
 
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Product $product)
{
//
}
 
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Product $product)
{
//
}
 
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Product $product)
{
//
}
 
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Product $product)
{
//
}
}

在每个方法中,你定义 true/false 返回的条件。因此,如果我们遵循与 Gates 相同的示例,我们可以这样做

class ProductPolicy
{
public function create(User $user)
{
return $user->is_admin == 1;
}

然后,你可以以与 Gates 非常相似的方式检查 Policy

public function store(Request $request)
{
$this->authorize('create', Product::class);
}

因此,你指定方法名称和 Policy 的类名称。

换句话说,Policies 只是另一种分组权限的方式,而不是 Gates。如果你的操作主要围绕模型的 CRUD,那么 Policies 可能比 Gates 更方便、结构更合理。


Role:通用权限集

让我们讨论另一个令人困惑的地方:在 Laravel 文档中,你找不到任何关于用户角色的部分。原因很简单:“roles”这个词是人为造出来的,用来将权限分组到某种名称下,比如“administrator”或“editor”。

从框架的角度来看,不存在“roles”,只有 gates/policies,你可以按任何你想要的方式进行分组。

换句话说,角色是 Laravel 框架**外部**的实体,因此我们需要自己构建角色结构。这可能是整体身份验证困惑的一部分,但这是有道理的,因为我们应该控制角色的定义方式

  • 是单个角色还是多个角色?
  • 用户可以拥有一个角色还是多个角色?
  • 谁可以管理系统中的角色?
  • 等等。

因此,Role 功能是 Laravel 应用程序的另一层。这就是我们可以使用 Laravel 包来提供帮助的地方。但是我们也可以在没有任何包的情况下创建角色

  1. 创建“roles”数据库表和 Role Eloquent 模型
  2. 从 User 到 Role 添加关系:一对多或多对多
  3. 播种默认角色并将它们分配给现有用户
  4. 在注册时分配默认角色
  5. 更改 Gates/Policies 以检查 Role 而不是其他内容

最后一点是最重要的。

因此,而不是

class ProductPolicy
{
public function create(User $user)
{
return $user->is_admin == 1;
}

你会做类似的事情

class ProductPolicy
{
public function create(User $user)
{
return $user->role_id == Role::ADMIN;
}

同样,你有多种选择来检查角色。在上面的示例中,我们假设从 User 到 Role 存在一个 belongsTo 关系,并且 Role 模型中还有常量,比如 ADMIN = 1,比如 EDITOR = 2,只是为了避免过度查询数据库。

但是,如果你更喜欢灵活,你可以在每次都查询数据库

class ProductPolicy
{
public function create(User $user)
{
return $user->role->name == 'Administrator';
}

但请记住要急切加载“role”关系,否则你很容易遇到 N+1 查询问题


使其灵活:将权限保存在数据库中

根据我的个人经验,将所有内容构建在一起的常用模型是:

  • 所有权限和角色都保存在数据库中,由某个管理面板进行管理;
  • 关系:角色多对多权限,User 属于 Role(或多对多角色);
  • 然后,在 AppServiceProvider 中,你从 DB 中的所有权限创建一个 foreach 循环,并为每个权限运行一个 Gate::define() 语句,根据角色返回 true/false;
  • 最后,你使用 @can('permission_name')$this->authorize('permission_name') 检查权限,就像上面的示例一样。
$roles = Role::with('permissions')->get();
$permissionsArray = [];
foreach ($roles as $role) {
foreach ($role->permissions as $permissions) {
$permissionsArray[$permissions->title][] = $role->id;
}
}
 
// Every permission may have multiple roles assigned
foreach ($permissionsArray as $title => $roles) {
Gate::define($title, function ($user) use ($roles) {
// We check if we have the needed roles among current user's roles
return count(array_intersect($user->roles->pluck('id')->toArray(), $roles)) > 0;
});
}

换句话说,我们不通过角色检查任何访问权限。角色只是一个“人工”层,一组在应用程序生命周期内转换为 Gates 的权限。

看起来很复杂吗?不用担心,这就是我们可以使用包来提供帮助的地方。


用于管理角色/权限的包

最流行的包是 Spatie Laravel PermissionBouncer,我有关于它们的 单独的长篇文章。这篇文章已经很旧了,但市场领导者仍然是一样的,因为它们很稳定。

这些包的作用是帮助您将权限管理抽象成一种用户友好的语言,提供易于记忆和使用的​​方法。

看看来自 Spatie 权限的这个漂亮的语法

$user->givePermissionTo('edit articles');
$user->assignRole('writer');
$role->givePermissionTo('edit articles');
$user->can('edit articles');

Bouncer 可能不太直观,但仍然非常好

Bouncer::allow($user)->to('create', Post::class);
Bouncer::allow('admin')->to('ban-users');
Bouncer::assign('admin')->to($user);

您可以通过链接到他们的 Github 或我上面的文章来了解更多关于如何使用这些包的信息。

因此,这些包是我们在这篇文章中介绍的身份验证/授权的最终“层”,我希望您现在能够全面了解并选择要使用的策略。


附注:等等,守卫怎么办?

哦,那些。多年来,它们造成了很多混乱。许多开发人员认为守卫就是角色,并开始创建诸如“管理员”之类的独立数据库表,然后将这些表分配为守卫。部分原因是,您可能会在文档中找到类似于 Auth::guard('admin')->attempt($credentials)) 的代码片段。

我甚至向文档提交了一个 Pull Request,以警告避免这种误解。

在官方文档中,您可能会找到以下段落

从本质上讲,Laravel 的身份验证功能由“守卫”和“提供程序”组成。守卫定义了每个请求如何对用户进行身份验证。例如,Laravel 附带一个会话守卫,它使用会话存储和 cookie 来维护状态。

因此,守卫比角色更全局的概念。守卫的一个示例是“会话”,稍后在文档中,您可能会看到一个 JWT 守卫示例。换句话说,守卫是一种完整的身份验证机制,对于大多数 Laravel 项目来说,您永远不需要更改守卫,甚至不需要了解它们的工作原理。守卫位于角色/权限主题之外。

PovilasKorop photo

Laravel Daily 的课程和教程创建者

归档于
Cube

Laravel 新闻通讯

加入 40,000 多名其他开发人员,绝不错过新的提示、教程等。

Laravel Forge logo

Laravel Forge

轻松创建和管理您的服务器,并在几秒钟内部署您的 Laravel 应用程序。

Laravel Forge
Tinkerwell logo

Tinkerwell

Laravel 开发人员必备的代码运行器。使用 AI、自动完成和本地和生产环境的即时反馈进行调试。

Tinkerwell
No Compromises logo

No Compromises

Joel 和 Aaron,来自 No Compromises 播客的两名经验丰富的开发人员,现在可以为您的 Laravel 项目提供服务。⬧ 固定费率为 7500 美元/月。⬧ 没有冗长的销售流程。⬧ 没有合同。⬧ 100% 退款保证。

No Compromises
Kirschbaum logo

Kirschbaum

提供创新和稳定性,以确保您的 Web 应用程序取得成功。

Kirschbaum
Shift logo

Shift

正在运行旧版本的 Laravel?即时、自动的 Laravel 升级和代码现代化,以保持您的应用程序新鲜。

Shift
Bacancy logo

Bacancy

只需 2500 美元/月,即可让您的项目充满活力,并配备拥有 4-6 年经验的资深 Laravel 开发人员。获得 160 小时的专业知识和 15 天的无风险试用期。立即安排电话会议!

Bacancy
Lucky Media logo

Lucky Media

现在就来尝试 Lucky Media - Laravel 开发的理想选择,拥有超过十年的经验!

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel 电子商务

Laravel 电子商务。一个开源包,将现代无头电子商务功能的强大功能带入 Laravel。

Lunar: Laravel 电子商务
LaraJobs logo

LaraJobs

官方 Laravel 职位招聘平台

LaraJobs
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS 启动工具包

SaaSykit 是一款 Laravel SaaS 启动工具包,包含运行现代 SaaS 所需的所有功能。支付、精美结账、管理面板、用户仪表板、身份验证、现成组件、统计信息、博客、文档等。

SaaSykit: Laravel SaaS 启动工具包
Rector logo

Rector

您无缝升级 Laravel、降低成本并加速创新的合作伙伴,助力企业取得成功

Rector
MongoDB logo

MongoDB

通过 MongoDB 和 Laravel 的强大集成增强您的 PHP 应用程序,使开发人员能够轻松高效地构建应用程序。支持事务、搜索、分析和移动用例,同时使用熟悉的 Eloquent API。探索 MongoDB 灵活、现代的数据库如何改变您的 Laravel 应用程序。

MongoDB
Maska is a Simple Zero-dependency Input Mask Library image

Maska 是一个简单的零依赖输入掩码库

阅读文章
Add Swagger UI to Your Laravel Application image

在您的 Laravel 应用程序中添加 Swagger UI

阅读文章
Assert the Exact JSON Structure of a Response in Laravel 11.19 image

在 Laravel 11.19 中断言响应的精确 JSON 结构

阅读文章
Build SSH Apps with PHP and Laravel Prompts image

使用 PHP 和 Laravel 提示构建 SSH 应用程序

阅读文章
Building fast, fuzzy site search with Laravel and Typesense image

使用 Laravel 和 Typesense 构建快速、模糊的网站搜索

阅读文章
Add Comments to your Laravel Application with the Commenter Package image

使用 Commenter 包在您的 Laravel 应用程序中添加评论

阅读文章