ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## **简介** Laravel 支持基于多表的用户认证,即同时允许不同数据表用户(如前台用户、后台用户)进行登录认证。下面我们就以前后台用户登录认证为例,简单介绍基于不同数据表实现用户注册及登录功能。 ## **1. 实现前台用户登录** 我们用框架自带的`users`表存储前台用户,接下来我们先实现前台用户注册登录。通过之前运行的`make:auth`命令,我们已经生成了前台认证所需的所有代码和数据表,并且我们在[上一篇教程](https://xueyuanjun.com/post/9734.html)中知晓了`users`表存储用户的注册登录流程。 ## **2. 创建后台用户模型** 假设我们的后台用户表是`admins`,对应的模型类是`Admin`,首先使用如下 Artisan 命令生成后台用户模型及对应数据库迁移文件: ~~~ php artisan make:model Admin -m ~~~ 编辑新生成的数据库迁移文件`create_admins_table`对应迁移类的`up`方法代码如下(直接从`users`表迁移中拷贝过来): ~~~ <?php namespace App; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; class Admin extends Authenticatable { use Notifiable; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes excluded from the model's JSON form. * * @var array */ protected $hidden = [ 'password', 'remember_token', ]; } ~~~ 然后运行迁移命令——`php artisan migrate`在数据库中创建该表。 ## **3. 编辑认证配置文件** 要实现多表用户认证,首先要配置认证配置文件`auth.php`,这里我们实现的功能是前后台用户登录,所以对应配置如下,该配置文件默认的用户认证相关配置如下: ~~~ // 默认用户认证配置,即不指定特定认证服务方的话,使用以下默认配置 'defaults' => [ 'guard' => 'web', 'passwords' => 'users', ], // 在这里配置不同的认证服务方,默认支持 web 和 api 认证, // 即 web 路由的请求认证和 api 路由的请求认证 // 如果要配置其它的认证服务方,比如后台登录,需要在这里配置 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'token', 'provider' => 'users', ], ], // 在这里配置系统支持的认证提供者(用户数据来源), // 默认是基于 User 模型的 EloquentProvider, // 如果系统支持不同表用户登录,需要在这里额外配置 'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => App\User::class, ], // 'users' => [ // 'driver' => 'database', // 'table' => 'users', // ], ], // 密码重置表,默认支持 users 表的密码重置,对应数据表是 password_resets // 如果要支持其它用户表的密码重置,需要在这里额外配置 'passwords' => [ 'users' => [ 'provider' => 'users', 'table' => 'password_resets', 'expire' => 60, ], ], ~~~ 我在注释里指出了每个配置项的作用,以及如果要配置多表用户认证,需要怎么配置。我们仍然使用默认的`web`guard 实现前台登录,接下来依样画葫芦,新增一个`admin`guard 用于后台登录,然后在`providers`中新增一个后台用户数据提供者`admins`,后台用户都是自己人,就不做密码重置了,如果你面对的用户系统更复杂,比如电商系统,涉及买家、卖家、系统后台用户,则可能需要为不同用户表设置密码重置表,在`passwords`配置项中参照`users`表进行设置就好了,然后还要创建对应的数据表。下面是更新后的`auth.php`配置表: ~~~ <?php return [ 'defaults' => [ 'guard' => 'web', 'passwords' => 'users', ], 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'admin' => [ 'driver' => 'session', 'provider' => 'admins', ], 'api' => [ 'driver' => 'token', 'provider' => 'users', ], ], 'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => App\User::class, ], 'admins' => [ 'driver' => 'eloquent', 'model' => App\Admin::class, ], ], 'passwords' => [ 'users' => [ 'provider' => 'users', 'table' => 'password_resets', 'expire' => 60, ], ], ]; ~~~ 认证配置是由`guard`和`provider`两部分构成的,`guard`用于配置认证请求服务方,比如前台登录、后台登录、API登录,以及基于 Web 路由还是 API 路由(Web 路由基于 Session 进行认证,API 路由基于 Token 进行认证);`provider`用于配置用户认证数据提供方,通过 Eloquent 还是数据库,以及哪张数据表。所以我们在这两个配置项中分别新增了`admin`和`admins`配置项,标识后台登录基于 Web 路由,并且由`Admin`模型类提供数据支持。 ## **4. 定义后台用户认证路由及控制器** 做好以上准备工作后,接下来我们正式开始实现后台认证。首先定义后台用户认证路由,在`routes/web.php`中新增如下路由定义: ~~~ Route::get('admin/login', 'Admin\LoginController@showLoginForm')->name('admin.login'); Route::post('admin/login', 'Admin\LoginController@login'); Route::get('admin/register', 'Admin\RegisterController@showRegistrationForm')->name('admin.register'); Route::post('admin/register', 'Admin\RegisterController@register'); Route::post('admin/logout', 'Admin\LoginController@logout')->name('admin.logout'); Route::get('admin', 'AdminController@index')->name('admin.home'); ~~~ 然后使用Artisan命令创建对应控制器: ~~~ php artisan make:controller Admin/LoginController php artisan make:controller Admin/RegisterController php artisan make:controller AdminController ~~~ 编辑`Admin/LoginController.php`代码如下: ~~~ <?php namespace App\Http\Controllers\Admin; use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Http\Request; use App\Http\Controllers\Controller; use Illuminate\Support\Facades\Auth; class LoginController extends Controller { use AuthenticatesUsers; protected $redirectTo = '/admin'; public function __construct() { $this->middleware('guest:admin')->except('logout'); } public function showLoginForm() { return view('admin.login'); } protected function guard() { return Auth::guard('admin'); } // 退出后跳转页面 protected function loggedOut(Request $request) { return redirect(route('admin.login')); } } ~~~ 可以看到我们重写了`AuthenticatesUsers`中的两个方法,`showLoginForm()`方法用户显示后台登录表单,`guard()`方法用于在`Auth::guard`方法中传入对应的认证服务方配置并将其返回,这样,后台登录时使用的就是上一步配置的后台认证 Guard 了,该参数值默认是`web`(在`auth.php`的`defaults`中配置),所以我们在前台用户认证时不需要手动传入。 另外,我们在中间件`guest`中也传入了`admin`参数,表示判断后台是否登录。为此,我们还要修改`guest`中间件对应类`RedirectIfAuthenticated`的处理方法`handle`,当传入`guard`是`admin`时,跳转到后台主页: ~~~ public function handle($request, Closure $next, $guard = null) { if (Auth::guard($guard)->check()) { if ($guard == 'admin') { return redirect('/admin'); } return redirect('/home'); } return $next($request); } ~~~ 最后,我们还设置了登录后跳转路径为`/admin`并重写`loggedOut`方法返回退出后台后调整页面为后台登录页面。 同理,编辑`Admin/RegisterController.php`代码如下: ~~~ <?php namespace App\Http\Controllers\Admin; use App\Admin; use Illuminate\Foundation\Auth\RegistersUsers; use Illuminate\Http\Request; use App\Http\Controllers\Controller; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; class RegisterController extends Controller { use RegistersUsers; protected $redirectTo = '/admin'; public function __construct() { $this->middleware('guest:admin'); } public function showRegistrationForm() { return view('admin.register'); } protected function guard() { return Auth::guard('admin'); } /** * Get a validator for an incoming registration request. * * @param array $data * @return \Illuminate\Contracts\Validation\Validator */ protected function validator(array $data) { return Validator::make($data, [ 'name' => 'required|string|max:255', 'email' => 'required|string|email|max:255|unique:admins', 'password' => 'required|string|min:6|confirmed', ]); } /** * Create a new user instance after a valid registration. * * @param array $data * @return \App\User */ protected function create(array $data) { return Admin::create([ 'name' => $data['name'], 'email' => $data['email'], 'password' => Hash::make($data['password']), ]); } } ~~~ 相应的逻辑和登录类似,就不多说了。然后编辑`AdminController.php`代码如下: ~~~ <?php namespace App\Http\Controllers; use Illuminate\Http\Request; class AdminController extends Controller { /** * Create a new controller instance. * * @return void */ public function __construct() { $this->middleware('auth:admin'); } /** * Show the application dashboard. * * @return \Illuminate\Http\Response */ public function index() { return view('admin.home'); } } ~~~ 通过`index`方法渲染后台登录后页面。如果没有登录访问该方法会通过`auth`中间件进行处理,我们在该中间件中也传入了`admin`参数,用于判断后台是否登录,同样,我们也要修改`auth`中间件对应类`Authenticate`来处理后台未登录跳转,这一次我们通过重写父类的`authenticate`方法来实现: ~~~ <?php namespace App\Http\Middleware; use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\Middleware\Authenticate as Middleware; class Authenticate extends Middleware { protected $redirectTo = ''; /** * Get the path the user should be redirected to when they are not authenticated. * * @param \Illuminate\Http\Request $request * @return string */ protected function redirectTo($request) { return route('login'); } protected function authenticate($request, array $guards) { if (empty($guards)) { $guards = [null]; } foreach ($guards as $guard) { if ($this->auth->guard($guard)->check()) { return $this->auth->shouldUse($guard); } } // 这里我们以 guards 传入的第一个参数为准选择跳转到的登录页面 $guard = $guards[0]; if ($guard == 'admin') { $this->redirectTo = route('admin.login'); } throw new AuthenticationException( 'Unauthenticated.', $guards, $this->redirectTo ? : $this->redirectTo($request) ); } } ~~~ ## **视图文件创建及修改** 在创建后台认证视图之前,先要为后台认证创建一个布局文件,我们拷贝`layouts/app.blade.php`进行编写 ~~~ cp resources/views/layouts/app.blade.php resources/views/layouts/admin.blade.php ~~~ 然后修改`admin.blade.php`布局文件代码如下: ~~~ <!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- CSRF Token --> <meta name="csrf-token" content="{{ csrf_token() }}"> <title>{{ config('app.name', 'Laravel') }} Admin</title> <!-- Scripts --> <script src="{{ asset('js/app.js') }}" defer></script> <!-- Fonts --> <link rel="dns-prefetch" href="https://fonts.gstatic.com"> <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet" type="text/css"> <!-- Styles --> <link href="{{ asset('css/app.css') }}" rel="stylesheet"> </head> <body> <div id="app"> <nav class="navbar navbar-expand-md navbar-light navbar-laravel"> <div class="container"> <a class="navbar-brand" href="{{ url('/admin') }}"> {{ config('app.name', 'Laravel') }} Admin </a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <!-- Left Side Of Navbar --> <ul class="navbar-nav mr-auto"> <li class="nav-item"> <a class="nav-link" href="{{ url('/') }}">Home</a> </li> </ul> <!-- Right Side Of Navbar --> <ul class="navbar-nav ml-auto"> <!-- Authentication Links --> @guest <li class="nav-item"> <a class="nav-link" href="{{ route('admin.login') }}">{{ __('Login') }}</a> </li> <li class="nav-item"> @if (Route::has('admin.register')) <a class="nav-link" href="{{ route('admin.register') }}">{{ __('Register') }}</a> @endif </li> @else <li class="nav-item dropdown"> <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre> {{ Auth::user('admin')->name }} <span class="caret"></span> </a> <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown"> <a class="dropdown-item" href="{{ route('admin.logout') }}" onclick="event.preventDefault(); document.getElementById('logout-form').submit();"> {{ __('Logout') }} </a> <form id="logout-form" action="{{ route('admin.logout') }}" method="POST" style="display: none;"> @csrf </form> </div> </li> @endguest </ul> </div> </div> </nav> <main class="py-4"> @yield('content') </main> </div> </body> </html> ~~~ 可以看到我们将其中的注册、登录、退出链接全部替换成了后台认证相关的链接,修改了导航栏,新增了「Home」链接跳转到应用首页,并且在用户登录后通过`Auth::user('admin')->name`获取后台登录用户名,这里的逻辑和前面后台认证控制器一样,通过在`Auth::user()`方法中传入指定的用户认证服务方来获取对应的用户认证信息,默认值是`web`,所以我们在前台用户登录流程中不需要手动传入这个参数值。 最后我们创建后台用户认证对应视图文件,先在`resources/views`目录下创建`admin`子目录,然后将前台用户认证视图模板拷贝过去并稍作修改即可: ~~~ cp resources/views/auth/login.blade.php resources/views/admin/ cp resources/views/auth/register.blade.php resources/views/admin/ cp resources/views/home.blade.php resources/views/admin/ ~~~ 修改`admin/login.blade.php`代码如下: ~~~ @extends('layouts.admin') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card"> <div class="card-header">{{ __('Admin Login') }}</div> <div class="card-body"> <form method="POST" action="{{ route('admin.login') }}"> @csrf <div class="form-group row"> <label for="email" class="col-sm-4 col-form-label text-md-right">{{ __('Email') }}</label> <div class="col-md-6"> <input id="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required autofocus> @if ($errors->has('email')) <span class="invalid-feedback" role="alert"> <strong>{{ $errors->first('email') }}</strong> </span> @endif </div> </div> <div class="form-group row"> <label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label> <div class="col-md-6"> <input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" required> @if ($errors->has('password')) <span class="invalid-feedback" role="alert"> <strong>{{ $errors->first('password') }}</strong> </span> @endif </div> </div> <div class="form-group row"> <div class="col-md-6 offset-md-4"> <div class="form-check"> <input class="form-check-input" type="checkbox" name="remember" id="remember" {{ old('remember') ? 'checked' : '' }}> <label class="form-check-label" for="remember"> {{ __('Remember Me') }} </label> </div> </div> </div> <div class="form-group row mb-0"> <div class="col-md-8 offset-md-4"> <button type="submit" class="btn btn-primary"> {{ __('Login') }} </button> </div> </div> </form> </div> </div> </div> </div> </div> @endsection ~~~ 修改`admin/register.blade.php`代码如下: ~~~ @extends('layouts.admin') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card"> <div class="card-header">{{ __('Admin Register') }}</div> <div class="card-body"> <form method="POST" action="{{ route('admin.register') }}"> @csrf <div class="form-group row"> <label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Name') }}</label> <div class="col-md-6"> <input id="name" type="text" class="form-control{{ $errors->has('name') ? ' is-invalid' : '' }}" name="name" value="{{ old('name') }}" required autofocus> @if ($errors->has('name')) <span class="invalid-feedback" role="alert"> <strong>{{ $errors->first('name') }}</strong> </span> @endif </div> </div> <div class="form-group row"> <label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label> <div class="col-md-6"> <input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required> @if ($errors->has('email')) <span class="invalid-feedback" role="alert"> <strong>{{ $errors->first('email') }}</strong> </span> @endif </div> </div> <div class="form-group row"> <label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label> <div class="col-md-6"> <input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" required> @if ($errors->has('password')) <span class="invalid-feedback" role="alert"> <strong>{{ $errors->first('password') }}</strong> </span> @endif </div> </div> <div class="form-group row"> <label for="password-confirm" class="col-md-4 col-form-label text-md-right">{{ __('Confirm Password') }}</label> <div class="col-md-6"> <input id="password-confirm" type="password" class="form-control" name="password_confirmation" required> </div> </div> <div class="form-group row mb-0"> <div class="col-md-6 offset-md-4"> <button type="submit" class="btn btn-primary"> {{ __('Register') }} </button> </div> </div> </form> </div> </div> </div> </div> </div> @endsection ~~~ 修改`admin/home.blade.php`代码如下: ~~~ @extends('layouts.admin') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card"> <div class="card-header">Admin Dashboard</div> <div class="card-body"> @if (session('status')) <div class="alert alert-success" role="alert"> {{ session('status') }} </div> @endif You are logged in the admin dashboard! </div> </div> </div> </div> </div> @endsection ~~~ 至此,后台用户注册登录的所有功能都实现了,在浏览器中访问`http://blog.test/admin`,就会跳转到后台登录页面。