个人博客:www.saoguang.top
框架版本:Laravel5.5
前言:根据Laravel的开发文档,只需要下面两条artisan命令就能够构建起一个完整的认证系统,这个认证系统只支持单用户类型。但是很多情况下,我们的网站需要有管理员用户,有时管理员用户还和普通用户的ID不一样,可能普通用户使用邮箱登陆而管理员却不是。这种情况下就可以使用多用户表实现了。Laravel自己生成的作为普通用户,建立的管理员则只需要登陆功能。管理员账号信息使用数据填充加入就行了。
php artisan make:auth
php artisan migrate
整个实现过程都使用自带的User来进行修改。
<表面上分为下面的几个步骤>
1.进入登陆页面
2.验证表单
3.调用Illuminate\Support\Facades\Auth;下的Auth::Guard('guard_name')->attempt(['字段名'=>$对应的输入值,...], $remember)来进行用户认证,也就是验证账号和密码。
4.如果3步中认证通过,就直接重定向到指定页面,(比如管理员重定向到管理员的页面)
<5.如果没登陆管理员的情况下,进入管理员页面,中间件验证没登陆,就重定向到登陆页面>
<6.如果登陆了管理员的情况下,进入登陆页面,中间件验证,如果登陆了就重定向到管理员页面>
<下面来分析下,每个步骤>
1.进入登陆页面
登陆页面当然需要先看路由,执行完上面的两条artisan命令后,路由web.php中会添加两条语句。
Auth::routes();
Route::get('/home', 'HomeController@index')->name('home');
先看Auth::routes();这个语句包含多个路由,可以去vendor\laravel\framework\src\Illuminate\Routing目录下的Router.php中找到这个函数。
(我们只看登陆和注销的路由,下面的都不看。)可以看出login的get路由调用了LoginController的showLoginForm方法,并路由命名为login。
在这个类的中
返回登陆页面,那么到时候我们新建一个AdminHomeController时就需要重载这个方法,让他返回的是管理员的登陆页面。
2.验证表单
继续看下一条路由。重头戏来了,LoginController@login
找到这个方法,就在上面点。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
这里几个函数分别进去看,
(1)$this->validateLogin($request);
一看就清楚了,这个就是表单验证,这个username()是它的成员方法,默认返回的是email,我们可以重载这个方法,让它返回其他字段,就能作为账号验证字段了。
(2)
这一块的作用从注释中能看出,是负责错误次数记录,以及暂时锁定用户的,这个在文档中叙述入下。
登录限制#
Laravel 内置的控制器 LoginController 已经包含了 Illuminate\Foundation\Auth\ThrottlesLogins trait。
默认情况下,如果用户在进行几次尝试后仍不能提供正确的凭证,该用户将在一分钟内无法进行登录。这个限制
基于用户的用户名/邮件地址和 IP 地址。
3.调用Illuminate\Support\Facades\Auth;下的Auth::Guard('guard_name')->attempt(['字段名'=>$对应的输入值,...], $remember)来进行用户认证,也就是验证账号和密码。
(3)
这里就是登陆验证了,看return语句就知道了。
/**
* Attempt to log the user into the application.
*
* @param \Illuminate\Http\Request $request
* @return bool
*/
protected function attemptLogin(Request $request)
{
return $this->guard()->attempt(
$this->credentials($request), $request->has('remember')
);
}
这个方法调用了guard()
/**
* Get the guard to be used during authentication.
*
* @return \Illuminate\Contracts\Auth\StatefulGuard
*/
protected function guard()
{
return Auth::guard();
}
就是返回一个guard()对象。没传参,默认使用的config/auth.php中的默认的值,也就是web。
然后调用这个guard的attempt方法进行登陆验证。
/**
* Get the needed authorization credentials from the request.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
protected function credentials(Request $request)
{
return $request->only($this->username(), 'password');
}
这个方法返回了要验证字段的数组,也就是用户名和密码的数组,传入attempt方法中,第二个参数就不用讲了,是否记住密码。
4.如果3步中认证通过,就直接重定向到指定页面,(比如管理员重定向到管理员的页面)
如果认证成功就要调用sendLoginResponse.然后在这个函数里面执行了redirectPath这个方法,
/**
* Get the post register / login redirect path.
*
* @return string
*/
public function redirectPath()
{
if (method_exists($this, 'redirectTo')) {
return $this->redirectTo();
}
return property_exists($this, 'redirectTo') ? $this->redirectTo : '/home';
}
从定义就可以看出,这个重定向的方向默认是/home,我们可以通过两种方式改变方向,属性和方法,优先级看调用顺序很明显,方法优先。
如果验证失败,继续$this->incrementLoginAttempts($request);这个是增加尝试次数的方法,和登陆锁定有关就不看了。
最后一条return $this->sendFailedLoginResponse($request);发送登陆失败响应。
protected function sendFailedLoginResponse(Request $request) { throw ValidationException::withMessages([ $this->username() => [trans('auth.failed')], ]); }
实际上就是抛出异常,用户表单的错误提示,因为上面的表单验证中,只验证了requried和string。是密码错误或者账号错误的异常就要从这抛出了。
<5.如果没登陆管理员的情况下,进入管理员页面,中间件验证没登陆,就重定向到登陆页面>
web.php路由中第二句比较明显,就是进入登陆后的页面的路由。去HomeController里面看看。
发现这个控制器在构造方法中加入了auth认证中间件。
public function __construct()
{
$this->middleware('auth');
}
这个中间件先不进去看,一想就知道实现了什么,也就是把没有登陆的用户的请求过滤掉,也就是判断是否已登陆,如果没登就重定向到登陆页面。
深入到这个auth中间件的代码中,一步一步进入,你会发现这样的代码,
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string[] ...$guards
* @return mixed
*
* @throws \Illuminate\Auth\AuthenticationException
*/
public function handle($request, Closure $next, ...$guards)
{
$this->authenticate($guards);
return $next($request);
}
这里的$guards是一个中间件参数数组,
然后继续调用
/**
* Determine if the user is logged in to any of the given guards.
*
* @param array $guards
* @return void
*
* @throws \Illuminate\Auth\AuthenticationException
*/
protected function authenticate(array $guards)
{
if (empty($guards)) {
return $this->auth->authenticate();
}
foreach ($guards as $guard) {
if ($this->auth->guard($guard)->check()) {
return $this->auth->shouldUse($guard);
}
}
throw new AuthenticationException('Unauthenticated.', $guards);
}
发现了吧,他调用了以$guard存的参数为名字guard的check来判断身份,然后返回的是什么呢,继续看。
/**
* Set the default guard driver the factory should serve.
*
* @param string $name
* @return void
*/
public function shouldUse($name)
{
$name = $name ?: $this->getDefaultDriver();
$this->setDefaultDriver($name);
$this->userResolver = function ($name = null) {
return $this->guard($name)->user();
};
}
最后返回的是一个user()实例。
从这部分能看出,这个默认的auth中间件是可以传参数,来指定guard的,也就是认证的方式可以通过添加guard来实现,
比如我要从管理员表中认证,那我先在config/auth.php中配置好provide-admins和guard-admin,这样在AdminController中就能加入中间件
$this->middleware('auth:admin');
这个admin传进入,那么auth就会使用我定义的guard来验证了。(但是这里有问题,你发现没,不知道怎么指定重定向目标,就算用的是Guard:admin来进行认证),
如果认证不通过,就会跳到Users 的 login中,然而我想跳到admin/login却无法实现,所以最简单的方法,我们自己写一个中间件来判断是不是管理员就可以了。
<6.如果登陆了管理员的情况下,进入登陆页面,中间件验证,如果登陆了就重定向到管理员页面>
在LoginController类中,的构造函数有这样的中间件。
/** * Create a new controller instance. * * @return void */ public function __construct() { $this->middleware('guest')->except('logout'); }
except里的参数是成员方法名,也就是除了logout方法不使用该中间件,其他函数都用。
这个也就是判断已经登陆的中间件。去看代码。(看中间件都从Kernel.php进去)
namespace App\Http\Middleware; use Closure; use Illuminate\Support\Facades\Auth; class RedirectIfAuthenticated { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @param string|null $guard * @return mixed */ public function handle($request, Closure $next, $guard = null) { if (Auth::guard($guard)->check()) { return redirect('/home'); } return $next($request); } }
这个中间件就是使用check判断登陆了没,登陆了就重定向到/home,没登陆就传递请求。
到此对登陆过程的分析就结束了。基本上,如果能自己分析一遍。应该能有个初步的自己的理解了。
---------------------------------------------------------------------------------------------------------
理解没,不是心里想的,而是要能自己写出来。实践是检验理解的最好方法。
1.先建立起Laravel默认的认证系统。(分别运行下面的命令)
php artisan make:auth
php artisan migrate
2.建立Admin模型并生成对应的数据迁移文件。
php artisan make:model User -m
下面去配置下迁移文件.
/** * Run the migrations. * * @return void */ public function up() { Schema::create('admins', function (Blueprint $table) { $table->increments('id'); $table->string('name')->unique(); $table->string('password'); $table->rememberToken(); $table->timestamps(); }); }
执行数据迁移
php artisan migrate
3.建立Admin模型对应的数据填充文件(因为管理员用户往往无法注册,所以加入的方式就是使用数据填充)
建立填充文件
php artisan make:seeder AdminsTableSeeder
配置填充文件
/** * Run the database seeds. * * @return void */ public function run() { DB::table('admins')->insert([ 'name' => 'admin', 'password' => bcrypt('123'), ]); }
执行填充
php artisan db:seed --class=AdminsTableSeeder
执行完这条记录 管理员账号:admin和密码123 就被加入到admins表中了。
4.去config/auth.php中配置好新Guard和Provider
先配置Provider(使用模型提供认证数据)
再配置Guard(使用session保持用户登陆状态用户通过session id来访问服务器的对应session区域)
5.按照之前分析的步骤来添加路由
//新增的Admin Route::group(['prefix' => 'admin'], function(){ //登陆页面 Route::get('login', 'Admin\Auth\AdminLoginController@showLoginForm'); //登陆和注销请求 Route::post('authLogin', 'Admin\Auth\AdminLoginController@login'); Route::post('authLogout', 'Admin\Auth\AdminLoginController@logout'); //管理页面 Route::get('index', 'Admin\AdminIndexController@AdminIndex'); });
我的登陆控制器是放在\Admin\Auth下面,管理页面放在\Admin\下,你要放哪就放哪。
6.根据分析的1,要新建一个登陆控制器,并重写showLoginForm方法,去返回我的管理员的登陆页面
控制器就直接复制默认的LoginController就好。重命名为AdminLoginController。
public function showLoginForm()
{
return view('admin.auth.login');
}
7.根据分析2表单验证的(1)重载username()方法,改变验证字段为name而不是email
public function username()
{
return 'name';
}
8.根据分析3的(3),需要重载guard()方法,不能使用默认的返回web。这个管理员用户的验证使用的是刚刚配置好的Guard:admin
protected function guard()
{
return Auth::guard('admin');
}
7.根据分析4。需要覆盖
protected $redirectTo
或者
重写redirectTo方法。
protected $redirectTo = 'admin/index';
使得登陆成功后重定向到管理员页面
8.对于抛出的异常,你想修改就根据分析的内容,去重载就可以了。
9.根据分析6,我们要对AdminLoginController加入中间件来过滤请求,当管理员已经登陆时,就要让其跳转到admin/index管理页面。
这里我们还是使用guest中间件,
对这个中间件进行下面的修改。就能通过中间件参数的方式来实现跳转了。
public function handle($request, Closure $next, $guard = null) { if (Auth::guard($guard)->check() or Auth::guard()->check()) { $url = $guard ? 'admin/index' : '/home'; return redirect($url); } return $next($request); }
然后在AdminLoginController构造函数中加入此中间件。
public function __construct()
{
$this->middleware('guest:admin')->except('logout');
}
这样,这个guest后面的admin就会作为中间件参数,赋值给$guard然后调用guard获取guard:admin来验证然后不为null,就会重定向到admin/index了。
10.然后创建管理页面控制器AdminIndexController,根据上面我加的路由,添加函数
/**
* 返回管理页面视图
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function AdminIndex()
{
return view('admin/home');
}
11.根据分析5,这个管理页面控制器,需要中间件过滤掉没有登陆为管理员的请求。这里我们就自己新建一个中间件,然后添加到这里。
新建中间件
php artisan make:middleware CheckRole
编写此中间件
public function handle($request, Closure $next, $guard)
{
if(!Auth::guard($guard)->check())
{
return redirect('admin/login');
}
return $next($request);
}
判断是不是管理员,不是就跳到管理员登陆页面,同样使用中间件参数把admin传进来。
,然后要注册中间件才能使用。
注册到Kernel.php中。
然后在AdminIndexController构造函数中使用此中间件。
public function __construct()
{
$this->middleware('auth.admin:admin');
}
把admin参数传进去。
12.所有视图的源码我都贴在下面(都是使用user的来修改的)我的视图都放在views/admin里目录结构如图
app.blade.php
{{ config('app.name', 'Laravel') }} @yield('content')
login.blade.php
@extends('admin.layouts.app') @section('content') Admin Login @endsection
home.blade.php
@extends('admin.layouts.app') @section('content') 后台管理 {{--@if (session('status'))--}} {{----}} {{--{{ session('status') }}--}} {{----}} {{--@endif--}} 欢迎管理员{{Auth::guard('admin')->user()->name}} @endsection
最后的效果。
普通用户的仍然正常使用
1.如果要实现中间件同时验证多个角色,比如用户可以进入用户界面,管理员应该能进所有页面,这种情况下,我们可以选择模仿auth,因为auth可以处理多个参数。也就是使用多个Guard进行判断。如果符合就允许请求。
2.从auth的源代码中我没找到可以修改重定向的部分。所以我猜想应该Laravel的auth虽然能实现多角色(用多Guard)验证,但是他们使用的同一个登陆页面。其实我们可以在登陆时,进行一个小处理,既然auth只会重定向到login页面,那我们就只使用这个login页面,但是我们在login的post请求中,进行多次判断,分别使用guard:user和admin来判断,哪个对就进入哪个的页面。这样我们在使用需要权限的页面时,就可以直接加上中间件
$this->middleware('auth:admin')或者
$this->middleware('auth:web,admin')
上面的就是管理才能进的页面要添加的中间件,
下面就是用户和管理员都能进的页面要添加的中间件。
默认的注销是这样的
public function logout(Request $request)
{
$this->guard()->logout();
$request->session()->invalidate();
return redirect('/');
}
这个注销函数我们就需要重载下,如下面
public function logout(Request $request)
{
if(Auth::Guard('admin')->check()){
Auth::Guard('admin')->logout();
}else if(Auth::Guard('web')->check()){
Auth::Guard('web')->logout();
}
$request->session()->invalidate();
return redirect('/');
}