通過修改Laravel Auth使用salt和password進行認證用戶詳解
前言
本文主要給大家介紹了通過修改Laravel Auth用salt和password進行認證用戶的相關內容,分享出來供大家參考學習,下面話不多說了,來一起看看詳細的介紹:
Laraval自帶的用戶認證系統(tǒng)Auth非常強大易用,不過在Laravel的用戶認證系統(tǒng)中用戶注冊、登錄、找回密碼這些模塊中用到密碼加密和認證算法時使用的都是bcrypt,而很多之前做的項目用戶表里都是采用存儲salt + password加密字符串的方式來記錄用戶的密碼的,這就給使用Laravel框架來重構之前的項目帶來了很大的阻力,不過最近自己通過在網(wǎng)上找資料、看社區(qū)論壇、看源碼等方式完成了對Laravel Auth的修改,在這里分享出來希望能對其他人有所幫助。 開篇之前需要再說明下如果是新項目應用Laravel框架,那么不需要對Auth進行任何修改,默認的bcrypt加密算法是比salt + password更安全更高效的加密算法。
修改用戶注冊
首先,在laravel 里啟用驗證是用的artisan命令
php artisan make:auth
執(zhí)行完命令后在routes文件(位置:app/Http/routes.php)會多一條靜態(tài)方法調用
Route::auth();
這個Route是Laravel的一個Facade (位于Illuminate\Support\Facades\Route), 調用的auth方法定義在Illuminate\Routing\Router類里, 如下可以看到auth方法里就是定義了一些Auth相關的路由規(guī)則
/**
* Register the typical authentication routes for an application.
*
* @return void
*/
public function auth()
{
// Authentication Routes...
$this->get('login', 'Auth\AuthController@showLoginForm');
$this->post('login', 'Auth\AuthController@login');
$this->get('logout', 'Auth\AuthController@logout');
// Registration Routes...
$this->get('register', 'Auth\AuthController@showRegistrationForm');
$this->post('register', 'Auth\AuthController@register');
// Password Reset Routes...
$this->get('password/reset/{token?}', 'Auth\PasswordController@showResetForm');
$this->post('password/email', 'Auth\PasswordController@sendResetLinkEmail');
$this->post('password/reset', 'Auth\PasswordController@reset');
}
通過路由規(guī)則可以看到注冊時請求的控制器方法是AuthController的register方法, 該方法定義在\Illuminate\Foundation\Auth\RegistersUsers這個traits里,AuthController在類定義里引入了這個traits.
/**
* Handle a registration request for the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function register(Request $request)
{
$validator = $this->validator($request->all());
if ($validator->fails()) {
$this->throwValidationException(
$request, $validator
);
}
Auth::guard($this->getGuard())->login($this->create($request->all()));
return redirect($this->redirectPath());
}
在register方法里首先會對request里的用戶輸入數(shù)據(jù)進行驗證,你只需要在AuthController的validator方法里定義自己的每個輸入字段的驗證規(guī)則就可以
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:user',
'password' => 'required|size:40|confirmed',
]);
}
接著往下看驗證通過后,Laravel會掉用AuthController的create方法來生成新用戶,然后拿著新用戶的數(shù)據(jù)去登錄Auth::guard($this->getGuard())->login($this->create($request->all()));
所以我們要自定義用戶注冊時生成用戶密碼的加密方式只需要修改AuthController的create方法即可。
比如:
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return User
*/
protected function create(array $data)
{
$salt = Str::random(6);
return User::create([
'nickname' => $data['name'],
'email' => $data['email'],
'password' => sha1($salt . $data['password']),
'register_time' => time(),
'register_ip' => ip2long(request()->ip()),
'salt' => $salt
]);
}
修改用戶登錄
修改登錄前我們需要先通過路由規(guī)則看一下登錄請求的具體控制器和方法,在上文提到的auth方法定義里可以看到
$this->get('login', 'Auth\AuthController@showLoginForm');
$this->post('login', 'Auth\AuthController@login');
$this->get('logout', 'Auth\AuthController@logout');
驗證登錄的操作是在\App\Http\Controllers\Auth\AuthController類的login方法里。打開AuthController發(fā)現(xiàn)Auth相關的方法都是通過性狀(traits)引入到類內的,在類內use 要引入的traits,在編譯時PHP就會把traits里的代碼copy到類中,這是PHP5.5引入的特性具體適用場景和用途這里不細講。 所以AuthController@login方法實際是定義在
\Illuminate\Foundation\Auth\AuthenticatesUsers這個traits里的
/**
* Handle a login request to the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function login(Request $request)
{
$this->validateLogin($request);
$throttles = $this->isUsingThrottlesLoginsTrait();
if ($throttles && $lockedOut = $this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
$credentials = $this->getCredentials($request);
if (Auth::guard($this->getGuard())->attempt($credentials, $request->has('remember'))) {
return $this->handleUserWasAuthenticated($request, $throttles);
}
if ($throttles && ! $lockedOut) {
$this->incrementLoginAttempts($request);
}
return $this->sendFailedLoginResponse($request);
}
登錄驗證的主要操作是在Auth::guard($this->getGuard())->attempt($credentials, $request->has('remember'));這個方法調用中來進行的,Auth::guard($this->getGuard()) 獲取到的是\Illuminate\Auth\SessionGuard (具體如何獲取的看Auth這個Facade \Illuminate\Auth\AuthManager里的源碼)
看一下SessionGuard里attempt 方法是如何實現(xiàn)的:
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;
}
if ($login) {
$this->fireFailedEvent($user, $credentials);
}
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);
}
retrieveByCredentials是用傳遞進來的字段從數(shù)據(jù)庫中取出用戶數(shù)據(jù)的,validateCredentials是用來驗證密碼是否正確的實際過程。
這里需要注意的是$this->provider這個provider是一個實現(xiàn)了\Illuminate\Contracts\Auth\UserProvider類的provider, 我們看到目錄Illuminate\Auth下面有兩個UserProvider的實現(xiàn),分別為DatabaseUserProvider和EloquentUserProvider, 但是我們驗證密碼的時候是通過那個來驗證的呢,看一下auth的配置文件
'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => App\User::class, //這個是driver用的Model ], ],
這里配置的是driver => eloquent , 那么就是通過EloquentUserProvider的retrieveByCredentials來驗證的, 這個EloquentUserProvider 是在SessionGuard實例化時被注入進來的, (具體是怎么通過讀取auth配置文件, 實例化相應的provider注入到SessionGuard里的請查閱\Illuminate\Auth\AuthManager 里createSessionDriver方法的源代碼)
接下來我們繼續(xù)查看EloquentUserProvider中retrieveByCredentials和validateCredentials方法的實現(xiàn):
/**
* Retrieve a user by the given credentials.
*
* @param array $credentials
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByCredentials(array $credentials)
{
if (empty($credentials)) {
return;
}
$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用除了密碼以外的字段從數(shù)據(jù)庫用戶表里取出用戶記錄,比如用email查詢出用戶記錄,然后validateCredentials方法就是通過$this->haser->check來將輸入的密碼和哈希的密碼進行比較來驗證密碼是否正確。
好了, 看到這里就很明顯了, 我們需要改成自己的密碼驗證就是自己實現(xiàn)一下validateCredentials就可以了, 修改$this->hasher->check為我們自己的密碼驗證規(guī)則就可以了。
首先我們修改$user->getAuthPassword()把數(shù)據(jù)庫中用戶表的salt和password傳遞到validateCredentials中
修改App\User.php 添加如下代碼
/** * The table associated to this model */ protected $table = 'user';//用戶表名不是laravel約定的這里要指定一下
/**
* 禁用Laravel自動管理timestamp列
*/
public $timestamps = false;
/**
* 覆蓋Laravel中默認的getAuthPassword方法, 返回用戶的password和salt字段
* @return type
*/
public function getAuthPassword()
{
return ['password' => $this->attributes['password'], 'salt' => $this->attributes['salt']];
}
然后我們在建立一個自己的UserProvider接口的實現(xiàn),放到自定義的目錄中:
新建app/Foundation/Auth/AdminEloquentUserProvider.php
namespace App\Foundation\Auth;
use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Str;
class AdminEloquentUserProvider extends EloquentUserProvider
{
/**
* Validate a user against the given credentials.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param array $credentials
*/
public function validateCredentials(Authenticatable $user, array $credentials) {
$plain = $credentials['password'];
$authPassword = $user->getAuthPassword();
return sha1($authPassword['salt'] . $plain) == $authPassword['password'];
}
}
最后我們修改auth配置文件讓Laravel在做Auth驗證時使用我們剛定義的Provider,
修改config/auth.php:
'providers' => [ 'users' => [ 'driver' => 'admin-eloquent', 'model' => App\User::class, ] ]
修改app/Provider/AuthServiceProvider.php
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
\Auth::provider('admin-eloquent', function ($app, $config) {
return New \App\Foundation\Auth\AdminEloquentUserProvider($app['hash'], $config['model']);
});
}
Auth::provider方法是用來注冊Provider構造器的,這個構造器是一個Closure,provider方法的具體代碼實現(xiàn)在AuthManager文件里
public function provider($name, Closure $callback)
{
$this->customProviderCreators[$name] = $callback;
return $this;
}
閉包返回了AdminEloquentUserProvider對象供Laravel Auth使用,好了做完這些修改后Laravel的Auth在做用戶登錄驗證的時候采用的就是自定義的salt + password的方式了。
修改重置密碼
Laravel 的重置密碼的工作流程是:
- 向需要重置密碼的用戶的郵箱發(fā)送一封帶有重置密碼鏈接的郵件,鏈接中會包含用戶的email地址和token。
- 用戶點擊郵件中的鏈接在重置密碼頁面輸入新的密碼,Laravel通過驗證email和token確認用戶就是發(fā)起重置密碼請求的用戶后將新密碼更新到用戶在數(shù)據(jù)表的記錄里。
第一步需要配置Laravel的email功能,此外還需要在數(shù)據(jù)庫中創(chuàng)建一個新表password_resets來存儲用戶的email和對應的token
CREATE TABLE `password_resets` ( `email` varchar(255) COLLATE utf8_unicode_ci NOT NULL, `token` varchar(255) COLLATE utf8_unicode_ci NOT NULL, `created_at` timestamp NOT NULL, KEY `password_resets_email_index` (`email`), KEY `password_resets_token_index` (`token`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
通過重置密碼表單的提交地址可以看到,表單把新的密碼用post提交給了/password/reset,我們先來看一下auth相關的路由,確定/password/reset對應的控制器方法。
$this->post('password/reset', 'Auth\PasswordController@reset');
可以看到對應的控制器方法是\App\Http\Controllers\Auth\PasswordController類的reset方法,這個方法實際是定義在\Illuminate\Foundation\Auth\ResetsPasswords 這個traits里,PasswordController引入了這個traits
/**
* Reset the given user's password.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function reset(Request $request)
{
$this->validate(
$request,
$this->getResetValidationRules(),
$this->getResetValidationMessages(),
$this->getResetValidationCustomAttributes()
);
$credentials = $this->getResetCredentials($request);
$broker = $this->getBroker();
$response = Password::broker($broker)->reset($credentials, function ($user, $password) {
$this->resetPassword($user, $password);
});
switch ($response) {
case Password::PASSWORD_RESET:
return $this->getResetSuccessResponse($response);
default:
return $this->getResetFailureResponse($request, $response);
}
}
方法開頭先通過validator對輸入進行驗證,接下來在程序里傳遞把新密碼和一個閉包對象傳遞給Password::broker($broker)->reset();方法,這個方法定義在\Illuminate\Auth\Passwords\PasswordBroker類里.
/**
* Reset the password for the given token.
*
* @param array $credentials
* @param \Closure $callback
* @return mixed
*/
public function reset(array $credentials, Closure $callback)
{
// If the responses from the validate method is not a user instance, we will
// assume that it is a redirect and simply return it from this method and
// the user is properly redirected having an error message on the post.
$user = $this->validateReset($credentials);
if (! $user instanceof CanResetPasswordContract) {
return $user;
}
$pass = $credentials['password'];
// Once we have called this callback, we will remove this token row from the
// table and return the response from this callback so the user gets sent
// to the destination given by the developers from the callback return.
call_user_func($callback, $user, $pass);
$this->tokens->delete($credentials['token']);
return static::PASSWORD_RESET;
}
在PasswordBroker的reset方法里,程序會先對用戶提交的數(shù)據(jù)做再一次的認證,然后把密碼和用戶實例傳遞給傳遞進來的閉包,在閉包調用里完成了將新密碼更新到用戶表的操作, 在閉包里程序調用了的PasswrodController類的resetPassword方法
function ($user, $password) {
$this->resetPassword($user, $password);
});
PasswrodController類resetPassword方法的定義
protected function resetPassword($user, $password)
{
$user->forceFill([
'password' => bcrypt($password),
'remember_token' => Str::random(60),
])->save();
Auth::guard($this->getGuard())->login($user);
}
在這個方法里Laravel 用的是bcrypt 加密了密碼, 那么要改成我們需要的salt + password的方式,我們在PasswordController類里重寫resetPassword方法覆蓋掉traits里的該方法就可以了。
/**
* 覆蓋ResetsPasswords traits里的resetPassword方法,改為用sha1(salt + password)的加密方式
* Reset the given user's password.
*
* @param \Illuminate\Contracts\Auth\CanResetPassword $user
* @param string $password
* @return void
*/
protected function resetPassword($user, $password)
{
$salt = Str::random(6);
$user->forceFill([
'password' => sha1($salt . $password),
'salt' => $salt,
'remember_token' => Str::random(60),
])->save();
\Auth::guard($this->getGuard())->login($user);
}
結語
到這里對Laravel Auth的自定義就完成了,注冊、登錄和重置密碼都改成了sha1(salt + password)的密碼加密方式, 所有自定義代碼都是通過定義Laravel相關類的子類和重寫方法來完成沒有修改Laravel的源碼,這樣既保持了良好的可擴展性也保證了項目能夠自由遷移。
注:使用的Laravel版本為5.2
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關文章
在Mac OS的PHP環(huán)境下安裝配置MemCache的全過程解析
這篇文章主要介紹了在Mac OS的PHP環(huán)境下安裝配置MemCache的全過程解析,MemCache是一套分布式的高速緩存系統(tǒng),需要的朋友可以參考下2016-02-02
Yii2學習筆記之漢化yii設置表單的描述(屬性標簽attributeLabels)
這篇文章主要介紹了Yii2學習筆記之漢化yii設置表單的描述(屬性標簽attributeLabels),非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-02-02
為PHP模塊添加SQL SERVER2012數(shù)據(jù)庫的步驟詳解
這篇文章主要介紹了為PHP模塊添加SQL SERVER2012數(shù)據(jù)庫,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04
Laravel給生產(chǎn)環(huán)境添加監(jiān)聽事件(SQL日志監(jiān)聽)
這篇文章主要給大家介紹了關于Laravel給生產(chǎn)環(huán)境添加監(jiān)聽事件(SQL日志監(jiān)聽)的相關資料,文中介紹的非常詳細,對大家具有一定的參考學習價值,需要的朋友們下面來一起看看吧。2017-06-06

