《PHP實(shí)戰(zhàn):通過修改Laravel Auth使用salt和password進(jìn)行認(rèn)證用戶詳解》要點(diǎn):
本文介紹了PHP實(shí)戰(zhàn):通過修改Laravel Auth使用salt和password進(jìn)行認(rèn)證用戶詳解,希望對您有用。如果有疑問,可以聯(lián)系我們。
前言
PHP編程
本文主要給大家介紹了通過修改Laravel Auth用salt和password進(jìn)行認(rèn)證用戶的相關(guān)內(nèi)容,分享出來供大家參考學(xué)習(xí),下面話不多說了,來一起看看詳細(xì)的介紹:PHP編程
Laraval自帶的用戶認(rèn)證系統(tǒng)Auth非常強(qiáng)大易用,不過在Laravel的用戶認(rèn)證系統(tǒng)中用戶注冊、登錄、找回密碼這些模塊中用到密碼加密和認(rèn)證算法時(shí)使用的都是bcrypt,而很多之前做的項(xiàng)目用戶表里都是采用存儲salt + password加密字符串的方式來記錄用戶的密碼的,這就給使用Laravel框架來重構(gòu)之前的項(xiàng)目帶來了很大的阻力,不過最近自己通過在網(wǎng)上找資料、看社區(qū)論壇、看源碼等方式完成了對Laravel Auth的修改,在這里分享出來希望能對其他人有所幫助. 開篇之前需要再說明下如果是新項(xiàng)目應(yīng)用Laravel框架,那么不需要對Auth進(jìn)行任何修改,默認(rèn)的bcrypt加密算法是比salt + password更安全更高效的加密算法.PHP編程
修改用戶注冊
PHP編程
首先,在laravel 里啟用驗(yàn)證是用的artisan命令PHP編程
php artisan make:auth
執(zhí)行完命令后在routes文件(位置:app/Http/routes.php)會(huì)多一條靜態(tài)方法調(diào)用PHP編程
Route::auth();
這個(gè)Route是Laravel的一個(gè)Facade (位于Illuminate\Support\Facades\Route), 調(diào)用的auth方法定義在Illuminate\Routing\Router類里, 如下可以看到auth方法里就是定義了一些Auth相關(guān)的路由規(guī)則PHP編程
/** * 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ī)則可以看到注冊時(shí)請求的控制器方法是AuthController的register方法, 該方法定義在\Illuminate\Foundation\Auth\RegistersUsers這個(gè)traits里,AuthController在類定義里引入了這個(gè)traits.PHP編程
/** * 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方法里首先會(huì)對request里的用戶輸入數(shù)據(jù)進(jìn)行驗(yàn)證,你只需要在AuthController的validator方法里定義自己的每個(gè)輸入字段的驗(yàn)證規(guī)則就可以PHP編程
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', ]); }
接著往下看驗(yàn)證通過后,Laravel會(huì)掉用AuthController的create方法來生成新用戶,然后拿著新用戶的數(shù)據(jù)去登錄Auth::guard($this->getGuard())->login($this->create($request->all()));
PHP編程
所以我們要自定義用戶注冊時(shí)生成用戶密碼的加密方式只需要修改AuthController的create方法即可.
PHP編程
比如:PHP編程
/** * 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 ]); }
修改用戶登錄
PHP編程
修改登錄前我們需要先通過路由規(guī)則看一下登錄請求的具體控制器和方法,在上文提到的auth方法定義里可以看到PHP編程
$this->get('login', 'Auth\AuthController@showLoginForm'); $this->post('login', 'Auth\AuthController@login'); $this->get('logout', 'Auth\AuthController@logout');
驗(yàn)證登錄的操作是在\App\Http\Controllers\Auth\AuthController類的login方法里.打開AuthController發(fā)現(xiàn)Auth相關(guān)的方法都是通過性狀(traits)引入到類內(nèi)的,在類內(nèi)use 要引入的traits,在編譯時(shí)PHP就會(huì)把traits里的代碼copy到類中,這是PHP5.5引入的特性具體適用場景和用途這里不細(xì)講. 所以AuthController@login
方法實(shí)際是定義在
\Illuminate\Foundation\Auth\AuthenticatesUsers這個(gè)traits里的PHP編程
/** * 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); }
登錄驗(yàn)證的主要操作是在Auth::guard($this->getGuard())->attempt($credentials, $request->has('remember'));
這個(gè)方法調(diào)用中來進(jìn)行的,Auth::guard($this->getGuard())
獲取到的是\Illuminate\Auth\SessionGuard (具體如何獲取的看Auth這個(gè)Facade \Illuminate\Auth\AuthManager里的源碼)PHP編程
看一下SessionGuard里attempt 方法是如何實(shí)現(xiàn)的:PHP編程
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是用傳遞進(jìn)來的字段從數(shù)據(jù)庫中取出用戶數(shù)據(jù)的,validateCredentials是用來驗(yàn)證密碼是否正確的實(shí)際過程.PHP編程
這里需要注意的是$this->provider
這個(gè)provider是一個(gè)實(shí)現(xiàn)了\Illuminate\Contracts\Auth\UserProvider類的provider, 我們看到目錄Illuminate\Auth下面有兩個(gè)UserProvider的實(shí)現(xiàn),分別為DatabaseUserProvider和EloquentUserProvider, 但是我們驗(yàn)證密碼的時(shí)候是通過那個(gè)來驗(yàn)證的呢,看一下auth的配置文件PHP編程
'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => App\User::class, //這個(gè)是driver用的Model ], ],
這里配置的是driver => eloquent
, 那么就是通過EloquentUserProvider的retrieveByCredentials來驗(yàn)證的, 這個(gè)EloquentUserProvider 是在SessionGuard實(shí)例化時(shí)被注入進(jìn)來的, (具體是怎么通過讀取auth配置文件, 實(shí)例化相應(yīng)的provider注入到SessionGuard里的請查閱\Illuminate\Auth\AuthManager 里createSessionDriver方法的源代碼)PHP編程
接下來我們繼續(xù)查看EloquentUserProvider中retrieveByCredentials和validateCredentials方法的實(shí)現(xiàn):PHP編程
/** * 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()); }
上面兩個(gè)方法retrieveByCredentials用除了密碼以外的字段從數(shù)據(jù)庫用戶表里取出用戶記錄,比如用email查詢出用戶記錄,然后validateCredentials方法就是通過$this->haser->check
來將輸入的密碼和哈希的密碼進(jìn)行比較來驗(yàn)證密碼是否正確.PHP編程
好了, 看到這里就很明顯了, 我們需要改成自己的密碼驗(yàn)證就是自己實(shí)現(xiàn)一下validateCredentials就可以了, 修改$this->hasher->check為我們自己的密碼驗(yàn)證規(guī)則就可以了.PHP編程
首先我們修改$user->getAuthPassword()
把數(shù)據(jù)庫中用戶表的salt和password傳遞到validateCredentials中
修改App\User.php 添加如下代碼PHP編程
/** * The table associated to this model */ protected $table = 'user';//用戶表名不是laravel約定的這里要指定一下
/** * 禁用Laravel自動(dòng)管理timestamp列 */ public $timestamps = false; /** * 覆蓋Laravel中默認(rèn)的getAuthPassword方法, 返回用戶的password和salt字段 * @return type */ public function getAuthPassword() { return ['password' => $this->attributes['password'], 'salt' => $this->attributes['salt']]; }
然后我們在建立一個(gè)自己的UserProvider接口的實(shí)現(xiàn),放到自定義的目錄中:
PHP編程
新建app/Foundation/Auth/AdminEloquentUserProvider.phpPHP編程
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驗(yàn)證時(shí)使用我們剛定義的Provider,
修改config/auth.php:PHP編程
'providers' => [ 'users' => [ 'driver' => 'admin-eloquent', 'model' => App\User::class, ] ]
修改app/Provider/AuthServiceProvider.phpPHP編程
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構(gòu)造器的,這個(gè)構(gòu)造器是一個(gè)Closure,provider方法的具體代碼實(shí)現(xiàn)在AuthManager文件里PHP編程
public function provider($name, Closure $callback) { $this->customProviderCreators[$name] = $callback; return $this; }
閉包返回了AdminEloquentUserProvider對象供Laravel Auth使用,好了做完這些修改后Laravel的Auth在做用戶登錄驗(yàn)證的時(shí)候采用的就是自定義的salt + password的方式了.PHP編程
修改重置密碼
PHP編程
Laravel 的重置密碼的工作流程是:PHP編程
第一步需要配置Laravel的email功能,此外還需要在數(shù)據(jù)庫中創(chuàng)建一個(gè)新表password_resets來存儲用戶的email和對應(yīng)的tokenPHP編程
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相關(guān)的路由,確定/password/reset對應(yīng)的控制器方法.PHP編程
$this->post('password/reset', 'Auth\PasswordController@reset');
可以看到對應(yīng)的控制器方法是\App\Http\Controllers\Auth\PasswordController類的reset方法,這個(gè)方法實(shí)際是定義在\Illuminate\Foundation\Auth\ResetsPasswords 這個(gè)traits里,PasswordController引入了這個(gè)traitsPHP編程
/** * 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對輸入進(jìn)行驗(yàn)證,接下來在程序里傳遞把新密碼和一個(gè)閉包對象傳遞給Password::broker($broker)->reset();方法,這個(gè)方法定義在\Illuminate\Auth\Passwords\PasswordBroker類里.PHP編程
/** * 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方法里,程序會(huì)先對用戶提交的數(shù)據(jù)做再一次的認(rèn)證,然后把密碼和用戶實(shí)例傳遞給傳遞進(jìn)來的閉包,在閉包調(diào)用里完成了將新密碼更新到用戶表的操作, 在閉包里程序調(diào)用了的PasswrodController類的resetPassword方法PHP編程
function ($user, $password) { $this->resetPassword($user, $password); });
PasswrodController類resetPassword方法的定義PHP編程
protected function resetPassword($user, $password) { $user->forceFill([ 'password' => bcrypt($password), 'remember_token' => Str::random(60), ])->save(); Auth::guard($this->getGuard())->login($user); }
在這個(gè)方法里L(fēng)aravel 用的是bcrypt 加密了密碼, 那么要改成我們需要的salt + password的方式,我們在PasswordController類里重寫resetPassword方法覆蓋掉traits里的該方法就可以了.PHP編程
/** * 覆蓋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); }
結(jié)語
PHP編程
到這里對Laravel Auth的自定義就完成了,注冊、登錄和重置密碼都改成了sha1(salt + password)的密碼加密方式, 所有自定義代碼都是通過定義Laravel相關(guān)類的子類和重寫方法來完成沒有修改Laravel的源碼,這樣既保持了良好的可擴(kuò)展性也保證了項(xiàng)目能夠自由遷移.PHP編程
注:使用的Laravel版本為5.2PHP編程
總結(jié)PHP編程
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持.PHP編程
轉(zhuǎn)載請注明本頁網(wǎng)址:
http://www.fzlkiss.com/jiaocheng/283.html