溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

如何通過(guò)修改Laravel Auth使用salt和password進(jìn)行認(rèn)證用戶(hù)

發(fā)布時(shí)間:2021-07-23 09:44:39 來(lái)源:億速云 閱讀:111 作者:小新 欄目:開(kāi)發(fā)技術(shù)

這篇文章主要介紹如何通過(guò)修改Laravel Auth使用salt和password進(jìn)行認(rèn)證用戶(hù),文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!

Laraval自帶的用戶(hù)認(rèn)證系統(tǒng)Auth非常強(qiáng)大易用,不過(guò)在Laravel的用戶(hù)認(rèn)證系統(tǒng)中用戶(hù)注冊(cè)、登錄、找回密碼這些模塊中用到密碼加密和認(rèn)證算法時(shí)使用的都是bcrypt,而很多之前做的項(xiàng)目用戶(hù)表里都是采用存儲(chǔ)salt + password加密字符串的方式來(lái)記錄用戶(hù)的密碼的,這就給使用Laravel框架來(lái)重構(gòu)之前的項(xiàng)目帶來(lái)了很大的阻力,不過(guò)最近自己通過(guò)在網(wǎng)上找資料、看社區(qū)論壇、看源碼等方式完成了對(duì)Laravel Auth的修改,在這里分享出來(lái)希望能對(duì)其他人有所幫助。 開(kāi)篇之前需要再說(shuō)明下如果是新項(xiàng)目應(yīng)用Laravel框架,那么不需要對(duì)Auth進(jìn)行任何修改,默認(rèn)的bcrypt加密算法是比salt + password更安全更高效的加密算法。

修改用戶(hù)注冊(cè)

首先,在laravel 里啟用驗(yàn)證是用的artisan命令

php artisan make:auth

執(zhí)行完命令后在routes文件(位置:app/Http/routes.php)會(huì)多一條靜態(tài)方法調(diào)用

Route::auth();

這個(gè)Route是Laravel的一個(gè)Facade (位于Illuminate\Support\Facades\Route), 調(diào)用的auth方法定義在Illuminate\Routing\Router類(lèi)里, 如下可以看到auth方法里就是定義了一些Auth相關(guān)的路由規(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ò)路由規(guī)則可以看到注冊(cè)時(shí)請(qǐng)求的控制器方法是AuthController的register方法, 該方法定義在\Illuminate\Foundation\Auth\RegistersUsers這個(gè)traits里,AuthController在類(lèi)定義里引入了這個(gè)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方法里首先會(huì)對(duì)request里的用戶(hù)輸入數(shù)據(jù)進(jìn)行驗(yàn)證,你只需要在AuthController的validator方法里定義自己的每個(gè)輸入字段的驗(yàn)證規(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',
 ]);
}

接著往下看驗(yàn)證通過(guò)后,Laravel會(huì)掉用AuthController的create方法來(lái)生成新用戶(hù),然后拿著新用戶(hù)的數(shù)據(jù)去登錄Auth::guard($this->getGuard())->login($this->create($request->all()));

所以我們要自定義用戶(hù)注冊(cè)時(shí)生成用戶(hù)密碼的加密方式只需要修改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
 ]);
}

修改用戶(hù)登錄

修改登錄前我們需要先通過(guò)路由規(guī)則看一下登錄請(qǐng)求的具體控制器和方法,在上文提到的auth方法定義里可以看到

 $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類(lèi)的login方法里。打開(kāi)AuthController發(fā)現(xiàn)Auth相關(guān)的方法都是通過(guò)性狀(traits)引入到類(lèi)內(nèi)的,在類(lèi)內(nèi)use 要引入的traits,在編譯時(shí)PHP就會(huì)把traits里的代碼copy到類(lèi)中,這是PHP5.5引入的特性具體適用場(chǎng)景和用途這里不細(xì)講。 所以AuthController@login方法實(shí)際是定義在
\Illuminate\Foundation\Auth\AuthenticatesUsers這個(gè)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);
}

登錄驗(yàn)證的主要操作是在Auth::guard($this->getGuard())->attempt($credentials, $request->has('remember'));這個(gè)方法調(diào)用中來(lái)進(jìn)行的,Auth::guard($this->getGuard()) 獲取到的是\Illuminate\Auth\SessionGuard (具體如何獲取的看Auth這個(gè)Facade \Illuminate\Auth\AuthManager里的源碼)

看一下SessionGuard里attempt 方法是如何實(shí)現(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是用傳遞進(jìn)來(lái)的字段從數(shù)據(jù)庫(kù)中取出用戶(hù)數(shù)據(jù)的,validateCredentials是用來(lái)驗(yàn)證密碼是否正確的實(shí)際過(guò)程。

這里需要注意的是$this->provider這個(gè)provider是一個(gè)實(shí)現(xiàn)了\Illuminate\Contracts\Auth\UserProvider類(lèi)的provider, 我們看到目錄Illuminate\Auth下面有兩個(gè)UserProvider的實(shí)現(xiàn),分別為DatabaseUserProvider和EloquentUserProvider, 但是我們驗(yàn)證密碼的時(shí)候是通過(guò)那個(gè)來(lái)驗(yàn)證的呢,看一下auth的配置文件

'providers' => [
 'users' => [
 'driver' => 'eloquent',
 'model' => App\User::class, //這個(gè)是driver用的Model
 ],
],

這里配置的是driver => eloquent , 那么就是通過(guò)EloquentUserProvider的retrieveByCredentials來(lái)驗(yàn)證的, 這個(gè)EloquentUserProvider 是在SessionGuard實(shí)例化時(shí)被注入進(jìn)來(lái)的, (具體是怎么通過(guò)讀取auth配置文件, 實(shí)例化相應(yīng)的provider注入到SessionGuard里的請(qǐng)查閱\Illuminate\Auth\AuthManager 里createSessionDriver方法的源代碼)

接下來(lái)我們繼續(xù)查看EloquentUserProvider中retrieveByCredentials和validateCredentials方法的實(shí)現(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());
}

