Laravel 5.2 Auth 认证解析以及改用 salt+passwrod 加密验证
但是往往我们一些系统中的密码是通过salt+password的方式来做密码认证的,或者一些老的系统是通过salt+passwrod来认证的,现在重构迁移到Laravel框架中,那么密码认证如何不用默认的passwrod的方式而用salt+password的方式认证?
要解决问题,我们最好还是先要弄明白根源,顺藤摸瓜
首先看一下Laravel默认如何做密码验证的,看看 Auth::guard($this->getGuard())->attempt($credentials)方法做了什么:
Illuminate/Contracts/Auth/StatefulGuard.php
namespace Illuminate\Contracts\Auth;interface StatefulGuard extends Guard { /** * Attempt to authenticate a user using the given credentials. * * @param array $credentials * @param bool $remember * @param bool $login * @return bool */ public function attempt(array $credentials = [], $remember = false, $login = true); ......
上面代码看到 attempt是 StatefulGuard接口中的方法,第一个参数为需要认证的字段,第二个参数为是否记住登陆,第三个参数是否登陆,继续往下看 attempt在SessionGuard中是如何实现的
illuminate/auth/SessionGuard.php
class SessionGuard implements StatefulGuard, SupportsBasicAuth { use GuardHelpers; ...... /** * Attempt to authenticate a user using the given credentials. * * @param array $credentials * @param bool $remember * @param bool $login * @return bool */ 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; } 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); }.......}
看到通过 $this->provider->retrieveByCredentials($credentials);和 $this->provider->validateCredentials($user, $credentials);来实现验证, retrieveByCredentials是用来验证传递的字段查找用户记录是否存在, validateCredentials才是通过用户记录中密码和传入的密码做验证的实际过程。
这里需要注意的是 $this->provider,这个 provider其实是实现了一个 Illuminate\Contracts\Auth\UserProvider的 Provider,我们看到 Illuminate/Contracts/Auth下面有两个 UserProvider的实现,分别为 DatabaseUserProvider.php和 EloquentUserProvider.php。但是我们验证密码的时候是通过哪个来验证的是在怎么决定的?
config/auth.php
'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => App\Models\User::class, //这是User Model ], ],
这里我配置了 'driver' => 'eloquent',那么就是通过EloquentUserProvider.php中的 retrieveByCredentials来验证的了,我们继续看看它都干了啥
illuminate/auth/EloquentUserProvider.php
class EloquentUserProvider implements UserProvider {...... /** * Retrieve a user by the given credentials. * * @param array $credentials * @return \Illuminate\Contracts\Auth\Authenticatable|null */ public function retrieveByCredentials(array $credentials) { // First we will add each credential element to the query as a where clause. // Then we can execute the query and, if we found a user, return it in a // Eloquent User "model" that will be utilized by the Guard instances. $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->hasher->check来将输入的密码和哈希的密码比较来验证密码是否正确, $plain是提交过来的为加密密码字符串, $user->getAuthPassword()是数据库记录存放的加密密码字符串。
好了,看到这里就很明显了,我们需要改成我们自己的密码验证不就是自己实现一下 validateCredentials方法就可以了吗,改变 $this->hasher->check为我们自己的密码验证就可以了,开始搞吧!
- 首先我们来实现 $user->getAuthPassword();把数据库中用户表的 salt和 password传递到 validateCredentials中来:
修改 App\Models\User.php 添加如下代码
public function getAuthPassword() { return ['password' => $this->attributes['password'], 'salt' => $this->attributes['salt']]; }
- 然后我们建立一个自己的 UserProvider.php的实现,你可以放到任何地方,我放到自定义目录中:
新建 app/Foundation/Auth/RyanEloquentUserProvider.php
getAuthPassword(); return sha1($authPassword['salt'] . sha1($authPassword['salt'] . sha1($plain))) == $authPassword['password']; }
我这里通过 $user->getAuthPassword();传递过来了用户记录的 salt和 password,然后将认证提交的密码 $plain和 salt进行加密,如果加密结果和用户数据库中记录的密码字符串匹配那么认证就通过了, 当然加密的算法完全是自定义的。
- 最后我们将User Providers换成我们自己的 RyanEloquentUserProvider
修改 app/Providers/AuthServiceProvider.php
public function boot(GateContract $gate) { $this->registerPolicies($gate); \Auth::provider('ryan-eloquent', function ($app, $config) { return new RyanEloquentUserProvider($this->app['hash'], $config['model']); }); }
修改 config/auth.php
'providers' => [ 'users' => [ 'driver' => 'ryan-eloquent', 'model' => App\Models\User::class, ], ],
好了,再试试可以用过salt+passwrod的方式密码认证了!
转载请注明:转载自 Ryan是菜鸟 | LNMP技术栈笔记