Laravel8:Gate源代码(2)---- 执行权限检查

文件位置:Illuminate\Auth\Access\Gate

allows(),check(),inspect(),raw() 这四个成员方法都是public 封装属性,可以在外部直接调用。

成员方法 作用
allows 确定是否应为当前用户授予给定的能力
check 确定是否应为当前用户授予所有给定的能力
inspect 检查用户的给定能力
raw 从授权回调中获取原始结果

使用上述四个方法,检查用户是否有相关操作授权按。研究代码发现,上述四个方法,属于一脉相承型的。放在一起研究是最合适的了。

这个图显示了,从allows()方法开始,内部的程序流程走向,最终将在raw()方法内部终结,并向返回结果。


image.png

现在将从allows()方法开始,学习整体底层代码流程。可以看到,allows()方法内部调用check()方法实现的。

public function allows($ability, $arguments = [])
{
    return $this->check($ability, $arguments);
}

然后在check()方法内部,先变成集合对象,然后通过every()方法,内部执行inspect()方法,最终得到Response对象,执行allowed()方法,返回一个布尔值。

    public function check($abilities, $arguments = [])
    {
        return collect($abilities)->every(function ($ability) use ($arguments) {
            return $this->inspect($ability, $arguments)->allowed();
        });
    }

分析代码:

check方法,可以检查单独的授权动作,也可以批量检查授权,这块有两种用法

Gate::check("addMember")
或者批量检查多个授权操作,这时候要用数组类型传值
Gate::check(["addMember","editMember"]);

然后代码里 collect() 函数会生成一个集合对象Collection (Illuminate\Support\Collection) ,它的结构如下:

Illuminate\Support\Collection Object
(
    [items:protected] => Array
        (
            [0] => "addMember"
            [1] => "editMember"
        )

)

集合类中的every()方法,它有三个传数,如果像现在程序中这样,只传入一个参数,并且参数类型为匿名函数。那么every()方法将先遍历items这个数组,将数组中每个元素(授权动作的名字)作为参数,放入到这个匿名函数中去运行。 如果其中有任意一个运行结果为false时,将终止遍历操作。
只有全部的授权都返回true时,最终every()方法才会返回true.

现在看一下check方法中,传入every()里的匿名函数。

function ($ability) use ($arguments) {
            return $this->inspect($ability, $arguments)->allowed();
}

在every()方法中,将运行这个匿名函数,$ability 就是注册的授权动作addMember,editMember

这个函数执行后,其中运行的就是下面这句

return $this->inspect($ability, $arguments)->allowed();

$this->inspect($ability, $arguments) 将得到一个Response对象(Illuminate\Auth\Access\Response)

Response对象调用allowed()方法 ,得到布尔值,是true 或 false

接下来继续看inspect()方法

inspect()方法分析

inspect方法 直接使用时,返回Response对象(Illuminate\Auth\Access\Response)

它内部会先调用raw()方法,通过raw()方法找到注册授权时,addMember 对应的回调函数,然后运行函数,得到结果并返回给inspect()方法

如果当初Gate::define()注册授权的时候

raw() 返回两种类型:

第1种,如果注册时,像下面这样,将得到Response对象

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

        Gate::define("addMember",function($user){
            return $user->id == 1 ? 
                    Response::allow("ok,你可以操作"): Response::deny("你没有权限");    
        }); 
    }

第2种就是普通的返回布尔值,inspect()方法将根据布尔值,实例化不同的Response对象。

源代码:
public function inspect($ability, $arguments = [])
    {
        try {
           //找到授权动作对应的处理方法,并返回运行结果
            $result = $this->raw($ability, $arguments);

           //如果result是一个 Response类实例化对象,将直接返回
            if ($result instanceof Response) {
                return $result;
            }
            //$result 是布尔值,根据结果调用 Response中的方法
            return $result ? Response::allow() : Response::deny();
        } catch (AuthorizationException $e) {
            return $e->toResponse();
        }
    }

inspect()方法整体还是很容易理解的,主要是raw()方法,内部流程的线索比较多。现在看最重要的raw()方法

raw()方法

这可以独立使用 Gate::raw() 获得指定授权的响应结果。

我总结它内部的运行,分成下面几个部分:

