资讯专栏INFORMATION COLUMN

Laravel核心解读--用户认证系统的实现细节

NicolasHe / 2930人阅读

摘要:通过装载看守器和用户提供器装载看守器和用户提供器用到的方法比较多,用文字描述不太清楚,我们通过注解这个过程中用到的方法来看具体的实现细节。

用户认证系统的实现细节

上一节我们介绍来Laravel Auth系统的基础知识,说了他的核心组件都有哪些构成,这一节我们会专注Laravel Auth系统的实现细节,主要关注Auth也就是AuthManager是如何装载认证用的看守器(Guard)和用户提供器(UserProvider)以及默认的用户注册和登录的实现细节,通过梳理这些实现细节我们也就能知道应该如何定制Auth认证来满足我们自己项目中用户认证的需求的。

通过AuthManager装载看守器和用户提供器

AuthManager装载看守器和用户提供器用到的方法比较多,用文字描述不太清楚,我们通过注解这个过程中用到的方法来看具体的实现细节。

namespace IlluminateAuth;

class AuthManager implements FactoryContract
{
    /**
     * 尝试从$guards属性中获取指定的Guard
     *
     * @param  string  $name
     * @return IlluminateContractsAuthGuard|IlluminateContractsAuthStatefulGuard
     */
    public function guard($name = null)
    {
        $name = $name ?: $this->getDefaultDriver();

        return isset($this->guards[$name])
                    ? $this->guards[$name]
                    : $this->guards[$name] = $this->resolve($name);
    }
    
    /**
     * 解析出给定name的Guard
     *
     * @param  string  $name
     * @return IlluminateContractsAuthGuard|IlluminateContractsAuthStatefulGuard
     *
     * @throws InvalidArgumentException
     */
    protected function resolve($name)
    {
        //获取Guard的配置
        //$config = ["driver" => "session", "provider" => "users"]
        $config = $this->getConfig($name);

        if (is_null($config)) {
            throw new InvalidArgumentException("Auth guard [{$name}] is not defined.");
        }
       //如果通过extend方法为guard定义了驱动器,这里去调用自定义的Guard驱动器
        if (isset($this->customCreators[$config["driver"]])) {
            return $this->callCustomCreator($name, $config);
        }
        //Laravel auth默认的配置这里是执行createSessionDriver
        $driverMethod = "create".ucfirst($config["driver"])."Driver";

        if (method_exists($this, $driverMethod)) {
            return $this->{$driverMethod}($name, $config);
        }

        throw new InvalidArgumentException("Auth guard driver [{$name}] is not defined.");
    }
    
    /**
     * 从config/auth.php中获取给定名称的Guard的配置
     *
     * @param  string  $name
     * @return array
     */
    "guards" => [
        "web" => [
            "driver" => "session",
            "provider" => "users",
        ],

        "api" => [
            "driver" => "token",
            "provider" => "users",
        ],
    ],
    protected function getConfig($name)
    {
        //"guards" => [
        //    "web" => [
        //        "driver" => "session",
        //        "provider" => "users",
        //    ],

        //    "api" => [
        //        "driver" => "token",
        //        "provider" => "users",
        //    ],
        //],
        // 根据Laravel默认的auth配置, 这个方法会获取key "web"对应的数组
        return $this->app["config"]["auth.guards.{$name}"];
    }
    
    /**
     * 调用自定义的Guard驱动器
     *
     * @param  string  $name
     * @param  array  $config
     * @return mixed
     */
    protected function callCustomCreator($name, array $config)
    {
        return $this->customCreators[$config["driver"]]($this->app, $name, $config);
    }
    
    /**
     * 注册一个自定义的闭包Guard 驱动器 到customCreators属性中
     *
     * @param  string  $driver
     * @param  Closure  $callback
     * @return $this
     */
    public function extend($driver, Closure $callback)
    {
        $this->customCreators[$driver] = $callback;

        return $this;
    }
    
    /**
     * 注册一个自定义的用户提供器创建器到 customProviderCreators属性中
     *
     * @param  string  $name
     * @param  Closure  $callback
     * @return $this
     */
    public function provider($name, Closure $callback)
    {
        $this->customProviderCreators[$name] = $callback;

        return $this;
    }
    
    /**
     * 创建基于session的认证看守器 SessionGuard
     *
     * @param  string  $name
     * @param  array  $config
     * @return IlluminateAuthSessionGuard
     */
    public function createSessionDriver($name, $config)
    {
        //$config["provider"] == "users"
        $provider = $this->createUserProvider($config["provider"] ?? null);

        $guard = new SessionGuard($name, $provider, $this->app["session.store"]);

        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"));
        }

