美文网首页
Laravel 用户认证 源码学习

Laravel 用户认证 源码学习

作者: yangjingqzp | 来源:发表于2018-05-19 14:34 被阅读0次

    本文介绍 Laravel 用户认证的使用、基本逻辑和底层的实现。

    快速使用

    在 Laravel 框架初始化后,运行 php artisan make:authphp artisan migrate 就能启用 Laravel 自带的用户认证功能。

    1. 用户认证的配置文件在 config/auth.php 中,
    // 默认使用的配置。
    // guard 是用户认证逻辑的实现。web 是网站用户认证逻辑;api 是 API 用户认证逻辑
    'defaults' => [
            'guard' => 'web',
            'passwords' => 'users',
    ],
    

    guard 还需配置对应的 driverprovider
    driver 配置对应具体实现 guard 的逻辑类;provider 是逻辑类使用的数据提供者(查询、更新对应的用户信息的具体实现类)。

    'guards' => [
            'web' => [
                'driver' => 'session',
                'provider' => 'users',
            ],
            ...
        ],
    
    1. 认证的路由信息在 Illuminate\Routing\Router.php 中,通过 Illuminate\Support\Facades\Authroutes() 方法引入。注册和登录部分如下:
    public function auth()
    {
            // Authentication Routes...
            $this->get('login', 'Auth\LoginController@showLoginForm')->name('login');
            $this->post('login', 'Auth\LoginController@login');
            $this->post('logout', 'Auth\LoginController@logout')->name('logout');
    
            // Registration Routes...
            $this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
            $this->post('register', 'Auth\RegisterController@register');
    
            // Password Reset Routes...
     }
    

    由路由信息可知,注册、登录对应的控制器和方法。

    1. 限制认证用户才能访问
      通常我们有一些接口是需要登录后才能访问的,在 Laravel 中是给路由分组,并添加中间件来实现。
    // 请求到达路由中的 Controller 前,会先通过 auth 中间件验证
    Route::middleware('auth')->group(function () {
        Route::get('/user', 'UserController@index');
        // ....
    });
    

    App\Http\Kernel 中定义了 auth 中间件

     protected $routeMiddleware = [
            'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
            // ....
    ]
    

    注册、登录、登录检测逻辑

    注册逻辑

    注册逻辑在控制器 Auth\RegisterControllerregister 方法中,具体是由控制器中的 RegistersUsers trait 类实现。

    public function register(Request $request)
    {
         // 调用控制器中的方法对参数进行验证,包括 name\email\password
         $this->validator($request->all())->validate();
         // 调用控制器中 create 方法创建用户
         // 使用 $user 初始化 Redistered 对象
         // 通过 event() 函数触发注册事件
         event(new Registered($user = $this->create($request->all())));
          
         // 获取 guard 实例,并通过 guard 登录创建的用户
         $this->guard()->login($user);
         // 根据 $user 注册信息进行页面跳转
         return $this->registered($request, $user)
                            ?: redirect($this->redirectPath());
    }
    
    protected function guard()
    {
        // 通过 Illuminate\Support\Facades\Auth 获取对应 guard 对象
        return Auth::guard();
    }
    

    登录逻辑

    登录逻辑在控制器 Auth\LoginControllerlogin 方法中,具体是由控制器中的 AuthenticatesUsers trait 类实现。

    public function login(Request $request)
    {
       // 参数验证, email\password 参数
       $this->validateLogin($request);
    
       // 使用 ThrottlesLogins trait 对登录进行限制(一分钟内,登录失败超过配置的次数,将不能进行登录),防止恶意的登录尝试
       // 限制的依据是 登录的 email 和 IP 地址。
      
       if ($this->hasTooManyLoginAttempts($request)) {
            // 触发登录锁定事件
            $this->fireLockoutEvent($request);
            // 返回锁定的响应信息
            return $this->sendLockoutResponse($request);
       }
       // 根据 $request 中的登录凭证尝试登录
       // 这里实际以是获取 guard 对象进行登录尝试 
       if ($this->attemptLogin($request)) {
           // 登录成功后,重新生成 session,并跳转到设置的登录成功页面
           return $this->sendLoginResponse($request);
       }
    
       // 没登录成功,增加登录失败次数,返回登录失败的响应
       $this->incrementLoginAttempts($request);
    
       return $this->sendFailedLoginResponse($request);
    }
    
    protected function attemptLogin(Request $request)
    {
        // 通过调用 Illuminate\Support\Facades\Auth 获取 guard 对象,并通过 guard 进行实际的登录逻辑
        return $this->guard()->attempt(
           $this->credentials($request), $request->has('remember')
        );
    }
    

    登录检测逻辑

    中间件中通过 authenticate 方法检测用户是否登录

    protected function authenticate(array $guards)
    {  
        // 默认时,传递的 $guards 为空
        if (empty($guards)) {
            // 调用注入的 auth 对象的 authenticate 方法。auth 会调用默认 guard 的 authenticate 方法
            return $this->auth->authenticate();
        }
    
        foreach ($guards as $guard) {
            // 调用特定 guard 的 check 方法
            if ($this->auth->guard($guard)->check()) {
                return $this->auth->shouldUse($guard);
            }
        }
    
        throw new AuthenticationException('Unauthenticated.', $guards);
    }
    

    认证底层实现源码阅读

    上面 Controller 中的登录和注册逻辑最终都是调用 Illuminate\Support\Facades\Auth 来获取 guard ,并通过 guard 来进行实际的登录、注册逻辑。
    Illuminate\Support\Facades\Auth 是应用为 Illuminate\Auth\AuthManager 类提供的一个静态的接口,所以应用最终是使用 AuthManager 中的 guard() 方法来获取 guard 实例的。Facade 的原理可以参考文档
    下面为获取 guard 实例的主要逻辑:

    public function guard($name = null)
    {
        // 如果没有传 $name,就获取默认的 guard 。 默认的为配置中的 web
        $name = $name ?: $this->getDefaultDriver();
        // 如果 guards 已经被实例化的,就直接调用,否则通过 resolve 方法创建 guard 对象
        return isset($this->guards[$name])
                    ? $this->guards[$name]
                    : $this->guards[$name] = $this->resolve($name);
    }
    
    protected function resolve($name)
    {
        // 获取名称对应的 guard 配置,前面说了,包括 driver\provider
        $config = $this->getConfig($name);
        // 配置异常判断
        if (is_null($config)) {
            throw new InvalidArgumentException("Auth guard [{$name}] is not defined.");
        }
        // 如果有设置自定义的 guard 创建方法,则用自定义的方法创建
        // 自定义的方法通过类中的 extend 方法定义
        if (isset($this->customCreators[$config['driver']])) {
            return $this->callCustomCreator($name, $config);
        }
    
        $driverMethod = 'create'.ucfirst($config['driver']).'Driver';
        // 根据配置,判断是否有对应创建 guard 的方法,有则调用
        // 系统自带 createSessionDriver\createTokenDriver 两个创建 guard 方法 
        if (method_exists($this, $driverMethod)) {
            return $this->{$driverMethod}($name, $config);
        }
        // 抛出 guard driver 没有定义的异常
        throw new InvalidArgumentException("Auth guard driver [{$name}] is not defined.");
    }
    

    默认情况下,web guard 的 driver 为 session。所以是通过 createSessionDriver 方法将会返回一个 SessionGuard 实例。然后通过 SessionGuard 实例进行用户认证逻辑。
    这里最灵活的地方在于还提供了一个 extend 方法来自定义 guard。使得可以方便的扩展自己的用户认证。
    下面是用户认证的相关的类图。

    用户认证类图.png

    最后

    有问题,欢迎留言交流。

    参考文档

    https://laravel-china.org/docs/laravel/5.4/authentication/1239

    相关文章

      网友评论

          本文标题:Laravel 用户认证 源码学习

          本文链接:https://www.haomeiwen.com/subject/ssxudftx.html