zoukankan      html  css  js  c++  java
  • Laravel Auth实现多表多字段用户认证

    Laravel Auth实现多表多字段用户认证

    laravel提供了开箱即用的用户登录功能,6.0之前之前php artisan make:auth,6.0之后需要安装laravel/ui,然后执行

    php artisan ui vue --auth

    npm install && npm run dev

    php artisan migrate 至此我们就拥有关于认证的视图、路由、控制器了

    执行php artsian route:list 查看配套的路由 我们先看登录功能,由此引出auth认证,最终实现一个基于web的多表多字段的认证

    # 直奔主题Auth::attempt,未讲解代码可自行查看
    # AppHttpControllersAuthLoginController@login
    # 登录的主要代码位于IlluminateFoundationAuthAuthenticatesUsers
    
    public function login(Request $request)
    {
        $this->validateLogin($request);
    	// 这部分代码是限流trait带来的 限流器以后会单独讲解
        if (method_exists($this, 'hasTooManyLoginAttempts') &&
            $this->hasTooManyLoginAttempts($request)) {
            $this->fireLockoutEvent($request);
    
            return $this->sendLockoutResponse($request);
        }
    	// 今天的重点由此引出
        if ($this->attemptLogin($request)) {
            return $this->sendLoginResponse($request);
        }
    
        $this->incrementLoginAttempts($request);
    
        return $this->sendFailedLoginResponse($request);
    }
    
    // 调用了guard的attempt方法
    /**
     * Attempt to log the user into the application.
     *
     * @param  IlluminateHttpRequest  $request
     * @return bool
     */
    protected function attemptLogin(Request $request)
    {
        return $this->guard()->attempt(
            $this->credentials($request), $request->filled('remember')
        );
    }
    

    我有点感觉了 你们呢?一起来看Auth门面提供的attempt方法 Auth门面的对应的实例为AuthManager,为什么叫manager呢,就是因为确实是一个管理器,laravel通过guard provider等配置提供了多种的认证方式。前面有关于门面讲解的文章。

    # 我们尝试调用Auth::attempt()方法 实际触发的是AuthManager::__call方法
    /**
     * Dynamically call the default driver instance.
     *
     * @param  string  $method
     * @param  array  $parameters
     * @return mixed
     */
    public function __call($method, $parameters)
    {
        return $this->guard()->{$method}(...$parameters);
    }
    
    # 接着看guard方法
    public function guard($name = null)
    {   
        // getDefaultDriver非常简单返回的是web 如果你们修改相关auth.php配置的话
        $name = $name ?: $this->getDefaultDriver();
    	// 这是一个类似单例的写法 将解析出来的guard实例保存在数组中,下次直接使用时直接返回
        return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
    }
    
    # 接着看resolve方法
    protected function resolve($name)
    {	
        // getConfig获取给定的guard的配置
        // 'web' => [
        //     'driver' => 'session',
        //     'provider' => 'users',
        // ],
        $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';
        // dd($driverMethod); // createSessionDriver   我们接着看createSessionDriver方法	
    
        if (method_exists($this, $driverMethod)) {
            // 默认的情况下是 $name = web
            return $this->{$driverMethod}($name, $config);
        }
    
        throw new InvalidArgumentException(
            "Auth driver [{$config['driver']}] for guard [{$name}] is not defined."
        );
    }
    
    // 创建一个session为驱动的guard实例
    public function createSessionDriver($name, $config)
    {   
        // $config == 'users'
        $provider = $this->createUserProvider($config['provider'] ?? null);
    	// 可以看到实例化guard的时候需要provider实例
        // 至此你是否更加了解auth.php的配置格式了呢?
        $guard = new SessionGuard($name, $provider, $this->app['session.store']);
    	...
    
        return $guard;
    }
    
    // 接着看createUserProvider方法  位于IlluminateAuthCreatesUserProviders
    public function createUserProvider($provider = null)
    {
        if (is_null($config = $this->getProviderConfiguration($provider))) {
            return;
        }
    
        // driver = 'eloquent'
        // 可以看到laravel允许我们提供自己的provider
        if (isset($this->customProviderCreators[$driver = ($config['driver'] ?? null)])) {
            return call_user_func(
                $this->customProviderCreators[$driver], $this->app, $config
            );
        }
    	
        // 走到eloquent分支
        switch ($driver) {
            case 'database':
                return $this->createDatabaseProvider($config);
            case 'eloquent':
                return $this->createEloquentProvider($config);
            default:
                throw new InvalidArgumentException(
                    "Authentication user provider [{$driver}] is not defined."
                );
        }
    }
    
    // 接着查看createEloquentProvider方法
    protected function createEloquentProvider($config)
    {	
        return new EloquentUserProvider($this->app['hash'], $config['model']);
    }
    # 好的,我们可以返回到AuthManager@guard方法了 为我们返回了一个SessionGuard实例
    # 所以我们Auth::attempt() 实际调用的是SessionGuard的attempt方法
    
    # IlluminateAuthSessionGuard
    public function attempt(array $credentials = [], $remember = false)
    {
        $this->fireAttemptEvent($credentials, $remember);
    	// $this->provider就是实例化传递进来的EloquentUserProvider
        $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
    
        if ($this->hasValidCredentials($user, $credentials)) {
            $this->login($user, $remember);
    
            return true;
        }
        
        $this->fireFailedEvent($user, $credentials);
    
        return false;
    }
    
    # 前面说过既然auth.php允许我们配置自己的provider那么当然可以通过我们自己的UserProvider来重写这段找用户的代码了
    # 这就是今天要说的多表多字段认证 我们重写此方法可以通过上传的各种字段进行用户实例的查找
    public function retrieveByCredentials(array $credentials)
    {
        if (empty($credentials) ||
            (count($credentials) === 1 &&
             Str::contains($this->firstCredentialKey($credentials), 'password'))) {
            return;
        }
    
        $query = $this->newModelQuery();
    
        foreach ($credentials as $key => $value) {
            if (Str::contains($key, 'password')) {
                continue;
            }
    		// laravel试图将认证字段拼接成查询条件
            if (is_array($value) || $value instanceof Arrayable) {
                $query->whereIn($key, $value);
            } else {
                $query->where($key, $value);
            }
        }
    	// 返回第一个符合的model实例 此model就是在auth.php中配置的model
        return $query->first();
    }
    
    # 接着看hasValidCredentials方法 判断找到第一个能够通过验证
    # 这soc还是非常值得学习的啊 
    # 也就是说我们通过重写UserProvider的validateCredentials方法就能够最终决定用户是否能够登录应用
    protected function hasValidCredentials($user, $credentials)
    {	
        return ! is_null($user) && $this->provider->validateCredentials($user, $credentials);
    }
    
    # 接着看UserProvider的validateCredentials方法
    # 通过实例化EloquentUserProvider传递进去的hasher进行密码校验
    /**
     * Validate a user against the given credentials.
     *
     * @param  IlluminateContractsAuthAuthenticatable  $user
     * @param  array  $credentials
     * @return bool
     */
    public function validateCredentials(UserContract $user, array $credentials)
    {
        $plain = $credentials['password'];
    
        // getAuthPassword在user模型继承的接口中 返回的是$this->password
        return $this->hasher->check($plain, $user->getAuthPassword());
    }
    
    # 如果校验成功就返回true 失败返回false 我们通过attempt方法的返回值就可以判断用户是合法,从而将用户log到我们的app中
    

    理解了上面的大体流程,来实现下多表多字段登录吧

    # 我们知道了通过重写UserProvider的方法可以实现对登录完全的掌控,但是如何让自定义的UserProvider生效呢?
    # 还记得AuthManager的customProviderCreators属性吗,其实是引入的trait带来的
    # 我们可以通过provider方法注册我们自己的UserProvider
    # 在哪注册合适呢 当然是AuthServiceProvider的boot方法了
    
    1 创建一个UserProvider  
    # 重写EloquentUserProvider的retrieveByCredentials方法
    # 你当然还可以继续重写validateCredentials方法 这里只是娱乐举个例子
    <?php
    
    namespace AppServices;
    
    use IlluminateAuthEloquentUserProvider as BaseProvider;
    
    class EloquentUserProvider extends BaseProvider
    {
        public function retrieveByCredentials(array $credentials)
        {
            if (
                empty($credentials) || (count($credentials) === 1 &&
                    array_key_exists('password', $credentials))
            ) {
                return;
            }
    
            $query = $this->createModel()->newQuery();
    		// 没什么实际意义 我瞎写的代码 
            // 各位可在此编写符合自己业务的用户查找代码
            $rawWhere = collect($credentials)->filter(function ($v, $key) {
                return 'password' != $key;
            })->map(function ($value, $filed) { 
                return "`{$filed}` = '{$value}'";
            })->values()->implode(' or ');
    
            $query->whereRaw($rawWhere);
            return $query->first();
        }
    }
    
    2 配置auth.php
    'providers' => [
        'users' => [
            'driver' => 'frontend_eloquent',
            'model' => AppUser::class,
        ],
    ...
        
    3 在AuthServiceProvider中注册自定义的EloquentUserProvider
    public function boot()
    {
        $this->registerPolicies();
    
        Auth::provider('frontend_eloquent', function ($app, $config) {
            return new EloquentUserProvider($app->make('hash'), $config['model']);
        });
    }
        
    # 这样当使用Auth::guard('web')->attempt()的时候,其中的UserProvider就是我们自定义的了,通过此种方式实现用户登录的控制
    # 你还可以配置个后台用户认证表 配置可能长成这样
    'guards' => [
        'admin' => [
            'driver' => 'session',
            'provider' => 'admins',
        ],
        
    'providers' => [
        'admins' => [
            'driver' => 'backend_eloquent',
            'model' => AppAdmin::class,
        ],
    # 另外你可能还需要在AuthServiceProvider中再Auth::provider一个全新的EloquentUserProvider 这样你完全可以各类用户拥有完全自定义的认证方式了
    # 这样在使用Auth::guard('admin')->attempt()的时候就是你的新的自定义的guard 全新的认证方式了
    
    # 总结一下auth.php中的guards配置表示通过什么样的方式进行用户认证 providers表示用什么方式提供认证的数据
    # 每在guards数组中添加一个配置,对应的在使用Auth::guard('name')的时候就会在AuthManager的guards属性下就会多出一组键值对,key是guards中配置的,
    # 而value是对应生成的guard实例,默认的auth.php中说明提供session和token两种类型的guard实例,拿session驱动的guard举例,每个SessionGuard实例中
    # 都有一个EloquentUserProvider来提供用户数据,我们主要定制的就是这个UserProvider并将其注册到对应的SessionGuard实例中,从而实现定制的用户认证
    

    本文只简单介绍了Auth::attempt的大体思路,大量代码没有提及,感兴趣的可以查看相关文档,提供了大量用户认证的扩展方式。
    https://laravel.com/docs/6.x/authentication#adding-custom-guards

    感谢各位的观看,我们下期再见

  • 相关阅读:
    ueditor 编译出错
    C# HttpWebRequest向远程地址Post文件
    C# HttpWebRequest请求远程地址获取返回消息
    windows server 2008 R2 Enterprise 防火墙开启允许远程桌面登录
    .Net C# 泛型序列化和反序列化JavaScriptSerializer
    两种方法比较两个字符串的不同
    JSP九大内置对象及四个作用域
    聚沙成塔
    maven环境变量配置
    C3P0和DBCP的区别
  • 原文地址:https://www.cnblogs.com/alwayslinger/p/14197821.html
Copyright © 2011-2022 走看看