扩展用户认证系统在上一节中,我们介绍了LaravelAuth系统的一些实现细节。我们知道Laravel是如何使用guards和userprovider来进行用户认证的,但是对于我们自己开发的项目,或多或少都会需要在内置的guard和userprovider的基础上做一些定制来适应项目。在本节中,我将列出一个在项目中遇到的具体案例。InThisexample使用自定义守卫和用户提供程序扩展了Laravel的用户身份验证系统,使其更适合我们自己的开发项目。在介绍用户认证系统的基础知识时,提到了Laravel内置的注册和登录验证用户密码用于验证bcypt加密存储的密码,但是现有的老系统很多都是采用salt值加明文的用户密码。密码被散列并存储。如果我们要在这个老系统上应用Laravel开发项目,那么就不能再使用Laravel自带的登录注册方式了。下面通过实例看看如何扩展Laravel的用户认证系统,使其能够满足我们项目的认证需求。修改用户注册首先,我们将用户密码的加密存储方式由bcypt加密改为用户注册时存储盐值和哈希后明文密码的方式。这很简单。上一节已经提到了Laravel自带的用户注册方法是如何实现的。这里我们直接修改\App\Http\Controllers\Auth\RegisterController中的create方法如下:/***有效注册后创建一个新的用户实例。**@paramarray$data*@returnUser*/protectedfunctioncreate(array$data){$salt=Str::random(6);returnUser::create(['email'=>$data['email'],'password'=>sha1($salt.$data['password']),'register_time'=>time(),'register_ip'=>ip2long(request()->ip()),'salt'=>$salt]);}经过上面的修改,注册用户后,用户数据可以按照我们指定的方式存储,还有是与用户信息相关的其他必填字段。需要存放在user表中,这里不再赘述。修改用户登录上一节在分析Laravel默认登录的实现细节时说到登录认证的逻辑是通过SessionGuard的attempt方法实现的。在attempt方法中,SessionGuard通过EloquentUserProvider的retrieveBycredentials方法从user表中查询用户数据,并使用validateCredentials方法验证给定的用户认证数据是否与从user表中查询到的用户数据匹配。之前讲这个的时候直接给出了下面的代码块:$this->lastAttempted=$user=$this->provider->retrieveByCredentials($credentials);//如果登录认证通过,则通过登录方法将用户对象加载到应用程序中返回真;}//如果登录失败,可以触发一个事件通知用户有可疑的登录尝试(需要定义自己的监听器来实现)$this->fireFailedEvent($user,$credentials);返回假;}protectedfunctionhasValidCredentials($user,$credentials){返回!is_null($user)&&$this->provider->validateCredentials($user,$credentials);}}classEloquentUserProviderimplementsUserProvider{从数据库中获取用户实例publicfunctionretrieveByCredentials(array$credentials){if(empty($credentials)||(count($credentials)===1&&array_key_exists('password',$credentials))){返回;}$query=$this->createModel()->newQuery();foreach($credentialsas$key=>$value){if(!Str::contains($key,'password')){$query->where($key,$value);}}}返回$query->first();}//Validateauserwithgivenuserauthenticationdata/***根据给定的凭据验证用户。**@param\Illuminate\Contracts\Auth\Authenticatable$user*@paramarray$credentials*@returnbool*/publicfunctionvalidateCredentials(UserContract$user,array$credentials){$plain=$credentials['password'];}返回$this->hasher->check($plain,$user->getAuthPassword());}}CustomUserProvider嗯,看到这里就很明显了,我们需要改成自己的密码验证来实现validateCredentials,修改$this->hasher->check为自己的密码验证规则首先,让我们重写$user->getAuthPassword();重写User模型中继承自父类的方法,将数据库中用户表的salt和password传给validateCredentials:classuserextendsAuthenticatable{/***重写Laravel中默认的getAuthPassword方法,返回用户的密码和盐字段*@returnarray*/publicfunctiongetAuthPassword(){return['password'=>$this->attributes['password'],'salt'=>$this->attributes['salt']];然后我们使用一个自定义的用户提供者,通过它的validateCredentials来实现我们自己系统的密码验证规则,因为用户提供者的其他方法没有用到EloquentUserProvider中的实现,所以我们让自定义用户提供者继承自EloquentUserProvider:namespaceApp\Foundation\Auth;useIlluminate\Auth\EloquentUserProvider;useIlluminate\Contracts\Auth\Authenticatable;useIlluminate\Support\Str;classCustomEloquentUserProviderextendsEloquentUserProvider{/***根据给定的凭据验证用户。**@param\Illuminate\Contracts\Auth\Authenticatable$user*@param数组$credentials*/publicfunctionvalidateCredentials(Authenticatable$user,array$credentials){$plain=$credentials['password'];$authPassword=$user->getAuthPassword();返回sha1($authPassword['salt'].$plain)==$authPassword['password'];接下来,通过Auth::provider()向Laravel系统注册CustomEloquentUserProvider。Auth::provider方法采用一个闭包,该闭包将用户提供者对象作为用户提供者创建者返回,并使用给定名称进行注册。在Laravel中,代码如下:classAppServiceProviderextendsServiceProvider{/***引导任何应用程序服务。**@returnvoid*/publicfunctionboot(){\Auth::provider('custom-eloquent',function($app,$config){returnNew\App\Foundation\Auth\CustomEloquentUserProvider($app['hash'],$config['模型']);});}......}注册用户提供者后我们可以在config/auth.php://config/auth.php'providers'=>['users'=>['driver'=>'coutom-eloquent','model'=>\App\User::class,]]自定义认证守卫,现在密码认证已经修改,守卫用于用户认证还是SessionGUard,系统中会有一个模块对外提供API。这种情况下,我们一般希望用户在登录认证后返回一个JSONWEBTOKEN给客户端。每次接口调用时,使用这个token来验证请求接口是否为Effectiveusers,这个需求需要通过自定义Guard扩展函数来实现,有一个composer包"tymon/jwt-auth":"dev-develop",其1.0beta版本中带来的JwtGuard是一个实现了Illuminate\Contracts\Auth\Guard的守卫,完全满足我上面提到的要求,所以我们通过Auth::extend()方法将JwtGuard注册到系统中:classAppServiceProviderextendsServiceProvider{/***引导任何应用程序服务。**@returnvoid*/publicfunctionboot(){\Auth::provider('custom-eloquent',function($app,$config){returnNew\App\Foundation\Auth\CustomEloquentUserProvider($app['hash'],$config['模型']);});\Auth::extend('jwt',function($app,$name,array$config){//返回一个Illuminate\Contracts\Auth\Guard实例..returnnew\Tymon\JWTAuth\JwtGuard(\Auth::createUserProvider($config['provider']));});}......}定义后,配置auth.php配置文件的guards修改如下:'guards'=>['web'=>['driver'=>'session','provider'=>'users',],'api'=>['driver'=>'jwt',//token==>jwt'provider'=>'users',]],接下来我们定义一个API使用的登录认证方式。在鉴权时,会使用上面注册的jwtguard来完成鉴权。认证完成后,会返回一个JSONWEBTOKEN给客户端。Route::post('apilogin','Auth\LoginController@apiLogin');classLoginControllerextendsController{publicfunctionapiLogin(Request$request){...if($token=$this->guard('api')->attempt($credentials)){$return['status_code']=200;$return['message']='登录成功';$response=\Response::json($return);$response->headers->set('Authorization','Bearer'.$token);返回$响应;}...}}总结通过上面的例子,我们说明了如何通过自定义的认证守卫和用户提供者来扩展Laravel的用户认证系统。Laravel的用户认证系统比较了解,知道在Laravel系统默认的用户认证方式不能满足我们需求的情况下,如何通过自定义这两个组件来扩展功能来完成我们项目自身的认证需求。Laravel源码学习系列文章中,欢迎访问阅读。
