关于Laravel的拦截器Gate的学习

首先是看到了这个问题 问答:Dcat Admin 使用 Laravel 的拦截器 Gate 和策略 Policy,能否对除 Ap...

当时并没有深究,直接建议用\Admin::user()去验证权限,后面看了最佳回答,然后去找了下dcat的源码怎么实现的,发现这样也可以,没必要自己重新定义一个guard,直接用dcat的就行。

\Admin::user()就是auth('admin')->user()或者说\Auth::guard('admin')

file

file

Laravel

然后我打算深入了解一下,ctrl+右键点$this->authorize(),找到

    public function authorize($ability, $arguments = [])
    {
        [$ability, $arguments] = $this->parseAbilityAndArguments($ability, $arguments);

        return app(Gate::class)->authorize($ability, $arguments);
    }

然后点进app(Gate::class)->authorize($ability, $arguments);,找到

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

    /**
     * Inspect the user for the given ability.
     *
     * @param  string  $ability
     * @param  array|mixed  $arguments
     * @return \Illuminate\Auth\Access\Response
     */
    public function inspect($ability, $arguments = [])
    {
        try {
            $result = $this->raw($ability, $arguments);

            if ($result instanceof Response) {
                return $result;
            }

            return $result ? Response::allow() : Response::deny();
        } catch (AuthorizationException $e) {
            return $e->toResponse();
        }
    }

authorize调用inspect,然后再去raw方法,调用resolveUser获取$user

   public function raw($ability, $arguments = [])
    {
        $arguments = Arr::wrap($arguments);

        $user = $this->resolveUser();

        // First we will call the "before" callbacks for the Gate. If any of these give
        // back a non-null response, we will immediately return that result in order
        // to let the developers override all checks for some authorization cases.
        $result = $this->callBeforeCallbacks(
            $user, $ability, $arguments
        );

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

        // After calling the authorization callback, we will call the "after" callbacks
        // that are registered with the Gate, which allows a developer to do logging
        // if that is required for this application. Then we'll return the result.
        return tap($this->callAfterCallbacks(
            $user, $ability, $arguments, $result
        ), function ($result) use ($user, $ability, $arguments) {
            $this->dispatchGateEvaluatedEvent($user, $ability, $arguments, $result);
        });
    }

继续找

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

userResolver是初始化的时候传入的,那么去找在哪里new的,全局搜索new Gate或者Illuminate\Auth\Access\Gate

    public function __construct(Container $container, callable $userResolver, array $abilities = [],
                                array $policies = [], array $beforeCallbacks = [], array $afterCallbacks = [],
                                callable $guessPolicyNamesUsingCallback = null)
    {
        $this->policies = $policies;
        $this->container = $container;
        $this->abilities = $abilities;
        $this->userResolver = $userResolver;
        $this->afterCallbacks = $afterCallbacks;
        $this->beforeCallbacks = $beforeCallbacks;
        $this->guessPolicyNamesUsingCallback = $guessPolicyNamesUsingCallback;
    }

AuthServiceProvider中找到了

    public function register()
    {
        $this->registerAuthenticator();
        $this->registerUserResolver();
        $this->registerAccessGate();
        $this->registerRequirePassword();
        $this->registerRequestRebindHandler();
        $this->registerEventRebindHandler();
    }

    /**
     * Register the authenticator services.
     *
     * @return void
     */
    protected function registerAuthenticator()
    {
        $this->app->singleton('auth', function ($app) {
            return new AuthManager($app);
        });

        $this->app->singleton('auth.driver', function ($app) {
            return $app['auth']->guard();
        });
    }

    /**
     * Register a resolver for the authenticated user.
     *
     * @return void
     */
    protected function registerUserResolver()
    {
        $this->app->bind(AuthenticatableContract::class, function ($app) {
            return call_user_func($app['auth']->userResolver());
        });
    }

    /**
     * Register the access gate service.
     *
     * @return void
     */
    protected function registerAccessGate()
    {
        $this->app->singleton(GateContract::class, function ($app) {
            return new Gate($app, function () use ($app) {
                return call_user_func($app['auth']->userResolver());
            });
        });
    }

首先直接看registerAccessGate,里面注册了Gate,它的userResolver,来自上面registerAuthenticator注册的$app[‘auth’],也就是AuthManager,ok点进去看

public function __construct($app)
    {
        $this->app = $app;

        $this->userResolver = function ($guard = null) {
            return $this->guard($guard)->user();
        };
    }

    /**
     * Attempt to get the guard from the local cache.
     *
     * @param  string|null  $name
     * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
     */
    public function guard($name = null)
    {
        $name = $name ?: $this->getDefaultDriver();

        return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
    }

在构造函数中userResolver被设置为一个回调函数,通过传入$guard,调用guard方法,如果不传,则调用getDefaultDriver

   public function getDefaultDriver()
    {
        return $this->app['config']['auth.defaults.guard'];
    }

可以看到默认的就是config/auth.php中的defaults.guard,也就是

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

拿到guard名称之后,$this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);这里由于不存在,所以调resolve方法

    protected function resolve($name)
    {
        $config = $this->getConfig($name);

        if (is_null($config)) {
            throw new InvalidArgumentException("Auth guard [{$name}] is not defined.");
        }

        if (isset($this->customCreators[$config['driver']])) {
            return $this->callCustomCreator($name, $config);
        }

        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';

        if (method_exists($this, $driverMethod)) {
            return $this->{$driverMethod}($name, $config);
        }

        throw new InvalidArgumentException(
            "Auth driver [{$config['driver']}] for guard [{$name}] is not defined."
        );
    }

    /**
     * Call a custom driver creator.
     *
     * @param  string  $name
     * @param  array  $config
     * @return mixed
     */
    protected function callCustomCreator($name, array $config)
    {
        return $this->customCreators[$config['driver']]($this->app, $name, $config);
    }

首先是验证了auth.php是否配置了传入的guard,然后看有没有注册过自定义的customCreator,这里没有,由于使用默认值session,$driverMethod拼接为createSessionDriver,调用

    public function createSessionDriver($name, $config)
    {
        $provider = $this->createUserProvider($config['provider'] ?? null);

        $guard = new SessionGuard(
            $name,
            $provider,
            $this->app['session.store'],
        );

        // When using the remember me functionality of the authentication services we
        // will need to be set the encryption instance of the guard, which allows
        // secure, encrypted cookie values to get generated for those cookies.
        if (method_exists($guard, 'setCookieJar')) {
            $guard->setCookieJar($this->app['cookie']);
        }

        if (method_exists($guard, 'setDispatcher')) {
            $guard->setDispatcher($this->app['events']);
        }

        if (method_exists($guard, 'setRequest')) {
            $guard->setRequest($this->app->refresh('request', $guard, 'setRequest'));
        }

        if (isset($config['remember'])) {
            $guard->setRememberDuration($config['remember']);
        }

        return $guard;
    }

首先是createUserProvider,根据auth.php,默认web的provider为users,dcat的是admin.php中的admin

   'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],
'providers' => [
    'admin' => [
        'driver' => 'eloquent',
        // 'model'  => Dcat\Admin\Models\Administrator::class,
        'model'  => Yang\Models\AdminUser::class,
    ],
],

然后new了一个SessionGuard

   $guard = new SessionGuard(
            $name,
            $provider,
            $this->app['session.store'],
        );

点进去,终于找到了眼熟的user方法

    public function __construct($name,
                                UserProvider $provider,
                                Session $session,
                                Request $request = null,
                                Timebox $timebox = null)
    {
        $this->name = $name;
        $this->session = $session;
        $this->request = $request;
        $this->provider = $provider;
        $this->timebox = $timebox ?: new Timebox;
    }
    /**
     * Get the currently authenticated user.
     *
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function user()
    {
        if ($this->loggedOut) {
            return;
        }

        // If we've already retrieved the user for the current request we can just
        // return it back immediately. We do not want to fetch the user data on
        // every call to this method because that would be tremendously slow.
        if (! is_null($this->user)) {
            return $this->user;
        }

        $id = $this->session->get($this->getName());

        // First we will try to load the user using the identifier in the session if
        // one exists. Otherwise we will check for a "remember me" cookie in this
        // request, and if one exists, attempt to retrieve the user using that.
        if (! is_null($id) && $this->user = $this->provider->retrieveById($id)) {
            $this->fireAuthenticatedEvent($this->user);
        }

        // If the user is null, but we decrypt a "recaller" cookie we can attempt to
        // pull the user data on that cookie which serves as a remember cookie on
        // the application. Once we have a user we can return it to the caller.
        if (is_null($this->user) && ! is_null($recaller = $this->recaller())) {
            $this->user = $this->userFromRecaller($recaller);

            if ($this->user) {
                $this->updateSession($this->user->getAuthIdentifier());

                $this->fireLoginEvent($this->user, true);
            }
        }

        return $this->user;
    }

$id = $this->session->get($this->getName());这里的getName也就是new SessionGuard时传入的$name生成的key

    public function getName()
    {
        return 'login_'.$this->name.'_'.sha1(static::class);
    }

我在dcat后台登录后,dd(session()->all()),发现有一个login_admin_xxxx的key,这就是登录用户的id了

关于Laravel的Gate的学习

   if (! is_null($id) && $this->user = $this->provider->retrieveById($id)) {
            $this->fireAuthenticatedEvent($this->user);
        }

点retrieveById,发现是个接口,然后点接口中的retrieveById实现,有EloquentUserProvider和DatabaseUserProvider,由于provider是eloquent,所以进到EloquentUserProvider

    public function __construct(HasherContract $hasher, $model)
    {
        $this->model = $model;
        $this->hasher = $hasher;
    }

    /**
     * Retrieve a user by their unique identifier.
     *
     * @param  mixed  $identifier
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveById($identifier)
    {
        $model = $this->createModel();

        return $this->newModelQuery($model)
                    ->where($model->getAuthIdentifierName(), $identifier)
                    ->first();
    }

model就是配置文件中的model,web的guard就是App\Models\User,admin的guard就是Dcat\Admin\Models\Administrator
根据session取的id,first了一个Administrator模型。

   if (is_null($this->user) && ! is_null($recaller = $this->recaller())) {
            $this->user = $this->userFromRecaller($recaller);

            if ($this->user) {
                $this->updateSession($this->user->getAuthIdentifier());

                $this->fireLoginEvent($this->user, true);
            }
        }

后面这段则是session过期,或清除了session后,记住我功能自动登录的逻辑,因为登录时session中还没有id,所以先recaller获取cookie中的remember me的token,userFromRecaller则根据这个token去获取用户,如果获取到了,则更新session并且触发login事件。

ok现在回到dcat框架

在composer.json中自动加载了AdminServiceProvider

    "extra": {
        "laravel": {
            "providers": [
                "Dcat\\Admin\\AdminServiceProvider"
            ]
        }
    }

register中aliasAdmin注册别名,Admins注册为Dcat\Admin\Admin的别名,loadAdminAuthConfig加载了admin.php的auth配置

    public function register()
    {
        $this->aliasAdmin();
        $this->loadAdminAuthConfig(); // 这里注册了admin.auth配置
        $this->registerRouteMiddleware();
        $this->registerServices();
        $this->registerExtensions();

        $this->commands($this->commands);

        if (config('app.debug')) {
            $this->commands($this->devCommands);
        }
    }

    protected function aliasAdmin()
    {
        if (! class_exists(\Admin::class)) {
            class_alias(Admin::class, \Admin::class);
        }
    }
    /**
     * 设置 auth 配置.
     *
     * @return void
     */
    protected function loadAdminAuthConfig()
    {
        config(Arr::dot(config('admin.auth', []), 'auth.'));

        foreach ((array) config('admin.multi_app') as $app => $enable) {
            if ($enable) {
                config(Arr::dot(config($app.'.auth', []), 'auth.'));
            }
        }
    }

而在AuthController的postLogin中$this->guard()->attempt($credentials, $remember),guard方法里面返回了Admin::guard()

    public function postLogin(Request $request)
    {
        $credentials = $request->only([$this->username(), 'password']);
        $remember = (bool) $request->input('remember', false);

        /** @var \Illuminate\Validation\Validator $validator */
        $validator = Validator::make($credentials, [
            $this->username()   => 'required',
            'password'          => 'required',
        ]);

        if ($validator->fails()) {
            return $this->validationErrorsResponse($validator);
        }

        if ($this->guard()->attempt($credentials, $remember)) {
            return $this->sendLoginResponse($request);
        }

        return $this->validationErrorsResponse([
            $this->username() => $this->getFailedLoginMessage(),
        ]);
    }

    // ...

    protected function guard()
    {
        return Admin::guard();
    }

终于,我们看到了本文最开头的截图\Admin::user()

     /**
     * 获取登录用户模型.
     *
     * @return Model|Authenticatable|HasPermissions
     */
    public static function user()
    {
        return static::guard()->user();
    }

    /**
     * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard|GuardHelpers
     */
    public static function guard()
    {
        return Auth::guard(config('admin.auth.guard') ?: 'admin');
    }

还有一个地方补充一下,关于AuthorizesRequests这个trait:

为什么我要引用这个trait呢,在普通的控制器中,可以使用$this->authorize('own', $order);来验证订单是否有own权限,但是起初我尝试这么写,发现在dcat的控制器中没有authorize方法,那么普通控制器的authorize是在哪里引入的呢

往上找父级,发现是在控制器的基类App\Http\Controllers\Controller中引用了,而dcat父级的控制器并没有引用它。

<?php

namespace App\Http\Controllers;

use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;

class Controller extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests; // 在这里引用了
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!