        return $guard;
    }
    
    //创建Guard驱动依赖的用户提供器对象
    public function createUserProvider($provider = null)
    {
        if (is_null($config = $this->getProviderConfiguration($provider))) {
            return;
        }
        //如果通过Auth::provider方法注册了自定义的用户提供器creator闭包则去调用闭包获取用户提供器对象
        if (isset($this->customProviderCreators[$driver = ($config["driver"] ?? null)])) {
            return call_user_func(
                $this->customProviderCreators[$driver], $this->app, $config
            );
        }

        switch ($driver) {
            case "database":
                return $this->createDatabaseProvider($config);
            case "eloquent":
                //通过默认的auth配置这里会返回EloquentUserProvider对象,它实现了IlluminateContractsAuth 接口
                return $this->createEloquentProvider($config);
            default:
                throw new InvalidArgumentException(
                    "Authentication user provider [{$driver}] is not defined."
                );
        }
    }
    
    /**
     * 会通过__call去动态地调用AuthManager代理的Guard的用户认证相关方法
     * 根据默认配置,这里__call会去调用SessionGuard里的方法
     * @param  string  $method
     * @param  array  $parameters
     * @return mixed
     */
    public function __call($method, $parameters)
    {
        return $this->guard()->{$method}(...$parameters);
    }
}

注册用户

Laravel Auth系统中默认的注册路由如下:

$this->post("register", "AuthRegisterController@register");

所以用户注册的逻辑是由RegisterController的register方法来完成的

class RegisterController extends Controller
{
    //方法定义在IlluminateFoundationAuthRegisterUsers中
    public function register(Request $request)
    {
        $this->validator($request->all())->validate();

        event(new Registered($user = $this->create($request->all())));

        $this->guard()->login($user);

        return $this->registered($request, $user)
        
     }
     
    protected function validator(array $data)
    {
        return Validator::make($data, [
            "name" => "required|string|max:255",
            "email" => "required|string|email|max:255|unique:users",
            "password" => "required|string|min:6|confirmed",
        ]);
    }
    
    protected function create(array $data)
    {
        return User::create([
            "name" => $data["name"],
            "email" => $data["email"],
            "password" => bcrypt($data["password"]),
        ]);
    }
         
}

register的流程很简单,就是验证用户输入的数据没问题后将这些数据写入数据库生成用户,其中密码加密采用的是bcrypt算法,如果你需要改成常用的salt加密码明文做哈希的密码加密方法可以在create方法中对这部分逻辑进行更改,注册完用户后会调用SessionGuard的login方法把用户数据装载到应用中,注意这个login方法没有登录认证,只是把认证后的用户装载到应用中这样在应用里任何地方我们都能够通过Auth::user()来获取用户数据啦。

用户登录认证

Laravel Auth系统的登录路由如下

$this->post("login", "AuthLoginController@login");

我们看一下LoginController里的登录逻辑

class LoginController extends Controller
{
    /**
     * 处理登录请求
     */
    public function login(Request $request)
    {
        //验证登录字段
        $this->validateLogin($request);
        //防止恶意的多次登录尝试
        if ($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);
    }
    
    //尝试进行登录认证
    protected function attemptLogin(Request $request)
    {
        return $this->guard()->attempt(
            $this->credentials($request), $request->filled("remember")
        );
    }
    
    //获取登录用的字段值
    protected function credentials(Request $request)
    {
        return $request->only($this->username(), "password");
    }
}

可以看到,登录认证的逻辑是通过SessionGuardattempt方法来实现的,其实就是Auth::attempt(), 下面我们来看看attempt方法里的逻辑:

class SessionGuard implements StatefulGuard, SupportsBasicAuth
{
    public function attempt(array $credentials = [], $remember = false)
    {
        $this->fireAttemptEvent($credentials, $remember);

        $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
       //如果登录认证通过,通过login方法将用户对象装载到应用里去
        if ($this->hasValidCredentials($user, $credentials)) {
            $this->login($user, $remember);

            return true;
        }
        //登录失败的话,可以触发事件通知用户有可疑的登录尝试(需要自己定义listener来实现)
        $this->fireFailedEvent($user, $credentials);

        return false;
    }
    
    protected function hasValidCredentials($user, $credentials)
    {
        return ! is_null($user) && $this->provider->validateCredentials($user, $credentials);
    }
}

SessionGuardattempt方法首先通过用户提供器的retriveBycredentials方法通过用户名从用户表中查询出用户数据,认证用户信息是通过用户提供器的validateCredentials来实现的,所有用户提供器的实现类都会实现UserProvider契约(interface)中定义的方法,通过上面的分析我们知道默认的用户提供器是EloquentUserProvider

class EloquentUserProvider implements UserProvider
{
    从数据库中取出用户实例
    public function retrieveByCredentials(array $credentials)
    {
        if (empty($credentials) ||
           (count($credentials) === 1 &&
            array_key_exists("password", $credentials))) {
            return;
        }

        $query = $this->createModel()->newQuery();

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

        return $query->first();
    }
    