(1)先检查参数2 如果是字符串,就将它转化成数组。

(2)调用 $this->resolveUser() 得到当前登陆用户对应的Eloquent 模型对象 ,这样就可以掌握当前登陆者的各方面信息

(3)是寻找注册的授权动作,得到对应的回调函数的运行结果。

现在分析raw()源代码:

public function raw($ability, $arguments = [])
{
       //转化成数组形式
        $arguments = Arr::wrap($arguments);
      //得到当前登陆用户的Eloquent 模型对象
        $user = $this->resolveUser();

        $result = $this->callBeforeCallbacks(
            $user, $ability, $arguments
        );

        if (is_null($result)) {
            $result = $this->callAuthCallback($user, $ability, $arguments);
        }

        return $this->callAfterCallbacks(
            $user, $ability, $arguments, $result
        );
    }

这里主要讲$this->resolveUser()的执行过程,raw()方法其余代码在这个链接里有分析。
https://www.jianshu.com/p/b6e9a4ccb221

第一部分:检查参数类型

这块使用了Laravel定义的数组类 Arr,它的文件位置:Illuminate/Collections/Arr.php
wrap()方法很简单,判断是不是数组类型,如果不是转化成数组。

 //转化成数组
  $arguments = Arr::wrap($arguments);

第二部分:获取用户的Eloquent模型

$user = $this->resolveUser();

首先说明一下,我现在使用的Laravel8里,用户认证这块是使用的默认机制,在 config/auth.php中,pc端用的是web守卫模式,对应的用户模型类文件就是 App\Models\User.php 是使用的Eloquent类型。

'defaults' => [
        'guard' => 'web',
        'passwords' => 'users',
    ],

通过resolveUser() 得到当前登陆用户的Eloquent 模型对象
方法代码如下:

protected function resolveUser()
{
        return call_user_func($this->userResolver);
 }

以回调函数的形式,调用$this->userResolver. 
这个userResolver是Gate类属性,在当初注册绑定Gate类时,传入了一个匿名函数。

function () use ($app) {
   return call_user_func($app['auth']->userResolver());
});

所以此时运行这个匿名函数,实际上就是执行

return call_user_func($app['auth']->userResolver());

其中 $app['auth']->userResolver()  这句代码里

$app['auth'] 是 new AuthManager($app) 对象,

而AuthManager类里的userResolver方法代码如下:

public function userResolver()
{
  return $this->userResolver;
}

在AuthManager类构造函数里,给 $this->userResolver 作了初始化,userResolver 也是一个匿名函数。
function ($guard = null) {
       return $this->guard($guard)->user();
}

因此最后将执行

$this->guard($guard)->user();

$this->guard($guard) 将返回对应当前用户的模型类,例如PC端默认使用的是SessionGuard ,最后调用user()方法,得到当前登陆用户的Eloquent模型对象。

第二部分的分析结束,这块只要是看过Auth认证的底层代码逻辑,就比较明白,否则可能会觉得层级太深,感觉很绕。

第三部分:获取注册授权时,对应的回调函数

第三部分,查找授权指定的回调函数时,其实是分了三步进行。
因为Gate模块提供了一个叫授权拦截的功能。我先来说明一下这个什么叫拦截。

可以通过 Gate::before() 和 Gate:after() 注册前置和后置授权。

如果使用了上述两个方法后,raw()方法内部就会先检查 有没有Gate::before()操作,如果有这个操作,并且函数运行后,返回true,剩余的程序流程,就将不执行了。

如果没有Gate::before() ,才会执行 $this->callAuthCallback() 找到注册授权对应的回调函数。

当$this->callAuthCallback()操作结束后,检查有没有Gate::after()后置授权,如果存在,检查一下返回结果。

$result = $this->callBeforeCallbacks(
            $user, $ability, $arguments
 );

        if (is_null($result)) {
            $result = $this->callAuthCallback($user, $ability, $arguments);
        }

   
        return $this->callAfterCallbacks(
            $user, $ability, $arguments, $result
        );

关于before()和after()的操作,我单独放到了下面的链接

https://www.jianshu.com/p/b6e9a4ccb221

$this->callAuthCallback()部分的代码分析,放到了下面这个链接。

https://www.jianshu.com/p/828b27080cac

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容