关于Laravel的拦截器Gate的学习
首先是看到了这个问题 问答:Dcat Admin 使用 Laravel 的拦截器 Gate 和策略 Policy,能否对除 Ap...
当时并没有深究,直接建议用\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 协议》,转载必须注明作者和本文链接