通过修改Laravel Auth使用salt和password进行认证用户详解
《PHP实战:通过修改Laravel Auth使用salt和password进行认证用户详解》要点: 前言 本文主要给大家介绍了通过修改Laravel Auth用salt和password进行认证用户的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍:PHP编程 Laraval自带的用户认证系统Auth非常强大易用,不过在Laravel的用户认证系统中用户注册、登录、找回密码这些模块中用到密码加密和认证算法时使用的都是bcrypt,而很多之前做的项目用户表里都是采用存储salt + password加密字符串的方式来记录用户的密码的,这就给使用Laravel框架来重构之前的项目带来了很大的阻力,不过最近自己通过在网上找资料、看社区论坛、看源码等方式完成了对Laravel Auth的修改,在这里分享出来希望能对其他人有所帮助. 开篇之前需要再说明下如果是新项目应用Laravel框架,那么不需要对Auth进行任何修改,默认的bcrypt加密算法是比salt + password更安全更高效的加密算法.PHP编程 修改用户注册 首先,在laravel 里启用验证是用的artisan命令PHP编程 php artisan make:auth 执行完命令后在routes文件(位置:app/Http/routes.php)会多一条静态方法调用PHP编程 Route::auth(); 这个Route是Laravel的一个Facade (位于IlluminateSupportFacadesRoute),调用的auth方法定义在IlluminateRoutingRouter类里,如下可以看到auth方法里就是定义了一些Auth相关的路由规则PHP编程 /** * Register the typical authentication routes for an application. * * @return void */ public function auth() { // Authentication Routes... $this->get('login','AuthAuthController@showLoginForm'); $this->post('login','AuthAuthController@login'); $this->get('logout','AuthAuthController@logout'); // Registration Routes... $this->get('register','AuthAuthController@showRegistrationForm'); $this->post('register','AuthAuthController@register'); // Password Reset Routes... $this->get('password/reset/{token?}','AuthPasswordController@showResetForm'); $this->post('password/email','AuthPasswordController@sendResetLinkEmail'); $this->post('password/reset','AuthPasswordController@reset'); } 通过路由规则可以看到注册时请求的控制器方法是AuthController的register方法,该方法定义在IlluminateFoundationAuthRegistersUsers这个traits里,AuthController在类定义里引入了这个traits.PHP编程 /** * Handle a registration request for the application. * * @param IlluminateHttpRequest $request * @return IlluminateHttpResponse */ 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方法里定义自己的每个输入字段的验证规则就可以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',]); } 接着往下看验证通过后,Laravel会掉用AuthController的create方法来生成新用户,然后拿着新用户的数据去登录 所以我们要自定义用户注册时生成用户密码的加密方式只需要修改AuthController的create方法即可. 比如: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 ]); } 修改用户登录 修改登录前我们需要先通过路由规则看一下登录请求的具体控制器和方法,在上文提到的auth方法定义里可以看到PHP编程 $this->get('login','AuthAuthController@logout'); 验证登录的操作是在AppHttpControllersAuthAuthController类的login方法里.打开AuthController发现Auth相关的方法都是通过性状(traits)引入到类内的,在类内use 要引入的traits,在编译时PHP就会把traits里的代码copy到类中,这是PHP5.5引入的特性具体适用场景和用途这里不细讲. 所 /** * Handle a login request to the application. * * @param IlluminateHttpRequest $request * @return IlluminateHttpResponse */ 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); } 登录验证的主要操作是在 看一下SessionGuard里attempt 方法是如何实现的: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是用传递进来的字段从数据库中取出用户数据的,validateCredentials是用来验证密码是否正确的实际过程.PHP编程 这里需要注意的是 'providers' => [ 'users' => [ 'driver' => 'eloquent','model' => AppUser::class,//这个是driver用的Model ],], 这里配置的是 接下来我们继续查看EloquentUserProvider中retrieveByCredentials和validateCredentials方法的实现:PHP编程 /** * Retrieve a user by the given credentials. * * @param array $credentials * @return IlluminateContractsAuthAuthenticatable|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 IlluminateContractsAuthAuthenticatable $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方法就是通过 好了,看到这里就很明显了,我们需要改成自己的密码验证就是自己实现一下validateCredentials就可以了,修改$this->hasher->check为我们自己的密码验证规则就可以了.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.phpPHP编程 namespace AppFoundationAuth; use IlluminateAuthEloquentUserProvider; use IlluminateContractsAuthAuthenticatable; use IlluminateSupportStr; class AdminEloquentUserProvider extends EloquentUserProvider { /** * Validate a user against the given credentials. * * @param IlluminateContractsAuthAuthenticatable $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, 'providers' => [ 'users' => [ 'driver' => 'admin-eloquent',] ] 修改app/Provider/AuthServiceProvider.phpPHP编程 public function boot(GateContract $gate) { $this->registerPolicies($gate); Auth::provider('admin-eloquent',function ($app,$config) { return New AppFoundationAuthAdminEloquentUserProvider($app['hash'],$config['model']); }); } Auth::provider方法是用来注册Provider构造器的,这个构造器是一个Closure,provider方法的具体代码实现在AuthManager文件里PHP编程 public function provider($name,Closure $callback) { $this->customProviderCreators[$name] = $callback; return $this; } 闭包返回了AdminEloquentUserProvider对象供Laravel Auth使用,好了做完这些修改后Laravel的Auth在做用户登录验证的时候采用的就是自定义的salt + password的方式了.PHP编程 修改重置密码 Laravel 的重置密码的工作流程是:PHP编程
第一步需要配置Laravel的email功能,此外还需要在数据库中创建一个新表password_resets来存储用户的email和对应的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相关的路由,确定/password/reset对应的控制器方法.PHP编程 $this->post('password/reset','AuthPasswordController@reset'); 可以看到对应的控制器方法是AppHttpControllersAuthPasswordController类的reset方法,这个方法实际是定义在IlluminateFoundationAuthResetsPasswords 这个traits里,PasswordController引入了这个traitsPHP编程 /** * Reset the given user's password. * * @param IlluminateHttpRequest $request * @return IlluminateHttpResponse */ 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();方法,这个方法定义在IlluminateAuthPasswordsPasswordBroker类里.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方法里,程序会先对用户提交的数据做再一次的认证,然后把密码和用户实例传递给传递进来的闭包,在闭包调用里完成了将新密码更新到用户表的操作,在闭包里程序调用了的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); } 在这个方法里Laravel 用的是bcrypt 加密了密码,那么要改成我们需要的salt + password的方式,我们在PasswordController类里重写resetPassword方法覆盖掉traits里的该方法就可以了.PHP编程 /** * 覆盖ResetsPasswords traits里的resetPassword方法,改为用sha1(salt + password)的加密方式 * Reset the given user's password. * * @param IlluminateContractsAuthCanResetPassword $user * @param string $password * @return void */ protected function resetPassword($user,$password) { $salt = Str::random(6); $user->forceFill([ 'password' => sha1($salt . $password),'salt' => $salt,])->save(); Auth::guard($this->getGuard())->login($user); } 结语 到这里对Laravel Auth的自定义就完成了,注册、登录和重置密码都改成了sha1(salt + password)的密码加密方式,所有自定义代码都是通过定义Laravel相关类的子类和重写方法来完成没有修改Laravel的源码,这样既保持了良好的可扩展性也保证了项目能够自由迁移.PHP编程 注:使用的Laravel版本为5.2PHP编程 总结PHP编程 以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对编程之家的支持.PHP编程 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |