学习不久Laravel,碰壁非常多,整理一些 Auth组件上的理解,并重写Auth组件密码认证方式为MD5加密的一些调试过程,分享给其他初学Laravel的用户。
需求说明
由于项目是一个老项目,需要将部分数据直接迁移到新项目中直接使用,包括数据库的一些设计需要继续沿用。所以能改动的只有代码逻辑部分。
用户表:uc_user
加密方式 : md5
密码字段:user_pass
Auth::attempt 校验并登录
Auth::once 校验不登录,用于一次性授权,类似与api接口的场景
Auth::logout 注销登录的用户
Auth::user 获取已经登录的用户信息
Auth::check 检查用户是否已经登录
以上函数来源于哪里?laravel文档、查阅laravel代码打debug得知。
文件位于:/vendor/laravel/framework/src/Illuminate/Auth/SessionGuard.php
调试是一件很痛苦的事情,以下是调试完毕整理的过程说明,分享给各位需要用到的朋友。
1.配置数据库账户密码信息
修改根目录下 .env 文件即可,如图,只修改数据库信息即可,其他可默认。
APP_ENV=localAPP_DEBUG=trueAPP_KEY=base64:IxkVvrRLqdJeU9h8vGu1W58OG3NVuDtkMWyC6nIT4qs=APP_URL=http://www.example.comDB_CONNECTION=mysqlDB_HOST=127.0.0.1DB_PORT=3306DB_DATABASE=ucenter_exampleDB_USERNAME=rootDB_PASSWORD=rootCACHE_DRIVER=fileSESSION_DRIVER=fileQUEUE_DRIVER=syncREDIS_HOST=127.0.0.1REDIS_PASSWORD=nullREDIS_PORT=6379MAIL_DRIVER=smtpMAIL_HOST=mailtrap.ioMAIL_PORT=2525MAIL_USERNAME=nullMAIL_PASSWORD=nullMAIL_ENCRYPTION=null
2.创建用户控制器,编写测试登录代码。
artisan:
php artisan make:controller UserController
同样也可以手工进行创建文件:/app/Http/Controllers/UserController.php
文件内容如下:
get('username'); $password = request()->get('password'); //验证账号密码,postdata数据key为数据库存储字段名。 $postdata = ['user_name' => $username, 'user_pass'=>$password]; $ret = Auth::attempt($postdata); if($ret){ return 'success'; } return $username.$password; }}
3.增加路由映射
/app/Http/routes.php
4.测试进行访问
url:http://www.example.com/login?username=ellermister&password=admin888
很明显当前Auth验证生成的SQL语句和我们预想的不太一样。
1. user_pass 不能作为明文查询条件。
2.所查询的表名不是我们的表名。
为此我们进行调试修改。
第一个问题,我们一般通过配置文件就可以解决掉。
查看:/config/auth.php
[ 'guard' => 'web', 'passwords' => 'users', ], /* |-------------------------------------------------------------------------- | Authentication Guards |-------------------------------------------------------------------------- | */ 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'token', 'provider' => 'users', ], ], /* |-------------------------------------------------------------------------- | User Providers |-------------------------------------------------------------------------- */ 'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => App\User::class, ], // 'users' => [ // 'driver' => 'database', // 'table' => 'users', // ], ], /* |-------------------------------------------------------------------------- | Resetting Passwords |-------------------------------------------------------------------------- */ 'passwords' => [ 'users' => [ 'provider' => 'users', 'email' => 'auth.emails.password', 'table' => 'password_resets', 'expire' => 60, ], ],];
通过以上信息,结合路由配置(/app/Http/Kernel.php、/app/Http/routes.php),可以看到我们默认使用的web引擎,其配置的provider是users。
在users provider配置段可以看到当前配置的驱动是 eloquent (对象方式)。
加载的模型是:App\User::class 此刻我们修改这个模型文件。
/app/User.php
之后,我们解决过滤user_pass这个字段不被作为查询条件。
通过查询函数: function\s+attempt
得到验证函数位于:/vendor/laravel/framework/src/Illuminate/Auth/SessionGuard.php
图中我们可以看到 $credentials 就是我们传出的 $postdata 用户信息。
重点是画红线的那句,通过我们提供的$postdata来取回用户信息,之后再进行校验口令是否正确。
dd($this->provider);exit;
通过调试这个provider,得到对象文件位置。
打开同级目录下文件:/vendor/laravel/framework/src/Illuminate/Auth/EloquentUserProvider.php
定位到 retrieveByCredentials 方法
将其原字段名:password 修改为 user_pass。
再次请求调试:url:http://www.example.com/login?username=ellermister&password=admin888
我们发现基本sql语句正常,表也变成了user,可实际我们的表均有统一的前缀uc_,在此进行修改下配置:
/config/database.php
再次请求调试:url:http://www.example.com/login?username=ellermister&password=admin888
又是一个错误:未定义 password
我们追踪到这个文件中,116行进行查看。
经过测试,发现$credentials是我们提供数据,密码默认字段是user_pass而不是password,致使导致这个错误。改掉它为 user_pass。
再次请求调试:url:http://www.example.com/login?username=ellermister&password=admin888
此次依然没有输出我们预设的success,再次调试文件:
/vendor/laravel/framework/src/Illuminate/Auth/SessionGuard.php
得到是因为密码校验问题,验证调用依然是在文件:
/vendor/laravel/framework/src/Illuminate/Auth/EloquentUserProvider.php
方法:validateCredentials
测试发现两个问题:
$user->getAuthPassword() 方法字段取错,无法获取到密码密文;
$this->hasher->check() 验证方式和我们已有的数据密文加密方式不一致,无法正确校验。
继续追踪到这个方法所在的文件:
/vendor/laravel/framework/src/Illuminate/Auth/Authenticatable.php
将其进行修改为 user_pass。
另外修改外部验证密码方式为:
/** * 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['user_pass']; return md5($plain)===$user->getAuthPassword()?true:false; return $this->hasher->check($plain, $user->getAuthPassword()); }
再次请求调试:url:http://www.example.com/login?username=ellermister&password=admin888
此时我们终于登录成功。
以上逻辑代码只建议作为调试使用,因为刚才我们都是直接修改框架源代码,可能会带来无法预期的问题,非常不建议这么做,那么实际项目中应用请参见后面改法。
5.常规项目正确修改
整理一下,我们刚才修改过的核心文件。
/vendor/laravel/framework/src/Illuminate/Auth/EloquentUserProvider.php 修改验证密码字段以及验证方式
/vendor/laravel/framework/src/Illuminate/Auth/Authenticatable.php 修改数据库取回记录的字段
其他文件可以忽略,如有调试代码,可以删除掉,实际修改的文件只有以上两个核心文件(位于:/vendor目录)。
laravel一般情况下所有组件都可以进行扩展修改,可以不修改源文件的情况下对其功能进行重写扩展等。
我们接下来进行扩展Auth组件,修改为我们的需求。
6.新建 UserProvider
复制 /vendor/laravel/framework/src/Illuminate/Auth/EloquentUserProvider.php
到 /app/Foundation/Auth/EllerEloquentUserProvider.php
并修改文件名以及类名(注意,此时文件的位置以及命名完全可以自定义,不要限定于此)
createModel()->newQuery(); foreach ($credentials as $key => $value) { if (! Str::contains($key, 'user_pass')) { $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['user_pass']; return md5($plain)===$user->getAuthPassword()?true:false; return $this->hasher->check($plain, $user->getAuthPassword()); } /* 当前省略 请复制原文件内容即可 */}
注入这个UserProvider
/app/Providers/AuthServiceProvider.php
'App\Policies\ModelPolicy', ]; /** * Register any application authentication / authorization services. * * @param \Illuminate\Contracts\Auth\Access\Gate $gate * @return void */ public function boot(GateContract $gate) { $this->registerPolicies($gate); //进行拦截注入,Eller-eloquent 自定义需要与配置文件对应。 Auth::provider('Eller-eloquent', function ($app, $config) { return new EllerEloquentUserProvider($this->app['hash'], $config['model']); }); }}
修改配置
/config/auth.php(以下节选)
'providers' => [ 'users' => [ 'driver' => 'Eller-eloquent',#修改此处,需与上文注入UserProvider对应。 'model' => App\User::class, ], // 'users' => [ // 'driver' => 'database', // 'table' => 'users', // ], ],
7.重写 getAuthPassword 方法
将Authenticatable.php的getAuthPassword 方法恢复,在User模型里进行重写。
/app/User.php
user_pass; }}
8.恢复所有修改核心文件,测试通过。
url:http://www.example.com/login?username=ellermister&password=admin888
终于测试通过。
需要注意的是:
测试登录就要单纯的测试登录,不管其他的,只测试是否能登陆成功,再去看其他的。否则有可能出现我之前的惨状,各种问题缠绕在一起,很难分辨,到最后很难坚持下去。
比如:laravel的落地Session机制、laravel的Csrf安全机制、密码加密方法。