在這篇文章中,我們分析一下 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,那麼就會呼叫ensureRememberTokenIsSet
和queueRecallerCookie
方法。
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 協議》,轉載必須註明作者和本文連結