本文原連結來自我的部落格,地址:Laravel 5.2 Auth 認證解析以及改用 salt+passwrod 加密驗證
Larval 5.2的預設Auth登陸傳入郵件和使用者密碼到attempt
方法來認證,通過email
的值獲取,如果使用者被找到,經雜湊運算後儲存在資料中的password
將會和傳遞過來的經雜湊運算處理的passwrod
值進行比較。如果兩個經雜湊運算的密碼相匹配那麼將會為這個使用者開啟一個認證Session。
但是往往我們一些系統中的密碼是通過salt+password的方式來做密碼認證的,或者一些老的系統是通過salt+passwrod來認證的,現在重構遷移到Laravel框架中,那麼密碼認證如何不用預設的passwrod的方式而用salt+password的方式認證?
要解決問題,我們最好還是先要弄明白根源,順藤摸瓜
首先看一下Laravel預設如何做密碼驗證的,看看Auth::guard($this->getGuard())->attempt($credentials)
方法做了什麼:
Illuminate/Contracts/Auth/StatefulGuard.php
namespace Illuminate\Contracts\Auth;
interface StatefulGuard extends Guard
{
/**
* Attempt to authenticate a user using the given credentials.
*
* @param array $credentials
* @param bool $remember
* @param bool $login
* @return bool
*/
public function attempt(array $credentials = [], $remember = false, $login = true);
......
上面程式碼看到attempt
是StatefulGuard
介面中的方法,第一個引數為需要認證的欄位,第二個引數為是否記住登陸,第三個引數是否登陸,繼續往下看attempt
在SessionGuard中是如何實現的
illuminate/auth/SessionGuard.php
class SessionGuard implements StatefulGuard, SupportsBasicAuth
{
use GuardHelpers;
......
/**
* Attempt to authenticate a user using the given credentials.
*
* @param array $credentials
* @param bool $remember
* @param bool $login
* @return bool
*/
public function attempt(array $credentials = [], $remember = false, $login = true)
{
$this->fireAttemptEvent($credentials, $remember, $login);
$this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials);
if ($this->hasValidCredentials($user, $credentials)) {
if ($login) {
$this->login($user, $remember);
}
return true;
}
return false;
}
/**
* Determine if the user matches the credentials.
*
* @param mixed $user
* @param array $credentials
* @return bool
*/
protected function hasValidCredentials($user, $credentials)
{
return ! is_null($user) && $this->provider->validateCredentials($user, $credentials);
}
.......
}
看到通過 $this->provider->retrieveByCredentials($credentials);
和$this->provider->validateCredentials($user, $credentials);
來實現驗證,retrieveByCredentials
是用來驗證傳遞的欄位查詢使用者記錄是否存在,validateCredentials
才是通過使用者記錄中密碼和傳入的密碼做驗證的實際過程。
這裡需要注意的是$this->provider
,這個provider
其實是實現了一個Illuminate\Contracts\Auth\UserProvider
的Provider
,我們看到Illuminate/Contracts/Auth
下面有兩個UserProvider
的實現,分別為DatabaseUserProvider.php
和EloquentUserProvider.php
。但是我們驗證密碼的時候是通過哪個來驗證的是在怎麼決定的?
config/auth.php
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class, //這是User Model
],
],
這裡我配置了'driver' => 'eloquent'
,那麼就是通過EloquentUserProvider.php中的retrieveByCredentials
來驗證的了,我們繼續看看它都幹了啥
illuminate/auth/EloquentUserProvider.php
class EloquentUserProvider implements UserProvider
{
......
/**
* Retrieve a user by the given credentials.
*
* @param array $credentials
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByCredentials(array $credentials)
{
// First we will add each credential element to the query as a where clause.
// Then we can execute the query and, if we found a user, return it in a
// Eloquent User "model" that will be utilized by the Guard instances.
$query = $this->createModel()->newQuery();
foreach ($credentials as $key => $value) {
if (! Str::contains($key, 'password')) {
$query->where($key, $value);
}
}
return $query->first();
}
/**
* Validate a user against the given credentials.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param array $credentials
* @return bool
*/
public function validateCredentials(UserContract $user, array $credentials)
{
$plain = $credentials['password'];
return $this->hasher->check($plain, $user->getAuthPassword());
}
......
}
上面兩個方法 retrieveByCredentials
用除了密碼以外的驗證欄位檢視記錄是否存在,比如用email來查詢使用者記錄是否存在, 然後 validateCredentials
方法就是通過 $this->hasher->check
來將輸入的密碼和雜湊的密碼比較來驗證密碼是否正確,$plain
是提交過來的為加密密碼字串,$user->getAuthPassword()
是資料庫記錄存放的加密密碼字串。
好了,看到這裡就很明顯了,我們需要改成我們自己的密碼驗證不就是自己實現一下validateCredentials
方法就可以了嗎,改變 $this->hasher->check
為我們自己的密碼驗證就可以了,開始搞吧!
- 首先我們來實現
$user->getAuthPassword();
把資料庫中使用者表的salt
和password
傳遞到validateCredentials
中來:
修改 App\Models\User.php 新增如下程式碼
public function getAuthPassword()
{
return ['password' => $this->attributes['password'], 'salt' => $this->attributes['salt']];
}
- 然後我們建立一個自己的
UserProvider.php
的實現,你可以放到任何地方,我放到自定義目錄中:
新建 app/Foundation/Auth/RyanEloquentUserProvider.php
<?php namespace App\Foundation\Auth;
use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Str;
class RyanEloquentUserProvider extends EloquentUserProvider
{
/**
* Validate a user against the given credentials.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param array $credentials
* @return bool
*/
public function validateCredentials(Authenticatable $user, array $credentials)
{
$plain = $credentials['password'];
$authPassword = $user->getAuthPassword();
return sha1($authPassword['salt'] . sha1($authPassword['salt'] . sha1($plain))) == $authPassword['password'];
}
我這裡通過$user->getAuthPassword();
傳遞過來了使用者記錄的salt
和password
,然後將認證提交的密碼$plain
和salt
進行加密,如果加密結果和使用者資料庫中記錄的密碼字串匹配那麼認證就通過了, 當然加密的演算法完全是自定義的。
- 最後我們將User Providers換成我們自己的
RyanEloquentUserProvider
修改 app/Providers/AuthServiceProvider.php
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
\Auth::provider('ryan-eloquent', function ($app, $config) {
return new RyanEloquentUserProvider($this->app['hash'], $config['model']);
});
}
修改 config/auth.php
'providers' => [
'users' => [
'driver' => 'ryan-eloquent',
'model' => App\Models\User::class,
],
],
好了,再試試可以用過salt+passwrod的方式密碼認證了!
轉載請註明: 轉載自Ryan是菜鳥 | LNMP技術棧筆記
如果覺得本篇文章對您十分有益,何不 打賞一下