    //通过给定用户认证数据来验证用户
    public function validateCredentials(UserContract $user, array $credentials)
    {
        $plain = $credentials["password"];

        return $this->hasher->check($plain, $user->getAuthPassword());
    }
}

class BcryptHasher implements HasherContract
{
    //通过bcrypt算法计算给定value的散列值
    public function make($value, array $options = [])
    {
        $hash = password_hash($value, PASSWORD_BCRYPT, [
            "cost" => $this->cost($options),
        ]);

        if ($hash === false) {
            throw new RuntimeException("Bcrypt hashing not supported.");
        }

        return $hash;
    }
    
    //验证散列值是否给定明文值通过bcrypt算法计算得到的
    public function check($value, $hashedValue, array $options = [])
    {
        if (strlen($hashedValue) === 0) {
            return false;
        }

        return password_verify($value, $hashedValue);
    }
}

用户密码的验证是通过EloquentUserProvider依赖的hasher哈希器来完成的,Laravel认证系统默认采用bcrypt算法来加密用户提供的明文密码然后存储到用户表里的,验证时haser哈希器的check方法会通过PHP内建方法password_verify来验证明文密码是否是存储的密文密码的原值。

用户认证系统的主要细节梳理完后我们就知道如何定义我们自己的看守器(Guard)或用户提供器(UserProvider)了,首先他们必须实现各自遵守的契约里的方法才能够无缝接入到Laravel的Auth系统中,然后还需要将自己定义的Guard或Provider通过Auth::extendAuth::provider方法注册返回Guard或者Provider实例的闭包到Laravel中去,Guard和UserProvider的自定义不是必须成套的,我们可以多带带自定义Guard仍使用默认的EloquentUserProvider,或者让默认的SessionGuard使用自定义的UserProvider。

下一节我会给出一个我们以前项目开发中用到的一个案例来更好地讲解应该如何对Laravel Auth系统进行扩展。

本文已经收录在系列文章Laravel源码学习里,欢迎访问阅读。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/28951.html

相关文章

  • Laravel核心解读 -- 用户认证系统(基础介绍)

    摘要:系统的核心是由的认证组件的看守器和提供器组成。使用的认证系统,几乎所有东西都已经为你配置好了。其配置文件位于,其中包含了用于调整认证服务行为的注释清晰的选项配置。 用户认证系统(基础介绍) 使用过Laravel的开发者都知道,Laravel自带了一个认证系统来提供基本的用户注册、登录、认证、找回密码,如果Auth系统里提供的基础功能不满足需求还可以很方便的在这些基础功能上进行扩展。这篇...

    RebeccaZhong 评论0 收藏0
  • Laravel核心解读 -- 扩展用户认证系统

    摘要:扩展用户认证系统上一节我们介绍了系统实现的一些细节知道了是如何应用看守器和用户提供器来进行用户认证的,但是针对我们自己开发的项目或多或少地我们都会需要在自带的看守器和用户提供器基础之上做一些定制化来适应项目,本节我会列举一个在做项目时遇到的 扩展用户认证系统 上一节我们介绍了Laravel Auth系统实现的一些细节知道了Laravel是如何应用看守器和用户提供器来进行用户认证的,但是...

    王伟廷 评论0 收藏0
  • Laravel核心解读--完结篇

    摘要:过去一年时间写了多篇文章来探讨了我认为的框架最核心部分的设计思路代码实现。为了大家阅读方便,我把这些源码学习的文章汇总到这里。数据库算法和数据结构这些都是编程的内功,只有内功深厚了才能解决遇到的复杂问题。 过去一年时间写了20多篇文章来探讨了我认为的Larave框架最核心部分的设计思路、代码实现。通过更新文章自己在软件设计、文字表达方面都有所提高,在刚开始决定写Laravel源码分析地...

    laoLiueizo 评论0 收藏0
  • Laravel核心解读--Contracts契约

    摘要:的契约是一组定义框架提供的核心服务的接口,例如我们在介绍用户认证的章节中到的用户看守器契约和用户提供器契约以及框架自带的模型所实现的契约。接口与团队开发当你的团队在开发大型应用时,不同的部分有着不同的开发速度。 Contracts Laravel 的契约是一组定义框架提供的核心服务的接口, 例如我们在介绍用户认证的章节中到的用户看守器契约IllumninateContractsAuth...

    Prasanta 评论0 收藏0
  • Lumen用户认证JWT,源码解读

    摘要:如何做用户认证根据文档描述,提供用户认证的接口,他的核心是看守器和提供器,看守器定义怎么认证用户,提供器定义怎么检索用户。 最近的一个PHP项目,上一个项目是采用ThinkPHP来弄的,因为很早就听说过Laravel的大名,所以进了Laravel的官网,意外发现了Lumen,正好我项目是提供API的,所以选择了Lumen,因为是Laravel的精简版,看了几天的Laravel文档,也总...

    AZmake 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<