Laravel-学习笔记-多用户表登陆分析与实现(Authentication)

版权声明:本文为博主原创文章,未经博主允许不得转载。个人博客:www.saoguang.top https://blog.csdn.net/u011580175/article/details/78231904

个人博客:www.saoguang.top​​​​​​​

框架版本:Laravel5.5

Laravel-学习笔记-多用户表登陆分析与实现
(Authentication)

前言:根据Laravel的开发文档,只需要下面两条artisan命令就能够构建起一个完整的认证系统,这个认证系统只支持单用户类型。但是很多情况下,我们的网站需要有管理员用户,有时管理员用户还和普通用户的ID不一样,可能普通用户使用邮箱登陆而管理员却不是。这种情况下就可以使用多用户表实现了。Laravel自己生成的作为普通用户,建立的管理员则只需要登陆功能。管理员账号信息使用数据填充加入就行了。

php artisan make:auth
php artisan migrate

整个实现过程都使用自带的User来进行修改。

一、分析Laravel本身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

 


 
<!DOCTYPE html> <html lang="{{ app()->getLocale() }}"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- CSRF Token -->  <meta name="csrf-token" content="{{ csrf_token() }}"> <title>{{ config('app.name', 'Laravel') }}</title> <!-- Styles -->  <link href="{{ asset('css/app.css') }}" rel="stylesheet"> </head> <body> <div id="app"> <nav class="navbar navbar-default navbar-static-top"> <div class="container"> <div class="navbar-header"> <!-- Collapsed Hamburger -->  <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#app-navbar-collapse"> <span class="sr-only">Toggle Navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <!-- Branding Image -->  <a class="navbar-brand" href="{{ url('/') }}"> {{ config('app.name', 'Laravel') }}  </a> </div> <div class="collapse navbar-collapse" id="app-navbar-collapse"> <!-- Left Side Of Navbar -->  <ul class="nav navbar-nav"> &nbsp;  </ul> <!-- Right Side Of Navbar -->  <ul class="nav navbar-nav navbar-right"> <!-- Authentication Links -->  @guest('admin')  <li><a href="{{url('admin/login')}}">管理员登陆</a></li> @else  <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"> 管理员{{Auth::guard('admin')->user()->name}} <span class="caret"></span> </a> <ul class="dropdown-menu" role="menu"> <li> <a href="{{ url('admin/authLogout') }}"  onclick="event.preventDefault(); document.getElementById('logout-form').submit();"> Logout </a> <form id="logout-form" action="{{ url('admin/authLogout') }}" method="POST" style="display: none;"> {{ csrf_field() }}  </form> </li> </ul> </li> @endguest  </ul> </div> </div> </nav> @yield('content')  </div> <!-- Scripts -->  <script src="{{ asset('js/app.js') }}"></script> </body> </html> 

 

login.blade.php


 
@extends('admin.layouts.app')  @section('content') <div class="container"> <div class="row"> <div class="col-md-8 col-md-offset-2"> <div class="panel panel-default"> <div class="panel-heading">Admin Login</div> <div class="panel-body"> <form class="form-horizontal" method="POST" action="{{ url('admin/authLogin') }}"> {{ csrf_field() }}   <div class="form-group{{ $errors->has('name') ? ' has-error' : '' }}"> <label for="name" class="col-md-4 control-label">用户名</label> <div class="col-md-6"> <input id="name" type="text" class="form-control" name="name" value="{{ old('name') }}" required autofocus> @if ($errors->has('name'))  <span class="help-block"> <strong>{{ $errors->first('name') }}</strong> </span> @endif  </div> </div> <div class="form-group{{ $errors->has('password') ? ' has-error' : '' }}"> <label for="password" class="col-md-4 control-label">密码</label> <div class="col-md-6"> <input id="password" type="password" class="form-control" name="password" required> @if ($errors->has('password'))  <span class="help-block"> <strong>{{ $errors->first('password') }}</strong> </span> @endif  </div> </div> <div class="form-group"> <div class="col-md-6 col-md-offset-4"> <div class="checkbox"> <label> <input type="checkbox" name="remember" {{ old('remember') ? 'checked' : '' }}> 记住密码 </label> </div> </div> </div> <div class="form-group"> <div class="col-md-8 col-md-offset-4"> <button type="submit" class="btn btn-primary"> 登陆 </button> </div> </div> </form> </div> </div> </div> </div> </div> @endsection 


home.blade.php


 
@extends('admin.layouts.app')  @section('content') <div class="container"> <div class="row"> <div class="col-md-8 col-md-offset-2"> <div class="panel panel-default"> <div class="panel-heading">后台管理</div> <div class="panel-body"> {{--@if (session('status'))--}}  {{--<div class="alert alert-success">--}}  {{--{{ session('status') }}--}}  {{--</div>--}}  {{--@endif--}}   欢迎管理员{{Auth::guard('admin')->user()->name}} </div> </div> </div> </div> </div> @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('/');
}

没有更多推荐了,返回首页