How to rewrite the auth that comes with laravel

The Laravel version of this article is 5.5

Implementation in laravel

Laravel comes with a complete auth scheme, you can refer to the article laravel application . Here I implement a few important steps:

    //生成代码
    php artisan make:auth
    //导入数据表结构
    php artisan migrate

Important documents:

//用户模型
app/Http/User.php

//业务逻辑
app/Http/Controllers/Auth/*.php

//配置文件
config/auth.php

Let's take a look at the configuration file, and I will add a comment to my personal understanding and write it later:

<?php
return [
    'defaults' => [                      /*默认配置*/ 
        'guard' => 'web',                /*guard(我个人看成检验器,翻译是守卫、监视者) 使用web的配置 */
        'passwords' => 'users',           /*密码管理,使用users的配置*/
    ],
    'guards' => [                                  
        'web' => [                     /*web的检验器的配置,有驱动器和提供者2个属性*/
            'driver' => 'session',
            'provider' => 'users',
        ],
        'api' => [                      /*api的检验器的配置,有驱动器和提供者2个属性*/
            'driver' => 'token',
            'provider' => 'users',
        ],
    ],
    'providers' => [                     /*提供者的驱动器和模型*/
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Http\User::class,
        ]
    ],
    'passwords' => [
        'users' => [                    /*密码管理的提供者属性*/
            'provider' => 'users',
            'table' => 'password_resets',
            'expire' => 60,
        ],
    ]
];

From the configuration point of view, we can think of laravel's auth as multiple parts (and passwords should belong to one of the functions):

  1. Entry (web|api|...)
  2. validator guards
  3. providers providers

Analyze source code

Taking login as an example, we started to read the source code. When we clicked in and looked at LoginController.php, we found that he did not have a few lines of code. But he used a trait, as for what a trait is, please refer to the trait of php . There are only some simple business logic implementation methods. If you don't need it, you can rewrite or annotate it.

However, I found out from the constructor that he uses middleware. If you don't know what middleware is, please refer to my personal blog laravel (5.5) to customize middleware .

$this->middleware('guest')->except('logout');

Obviously, he uses middleware for processing, we trace the code, and we can go to the next step.

How laravel injects guard

The guest middleware is defined in app/Http/Kernel.php.

'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class

The main code is as follows:

 public function handle($request, Closure $next, $guard = null)
    {
        if (Auth::guard($guard)->check()) {
            return redirect('/');
        }

        return $next($request);
    }

Well, we found the Auth we were looking for. Auth is a facade, that is, the entrance of a class that can be used quickly. We found the location of his implementation class \Illuminate\Auth\AuthServiceProvider, and finally found our true master.

    protected function registerAuthenticator()
    {
        $this->app->singleton('auth', function ($app) {
            // Once the authentication service has actually been requested by the developer
            // we will set a variable in the application indicating such. This helps us
            // know that we need to set any queued cookies in the after event later.
            $app['auth.loaded'] = true;

            return new AuthManager($app);
        });

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

Whenever a request enters, as long as the Auth facade is used, the authentication identity will be registered. The implementation class is the AuthManager class. In this class, there is such a method to inject the validator.

    public function guard($name = null)
    {
        $name = $name ?: $this->getDefaultDriver();
        return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
    }

Did you find it? The driver is written from here, and a driver class will be generated with the driver in the configuration file. However, the quality of this code is not very high, such as the following:

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

emmm, hard string concatenation to get the method name? ? ? Good, according to the "elegant framework" of laravel, shouldn't the factory mode be added? Well, a little rant once.

How laravel's validator works

Next, we can see how his validator is implemented. Take TokenGuard as an example.

   public function createTokenDriver($name, $config)
    {

        $guard = new TokenGuard(
            $this->createUserProvider($config['provider'] ?? null),
            $this->app['request']
        );

        $this->app->refresh('request', $guard, 'setRequest');

        return $guard;
    }

A TokeGuard class is returned. After a closer look, it is nothing more than to judge whether there is a value to submit [api_key|api_token], if not, go to the HTTP verification header Authorization Bearer or $_SERVER['PHP_AUTH_PW'], and then, the important place coming.

    public function validate(array $credentials = [])
    {
        if (empty($credentials[$this->inputKey])) {
            return false;
        }
        $credentials = [$this->storageKey => $credentials[$this->inputKey]];
        if ($this->provider->retrieveByCredentials($credentials)) {
            return true;
        }
        return false;
    }

Check the table directly here? The condition is the mode of key=input key, which is too scary. At least the primary key should be used to look up the table?

modify rewrite

From the above analysis, most of the functional code has been analyzed, and we started to rewrite this complex wheel. Overriding, of course, it is better to implement the underlying class first.

rewrite of model

When a user's attributes come from multiple tables, one model is definitely not enough, but Laravel's Auth is obviously inconvenient to expand. Therefore, we can only rewrite part of it. We find where laravel is implemented! Recall from the initial configuration, the default drive used is eloquent and the class is App\Http\User. Find the implementation class Illuminate\Auth\uentUserProvider. There are a variety of query methods implemented in it, so we found a rule. Before querying, he must create a model first, and the model is our class App\Http\User. for example:

 $query = $this->createModel()->newQuery();
        foreach ($credentials as $key => $value) {
            if (! Str::contains($key, 'password')) {
                $query->where($key, $value);
            }
        }

return $query->first();

After searching for a long time, I found that it is not easy for us to rewrite this class, because this class is not injected with configuration, but I found a rule, before querying, we must first newQuery(), the calling object of this method is our configuration of. So, for my rewritten version, I rewrote the newQuery() of App\Http\User

    public function  newQuery() {
                $query = $this->newBaseQueryBuilder();
                $builder =  new class($query) extends Builder {

                        /**
                         * Create a new Eloquent query builder instance.
                         *
                         * @param  \Illuminate\Database\Query\Builder  $query
                         * @return void
                         */
                        public function __construct(QueryBuilder $query)
                        {

                                parent::__construct($query);
                        }
                        /**
                         * Execute the query and get the first result.
                         *
                         * @param  array  $columns
                         * @return \Illuminate\Database\Eloquent\Model|object|static|null
                         */
                        public function first($columns = ['*']) {
                                /** @var Model $user */
                                $user =  $this->take(1)->get($columns)->first();
                                //從別的模型查數據  設置到這個模型裏面
                                if($user!=null) {
                                        //拓展user的其他屬性
                                        $user->setAttribute('acl', ['dsadas', 'dsadas', 'dsada']);
                                }
                                //var_dump($user);exit();
                                return $user;
                        }

                };
                $builder->setModel($this);
                return $builder;
        }

Is it that the problem that user information exists in multiple tables is simply solved.

Override of guard

However, such as the above interface, the general call will be very diligent. At this time, we also use such messy data to check the database every time, whether it will cause the db card IO (when concurrent). In general, we already know the user's ID, and the interface usually requests the user's information, isn't it? Then, we obviously only need to query the user information according to the ID.

First, we create a new class App\Library\Auth\TokenGuard for future use.

Looking at the previous TokeGuard class, we found that his condition is to use the token to query, we might as well change it to the primary key, but where does the primary key come from? So we need to get user_id first. I imagine that my token is encrypted by user_id, at this time, I just need to decrypt it.

    $this->uid = $this->_validateToken($token);  //伪代码
    public function validate(array $credentials = [])
    {
        if($this->provider->retrieveById($this->uid)) {  //直接改成从ID查询
            return true;
        }
        return false;
    }

Obviously, since we already know the ID, it would be nice to use the ID query. If the performance requirements are particularly high and you don't want to query the database, you can implement this part in redis. This is not our focus, so I won't go into details. (To implement or modify newQuery to redis query, remember to return the Model object).

Rewrite of AuthManager

Now that the validator is also written, we must override our own validator. Create a new class and override the createTokenDriver method with your own class.

class AuthManager extends \Illuminate\Auth\AuthManager
{
        public function createTokenDriver($name, $config)
        {
                $guard = new TokenGuard(
                        $this->createUserProvider($config['provider'] ?? null),
                        $this->app['request']
                );

                $this->app->refresh('request', $guard, 'setRequest');

                return $guard;
        }
}

Other configuration

Finally, we need to load our authentication service into the kernel. Rewrite App\Providers\AuthServiceProvider to integrate the original authentication class, just rewrite registerAuthenticator, and other functions can still be used.

class AuthServiceProvider extends \Illuminate\Auth\AuthServiceProvider
{
        protected function registerAuthenticator()
        {
                $this->app->singleton('auth', function ($app) {
                        $app['auth.loaded'] = true;

                        return new \App\Library\Auth\AuthManager($app);
                });

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

config/app.php modification

Illuminate\Auth\AuthServiceProvider::class
=》
App\Providers\AuthServiceProvider::class

Summarize

personal experience

In fact, I don't like this method very much. After all, login and registration, which contains business logic, I don't think it makes much sense to encapsulate it into the framework. But in order to be lazy, rewriting and reusing is also a way.

Remark

Note that when changing the password and changing the update, you need to unset the data of other tables before the update, otherwise it will report that the field does not exist.

Source location

A teaching material management system that is being implemented, edu_book for a teacher I have a good relationship with . There is also a project of the company that uses this method to rewrite auth, so it cannot be open sourced.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324415834&siteId=291194637