详解如何修改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 的重置密码的工作流程是:
- 向需要重置密码的用户的邮箱发送一封带有重置密码链接的邮件,链接中会包含用户的email地址和token。
- 用户点击邮件中的链接在重置密码页面输入新的密码,Laravel通过验证email和token确认用户就是发起重置密码请求的用户后将新密码更新到用户在数据表的记录里。
第一步需要配置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
详解如何修改Laravel Auth使用salt和password来认证用户相关推荐
- python英语字典程序修改_详解如何修改python中字典的键和值
我们知道python中字典是无序的,它们都是通过hash去对应的.一般的如果我们需要修改字典的值,只需要直接覆盖即可,而修改字典的键,则需要使用字典自带的pop函数,示例如下: t = {} t['a ...
- python字典修改键所对应值_详解如何修改python中字典的键和值
我们知道python中字典是无序的,它们都是通过hash去对应的.一般的如果我们需要修改字典的值,只需要直接覆盖即可,而修改字典的键,则需要使用字典自带的pop函数,示例如下: t = {} t['a ...
- linux命令优先级设置,linux renice命令参数及用法详解(linux修改程序运行优先级命令)...
linux renice命令参数及用法详解(linux修改程序运行优先级命令) 发布时间:2012-07-21 12:45:32 作者:佚名 我要评论 renice指令可重新调整程序执行的优先 ...
- Linux文件关联详解 linux 修改默认文件关联打开程序
linux 修改默认文件关联打开程序 从总体上讲 /etc/gnome/defaults.list 保存了全局的打开方式 /.local/share/applications/mimeapps.lis ...
- linux socket send函数和recv函数详解以及修改缓冲区大小
一.send/recv详解 1.send 函数 int send( SOCKET s, const char FAR *buf, int len, int flags ); 不论是客户还是服务器应用程 ...
- FPgrwoth详解(转载+修改一处图片问题)
FP-growth算法,fpgrowth算法详解 下面使用的最小支持度是2,也就是说最小等于2算达标,对应代码中则是>minSup=1 使用FP-growth算法来高效发现频繁项集 前言 你用过 ...
- scn 详解(修改)
oracle SCN 详解 1. 什么是 scn SCN(system change number) 就是系统改变号 , 是数据库非常重要的一种数据结构. 在数据库中scn 作为一种时钟机制来标记 ...
- mip-img 不启用css,百度mip-img图片详解 如何修改图片MIP
百度mip-img 图片详解,我们先来看下完整的标签 mip-img layout = responsive width = 350 height = 263 popup alt = www.xhcs ...
- linux修改密码和宽限天数,Linux chage用法详解:修改用户密码状态
除了passwd -S命令可以查看用户的密码信息外,还可以利用 chage 命令,它可以显示更加详细的用户密码信息,并且和 passwd 命令一样,提供了修改用户密码信息的功能. 如果你要修改用户的密 ...
最新文章
- 解决git本地提交不到远程库
- 阿里云证书 | 套路太深,还是我打开姿势不对?
- DDoS***、CC***的***方式和防御方法
- python输入输出-python输入与输出
- c语言结构体与共同体课件,《结构体与共同体》PPT课件.ppt
- 用Python实现一个大数据搜索引擎
- 实例62:python
- MinGW编译boost库
- (7)UART接收verilog与Systemverilog编码
- 视频剪辑用i7,8600还是r5,3600好些?
- Bailian3729 用set实现字符串的排序和查找【文本处理】
- Bailian3195 最大公约数【数论】
- 使用QUARKUS开发JSON REST 服务
- 用Android studio设计贺卡,功能强大的贺卡设计制作软件推荐:Hallmark Card Studio
- Sitemesh小记
- meta http-equiv 属性 详解
- Codeup100000609问题 A: Jugs
- javaweb简单的外卖平台系统(一)
- Oracle数据库实训教师评语,教育教学评价系统数据库的设计与实现.doc
- [CVPR2020最佳论文详细解读] Unsupervised Learning of Probably Symmetric Deformable 3D Object