奥丁9
奥丁9
后端
数据库
redis
mysql
mongoDB
达梦
php
laravel
laravel-admin
dcat
表单
表格
java
spring
python
go
c
c++
前端
vue
nodejs
sass/less
html/css
前端框架
javascript
微信生态
公众号
小程序
uniapp
typescript
其他
AI
数据结构
安全
linux
seo
git
健身
算法
正则表达式
docker
待分类
后端
/
php
/
laravel
关于Laravel的拦截器Gate的学习
1年前
aoding9
96
php
laravel
Gate
首先是看到了这个问题 https://learnku.com/laravel/t/78342?#reply270963 当时并没有深究,直接建议用\Admin::user()去验证权限,后面看了最佳回答,然后去找了下dcat的源码怎么实现的,发现这样也可以,没必要自己重新定义一个guard,直接用dcat的就行。 \Admin::user()就是`auth('admin')->user()`或者说`\Auth::guard('admin')`    然后我打算深入了解一下,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了  ``` 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 协议》
,转载必须注明作者和本文链接