上面兩個(gè)方法retrieveByCredentials用除了密碼以外的字段從數(shù)據(jù)庫(kù)用戶(hù)表里取出用戶(hù)記錄,比如用email查詢(xún)出用戶(hù)記錄,然后validateCredentials方法就是通過(guò)$this->haser->check來(lái)將輸入的密碼和哈希的密碼進(jìn)行比較來(lái)驗(yàn)證密碼是否正確。

好了, 看到這里就很明顯了, 我們需要改成自己的密碼驗(yàn)證就是自己實(shí)現(xiàn)一下validateCredentials就可以了, 修改$this->hasher->check為我們自己的密碼驗(yàn)證規(guī)則就可以了。

首先我們修改$user->getAuthPassword()把數(shù)據(jù)庫(kù)中用戶(hù)表的salt和password傳遞到validateCredentials中
修改App\User.php 添加如下代碼

/**
 * The table associated to this model
 */
protected $table = 'user';//用戶(hù)表名不是laravel約定的這里要指定一下
/**
 * 禁用Laravel自動(dòng)管理timestamp列
 */
public $timestamps = false;

/**
 * 覆蓋Laravel中默認(rèn)的getAuthPassword方法, 返回用戶(hù)的password和salt字段
 * @return type
 */
public function getAuthPassword()
{
 return ['password' => $this->attributes['password'], 'salt' => $this->attributes['salt']];
}

然后我們?cè)诮⒁粋€(gè)自己的UserProvider接口的實(shí)現(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驗(yàn)證時(shí)使用我們剛定義的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方法是用來(lái)注冊(cè)Provider構(gòu)造器的,這個(gè)構(gòu)造器是一個(gè)Closure,provider方法的具體代碼實(shí)現(xiàn)在AuthManager文件里

public function provider($name, Closure $callback)
{
 $this->customProviderCreators[$name] = $callback;

 return $this;
}

閉包返回了AdminEloquentUserProvider對(duì)象供Laravel Auth使用,好了做完這些修改后Laravel的Auth在做用戶(hù)登錄驗(yàn)證的時(shí)候采用的就是自定義的salt + password的方式了。

修改重置密碼

Laravel 的重置密碼的工作流程是:

  • 向需要重置密碼的用戶(hù)的郵箱發(fā)送一封帶有重置密碼鏈接的郵件,鏈接中會(huì)包含用戶(hù)的email地址和token。

  • 用戶(hù)點(diǎn)擊郵件中的鏈接在重置密碼頁(yè)面輸入新的密碼,Laravel通過(guò)驗(yàn)證email和token確認(rèn)用戶(hù)就是發(fā)起重置密碼請(qǐng)求的用戶(hù)后將新密碼更新到用戶(hù)在數(shù)據(jù)表的記錄里。

第一步需要配置Laravel的email功能,此外還需要在數(shù)據(jù)庫(kù)中創(chuàng)建一個(gè)新表password_resets來(lái)存儲(chǔ)用戶(hù)的email和對(duì)應(yīng)的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;

通過(guò)重置密碼表單的提交地址可以看到,表單把新的密碼用post提交給了/password/reset,我們先來(lái)看一下auth相關(guān)的路由,確定/password/reset對(duì)應(yīng)的控制器方法。

 $this->post('password/reset', 'Auth\PasswordController@reset');

可以看到對(duì)應(yīng)的控制器方法是\App\Http\Controllers\Auth\PasswordController類(lèi)的reset方法,這個(gè)方法實(shí)際是定義在\Illuminate\Foundation\Auth\ResetsPasswords 這個(gè)traits里,PasswordController引入了這個(gè)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);
 }
}

方法開(kāi)頭先通過(guò)validator對(duì)輸入進(jìn)行驗(yàn)證,接下來(lái)在程序里傳遞把新密碼和一個(gè)閉包對(duì)象傳遞給Password::broker($broker)->reset();方法,這個(gè)方法定義在\Illuminate\Auth\Passwords\PasswordBroker類(lèi)里.

/**
 * 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ì)先對(duì)用戶(hù)提交的數(shù)據(jù)做再一次的認(rèn)證,然后把密碼和用戶(hù)實(shí)例傳遞給傳遞進(jìn)來(lái)的閉包,在閉包調(diào)用里完成了將新密碼更新到用戶(hù)表的操作, 在閉包里程序調(diào)用了的PasswrodController類(lèi)的resetPassword方法

function ($user, $password) {
 $this->resetPassword($user, $password);
});

PasswrodController類(lèi)resetPassword方法的定義

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的方式,我們?cè)赑asswordController類(lèi)里重寫(xiě)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);
}

結(jié)語(yǔ)

到這里對(duì)Laravel Auth的自定義就完成了,注冊(cè)、登錄和重置密碼都改成了sha1(salt + password)的密碼加密方式, 所有自定義代碼都是通過(guò)定義Laravel相關(guān)類(lèi)的子類(lèi)和重寫(xiě)方法來(lái)完成沒(méi)有修改Laravel的源碼,這樣既保持了良好的可擴(kuò)展性也保證了項(xiàng)目能夠自由遷移。

注:使用的Laravel版本為5.2

以上是“如何通過(guò)修改Laravel Auth使用salt和password進(jìn)行認(rèn)證用戶(hù)”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI