侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

Laravel角色和权限:拦截器Gates和策略Policies的解释

Laravel  /  管理员 发布于 3年前   947

在 Laravel 中,角色和权限多年来一直是最令人困惑的话题之一。 

大多数情况下,因为没有关于它的文档:相同的东西“隐藏”在框架中的其他术语下,如“gates”、“policies”、“guards”等。

在本文中,我将尝试将它们全部解释 “人类语言”。


门(gate)与权限(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 的同义词。


此外,在某些情况下,权限被称为“能力”,就像在 Bouncer 包中一样。

这也意味着同样的事情——某些行动的能力/许可。 我们将在本文后面介绍这些包。

https://laravel-news.com/bouncer-authorization-package

检查门权限的各种方法

另一个混乱的来源是如何/在哪里检查门。 它非常灵活,您可能会发现非常不同的示例。 

让我们来看看它们:


Option 1. Routes: middleware('can:xxxxxx')

这是上面的例子。 直接在路由/组上,您可以分配中间件:

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

Option 2. Controller: can() / cannot()

在 Controller 方法的第一行,我们可以看到类似这样的内容,使用方法 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);
    }
}



Option 3. Gate::allows() or 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);
}

Option 4. Controller: authorize()

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

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


Option 5. Form Request class:

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

您也可以使用它来检查大门。 这样,您就实现了关注点分离,这对于可靠的代码来说是一个很好的做法,因此控制器不负责验证,因为它是在其专用的表单请求类中完成的。

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 [
            // ...
        ];
    }
}



策略:基于模型的权限集

如果您的权限可以分配给 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)
    {
        //
    }
}


在这些方法中的每一个中,您都定义了真/假返回的条件。 所以,如果我们按照之前盖茨的例子,我们可以这样做:

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

然后,您可以使用与 Gates 非常相似的方式检查 Policy:

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

因此,您指定策略的方法名称和类名称。

换句话说,Policies 只是对权限进行分组的另一种方式,而不是 Gates。 

如果您的操作主要围绕模型的 CRUD,那么策略可能是比 Gates 更方便且结构更好的选择。


角色:通用权限集

让我们讨论另一个困惑:在 Laravel 文档中,您不会找到任何有关用户角色的部分。

原因很简单:

术语“角色”是人为组成的,将权限分组到某种名称下,例如“管理员”或“编辑”。

从框架的角度来看,没有“角色”,只有您可以以任何您想要的方式分组的门/策略。


换句话说,角色是 Laravel 框架之外的一个实体,所以我们需要自己构建角色结构。

它可能是整个身份验证混乱的一部分,但它非常有意义,因为我们应该控制角色的定义方式:

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

因此,角色功能是 Laravel 应用程序的另一层。

这是我们获得可能有帮助的 Laravel 软件包的地方。

但是我们也可以创建没有任何包的角色:

创建“角色”数据库表和角色雄辩模型
添加从用户到角色的关系:一对多或多对多
播种默认角色并将其分配给现有用户
在注册时分配默认角色
更改gates/Policies以检查角色


最后一点是最关键的。

所以,而不是:

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


但切记要预先加载“角色”关系,否则在这里很容易遇到 N+1 查询问题。


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

以我个人的经验,将它们一起构建的通常模型是这样的:

所有权限和角色都保存在数据库中,通过一些管理面板进行管理;

关系:角色多对多权限,用户属于角色(或多对多角色);

然后,在 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 Permission 和 Bouncer,我有一篇单独的长篇文章。 文章很老了,但市场领导者还是一样的,因为他们很稳定。

https://github.com/spatie/laravel-permission
https://laravel-news.com/two-best-roles-permissions-packages

这些包的作用是帮助您将权限管理抽象为一种对人类友好的语言,使用您可以轻松记住和使用的方法。


从 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 链接或我上面的文章中阅读有关如何使用这些软件包的更多信息。

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


附言等等,守卫呢?

哦,那些。

多年来,它们引起了很多混乱。

许多开发人员认为 Guards 是角色,并开始创建单独的数据库表,如“管理员”,然后将它们分配为 Guards。

部分是因为在文档中你可能会发现像 

Auth::guard('admin')->attempt($credentials)) 这样的代码片段

我什至向文档提交了一个 Pull Request 并发出警告以避免这种误解。

https://github.com/laravel/docs/pull/6750

在官方文档中,你可能会发现这一段:

Laravel 的认证设施的核心是由“守卫”和“提供者”组成。 

Guards 定义了如何为每个请求对用户进行身份验证。

例如,Laravel 附带了一个会话守卫,它使用会话存储和 cookie 来维护状态。


所以,守卫是一个比角色更全球化的概念。

守卫的一个示例是“会话”,在文档的后面,您可能会看到 JWT 守卫示例。

换句话说,守卫是一种完整的身份验证机制,对于大多数 Laravel 项目,您永远不需要更改守卫,甚至不需要知道它们是如何工作的。

警卫不在此角色/权限主题范围内.


  • 上一条:
    Laravel 9.12版本发布
    下一条:
    删库跑路之一链家程序员删除公司9TB数据被判7年,望各大码农警之!
  • 昵称:

    邮箱:

    2条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • Laravel 11.15版本发布 - Eloquent Builder中添加的泛型(0个评论)
    • Laravel 11.14版本发布 - 新的字符串助手和ServeCommand改进(0个评论)
    • Laravel 11.12版本发布 - Artisan的`make`命令自动剪切`.php `扩展(0个评论)
    • Laravel的轻量型购物车扩展包:binafy/laravel-cart(0个评论)
    • Laravel 11.11版本发布 - 查看模型中的第三方关系:show(0个评论)
    • 近期文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-07
    • 2017-08
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-08
    • 2020-09
    • 2020-10
    • 2020-11
    • 2021-01
    • 2021-02
    • 2021-03
    • 2021-04
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-11
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-03
    • 2022-04
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    • 2024-05
    • 2024-06
    • 2024-07
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客