這篇文章主要給大家介紹了關於通過修改Laravel Auth使用salt和password進行認證使用者的相關資料,文中通過範例程式碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面來一起看看吧。
前言
本文主要給大家介紹了通過修改Laravel Auth用salt和password進行認證使用者的相關內容,分享出來供大家參考學習,下面話不多說了,來一起看看詳細的介紹:
Laraval內建的使用者認證系統Auth非常強大易用,不過在Laravel的使用者認證系統中使用者註冊、登入、找回密碼這些模組中用到密碼加密和認證演算法時使用的都是bcrypt,而很多之前做的項目使用者表裡都是採用儲存salt + password加密字串的方式來記錄使用者的密碼的,這就給使用Laravel架構來重構之前的項目帶來了很大的阻力,不過最近自己通過在網上找資料、看社區論壇、看源碼等方式完成了對Laravel Auth的修改,在這裡分享出來希望能對其他人有所協助。 開篇之前需要再說明下如果是新項目應用Laravel架構,那麼不需要對Auth進行任何修改,預設的bcrypt密碼編譯演算法是比salt + password更安全更高效的密碼編譯演算法。
修改使用者註冊
首先,在laravel 裡啟用驗證是用的artisan命令
php artisan make:auth
執行完命令後在routes檔案(位置:app/Http/routes.php)會多一條靜態方法調用
Route::auth();
這個Route是Laravel的一個Facade (位於Illuminate\Support\Facades\Route), 調用的auth方法定義在Illuminate\Routing\Router類裡, 如下可以看到auth方法裡就是定義了一些Auth相關的路由規則
/** * 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');}
通過路由規則可以看到註冊時請求的控制器方法是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裡的使用者輸入資料進行驗證,你只需要在AuthController的validator方法裡定義自己的每個輸入欄位的驗證規則就可以
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方法來產生新使用者,然後拿著新使用者的資料去登入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 ]);}
修改使用者登入
修改登入前我們需要先通過路由規則看一下登入請求的具體控制器和方法,在上文提到的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發現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 方法是如何?的:
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是用傳遞進來的欄位從資料庫中取出使用者資料的,validateCredentials是用來驗證密碼是否正確的實際過程。
這裡需要注意的是$this->provider
這個provider是一個實現了\Illuminate\Contracts\Auth\UserProvider類的provider, 我們看到目錄Illuminate\Auth下面有兩個UserProvider的實現,分別為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方法的原始碼)
接下來我們繼續查看EloquentUserProvider中retrieveByCredentials和validateCredentials方法的實現:
/** * 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用除了密碼以外的欄位從資料庫使用者表裡取出使用者記錄,比如用email查詢出使用者記錄,然後validateCredentials方法就是通過$this->haser->check
來將輸入的密碼和雜湊的密碼進行比較來驗證密碼是否正確。
好了, 看到這裡就很明顯了, 我們需要改成自己的密碼驗證就是自己實現一下validateCredentials就可以了, 修改$this->hasher->check為我們自己的密碼驗證規則就可以了。
首先我們修改$user->getAuthPassword()
把資料庫中使用者表的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介面的實現,放到自訂的目錄中:
建立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方法的具體代碼實現在AuthManager檔案裡
public function provider($name, Closure $callback){ $this->customProviderCreators[$name] = $callback; return $this;}
閉包返回了AdminEloquentUserProvider對象供Laravel Auth使用,好了做完這些修改後Laravel的Auth在做使用者登入驗證的時候採用的就是自訂的salt + password的方式了。
修改重設密碼
Laravel 的重設密碼的工作流程是:
第一步需要配置Laravel的email功能,此外還需要在資料庫中建立一個新表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方法裡,程式會先對使用者提交的資料做再一次的認證,然後把密碼和使用者執行個體傳遞給傳遞進來的閉包,在閉包調用裡完成了將新密碼更新到使用者表的操作, 在閉包裡程式調用了的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