Laravel 中 “记住我” 原理分析

在这篇文章中,我们分析一下 Laravel 中的“记住我” 功能是怎样实现的。

在 Laravel 为我们提供的默认登录中,为我们提供了“记住我”的选项,那么这一逻辑是怎样实现的呢?

登录

首先在 LoginController中,引入了 Illuminate\Foundation\Auth\AuthenticatesUsers 这个 Trait。在这个 Trait 中有个 login 方法:

    public function login(Request $request)
    {
        $this->validateLogin($request);

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

在这其中,登录判断在$this->attemptLogin($request)方法中:

    protected function attemptLogin(Request $request)
    {
        return $this->guard()->attempt(
            $this->credentials($request), $request->filled('remember')
        );
    }

SessionGuard

在常规的 web 登录中,我们使用 Illuminate\Auth\SessionGuard 来处理登录。

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

        $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);

        if ($this->hasValidCredentials($user, $credentials)) {
            $this->login($user, $remember);

            return true;
        }

        $this->fireFailedEvent($user, $credentials);

        return false;
    }

    public function login(AuthenticatableContract $user, $remember = false)
    {
        $this->updateSession($user->getAuthIdentifier());

        if ($remember) {
            $this->ensureRememberTokenIsSet($user);

            $this->queueRecallerCookie($user);
        }

        $this->fireLoginEvent($user, $remember);

        $this->setUser($user);
    }

    protected function ensureRememberTokenIsSet(AuthenticatableContract $user)
    {
        if (empty($user->getRememberToken())) {
            $this->cycleRememberToken($user);
        }
    }

    protected function queueRecallerCookie(AuthenticatableContract $user)
    {
        $this->getCookieJar()->queue($this->createRecaller(
            $user->getAuthIdentifier().'|'.$user->getRememberToken().'|'.$user->getAuthPassword()
        ));
    }

    protected function createRecaller($value)
    {
        return $this->getCookieJar()->forever($this->getRecallerName(), $value);
    }

从上面的代码中我们看到在attempt方法中,我们调用了login方法。在login方法中,如果$remember为true,那么就会调用ensureRememberTokenIsSetqueueRecallerCookie方法。

ensureRememberTokenIsSet方法的作用很简单,如果 users 表中的 remember_token 为空,那么生成一个60位长度的随机字符串,然后保存到 remember_token 字段中。

而在queueRecallerCookie方法中,首先会创建一个Recaller,其返回值为一个Cookie对象,如下:

Cookie {#266 ▼
  #name: "remember_web_59ba36addc2b2f9401580f014c7f58ea4e30989d"
  #value: "1|GI9K0OMvKvPFeWBjA3R8Kkyt7z0DB3wseJj0Pf87d4dAk5TNoDDkS8DNrjzZ|$2y$10$DXU1oUnRz.nzSqWHvuWZKuMTb4I9C46GIx6CYnkwtJNlHf.V4zu1C"
  #domain: null
  #expire: 1723619737
  #path: "/"
  #secure: false
  #httpOnly: true
  -raw: false
  -sameSite: null
  -secureDefault: false
}

其中 value 由两个|隔开,第一部分可以表示user_id,第二部分是 users 表中的 remember_token 值,第三部分是用户的密码。在经过 App\Http\Middleware\EncryptCookies的处理下返回加密后的cookie,其 key 为 上面 Cookie 中的 name 字段。

检查用户是否登录

那么我们怎样通过这个 cookie 来检查用户已经登录了呢?同样的在 SessionGuard 中有一个 user方法:

    public function user()
    {
        if ($this->loggedOut) {
            return;
        }

        if (! is_null($this->user)) {
            return $this->user;
        }

        $id = $this->session->get($this->getName());

        if (! is_null($id) && $this->user = $this->provider->retrieveById($id)) {
            $this->fireAuthenticatedEvent($this->user);
        }

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

用户在请求的过程中会判断cookie中是否有remember_web_xxxx 的key,如果有那么就会尝试解析是否能够获得用户,如果能,那么证明用户已登录,否则登录失败。

本作品采用《CC 协议》,转载必须注明作者和本文链接
There's nothing wrong with having a little fun.
本帖由系统于 4年前 自动加精
Epona
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 3

如何设置“记住我”cookie的时限,我看了教程,但是会出现错误,每个账户登录,无论是否选中“记住我”都会生成cookie

4年前 评论
Epona

@SZL_ Cookie是都会生成的,但是点了记住我之后,才会生成这个remember_web_xxxx键值

4年前 评论
SZL_ 4年前
Epona (作者) (楼主) 4年前
SZL_ 4年前
Epona (作者) (楼主) 4年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!