学习不久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=local
APP_DEBUG=true
APP_KEY=base64:IxkVvrRLqdJeU9h8vGu1W58OG3NVuDtkMWyC6nIT4qs=
APP_URL=http://www.example.com
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=ucenter_example
DB_USERNAME=root
DB_PASSWORD=root
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_DRIVER=smtp
MAIL_HOST=mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
2.创建用户控制器,编写测试登录代码。
artisan:
php artisan make:controller UserController
同样也可以手工进行创建文件:/app/Http/Controllers/UserController.php
文件内容如下:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use Auth;
class UserController extends Controller
{
//
public function login(){
$username = request()->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
<?php
/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It's a breeze. Simply tell Laravel the URIs it should respond to
| and give it the controller to call when that URI is requested.
|
*/
Route::get('/', function () {
return view('welcome');
});
Route::get('/login', 'UserController@login');
4.测试进行访问
url:http://www.example.com/login?username=ellermister&password=admin888
很明显当前Auth验证生成的SQL语句和我们预想的不太一样。
1. user_pass 不能作为明文查询条件。
2.所查询的表名不是我们的表名。
为此我们进行调试修改。
第一个问题,我们一般通过配置文件就可以解决掉。
查看:/config/auth.php
<?php
return [
/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
*/
'defaults' => [
'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
<?php
namespace App;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
protected $table = 'user';#在这里设置表名
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
}
之后,我们解决过滤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
并修改文件名以及类名(注意,此时文件的位置以及命名完全可以自定义,不要限定于此)
<?php
namespace App\Foundation\Auth;#注意这里的命名空间
use Illuminate\Support\Str;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
#以及下方的类名
class EllerEloquentUserProvider implements UserProvider
{
/**
* The hasher implementation.
*
* @var \Illuminate\Contracts\Hashing\Hasher
*/
protected $hasher;
/**
* The Eloquent user model.
*
* @var string
*/
protected $model;
/* 当前省略 请复制原文件内容即可 */
/**
* 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;
}
// 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, '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
<?php
namespace App\Providers;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
#新引入命名空间
use Auth;
use App\Foundation\Auth\EllerEloquentUserProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
'App\Model' => '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
<?php
namespace App;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
protected $table = 'user';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* Get the password for the user.
*
* @return string
*/
public function getAuthPassword()
{
return $this->user_pass;
}
}
8.恢复所有修改核心文件,测试通过。
url:http://www.example.com/login?username=ellermister&password=admin888
终于测试通过。
需要注意的是:
测试登录就要单纯的测试登录,不管其他的,只测试是否能登陆成功,再去看其他的。否则有可能出现我之前的惨状,各种问题缠绕在一起,很难分辨,到最后很难坚持下去。
比如:laravel的落地Session机制、laravel的Csrf安全机制、密码加密方法。