Laravel 实践 – 多后台登录

一个简单站点,前台需要提供用户登录,后台需要实现管理员登录。这里就涉及两套登录系统。

前台的用户登录直接使用默认的。按照文档设置:

php artisan make:auth
php artisan migrate

以上命令最终添加路由和产生视图文件和产生一张用户表。
0 首先拷贝users为admins,password_resets为admin_password_resets; 拷贝app/User.php为app/Admin.php。

1 打开routes/web.php,添加路由

// 后台登录注册流程
Route::get('backend', 'Backend\Dashboard@index');
// 后台登录-Dashboard
Route::get('backend/dashboard', 'Backend\Dashboard@index')->name('backend.dashboard');
// 后台登录-认证
$this->get('backend/login', 'Backend\Auth\LoginController@showLoginForm')->name('backend.login');
$this->post('backend/login', 'Backend\Auth\LoginController@login');
$this->any('backend/logout', 'Backend\Auth\LoginController@logout')->name('backend.logout');
// 后台登录-注册
$this->get('backend/register', 'Backend\Auth\RegisterController@showRegistrationForm')->name('backend.register');
$this->post('backend/register', 'Backend\Auth\RegisterController@register');
// 后台登录-找回密码
$this->get('backend/password/reset', 'Backend\Auth\ForgotPasswordController@showLinkRequestForm')->name('backend.password.request');
$this->post('backend/password/email', 'Backend\Auth\ForgotPasswordController@sendResetLinkEmail')->name('backend.password.email');
$this->get('backend/password/reset/{token}', 'Backend\Auth\ResetPasswordController@showResetForm')->name('backend.password.reset');
$this->post('backend/password/reset', 'Backend\Auth\ResetPasswordController@reset');

2 进入app/Http/Controllers,建立目录Backend,把app/Http/Controllers下的Auth和home.php拷贝到新建的目录中(backend目录)。

#文件: app/Http/Controllers/Backend/Home.php
<?php
// 命名空间改为Backend
namespace App\Http\Controllers\Backend;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class Dashboard extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        // 中间件改为auth:admin
        // 原来为auth
        $this->middleware('auth:admin');
    }

    /**
     * Show the application dashboard.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        // 视图文件改为backend.home
        // 原来为home
        return view('backend.home');
    }
}

#文件: app/Http/Controllers/Backend/Auth/LoginController.php
<?php
// 命名空间改为Backend\Auth
namespace App\Http\Controllers\Backend\Auth;

use Illuminate\Http\Request;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;

class LoginController extends Controller
{
    use AuthenticatesUsers;

    // 登录后重定向改为/backend/home
    // 原来为home
    protected $redirectTo = '/backend/home';

    public function __construct()
    {
        // 中间件改为guest:admin
        // 原来为guest
        // 注:except不需要修改,except表示此中间件忽略logout方法
        $this->middleware('guest:admin')->except('logout');
    }

    // 新增方法,对应修改视图
    public function showLoginForm()
    {
        return view('backend.auth.login');
    }

    // 新增方法,指定使用admin这个拦截器
    protected function guard()
    {
        return Auth::guard('admin');
    }

    // 新增方法,管理员登出
    public function logout(Request $request)
    {
        $this->guard('admin')->logout();

        $request->session()->invalidate();

        return redirect('/backend/home');
    }
}

#文件: app/Http/Controllers/Backend/Auth/ForgotPasswordController.php
<?php
// 修改命名空间为Backend\Auth
namespace App\Http\Controllers\Backend\Auth;

use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class ForgotPasswordController extends Controller
{
    use SendsPasswordResetEmails;

    // 修改中间件(添加admin)
    public function __construct()
    {
        $this->middleware('guest:admin');
    }

    // 添加方法,修正视图
    public function showLinkRequestForm()
    {
        return view('backend.auth.passwords.email');
    }
}

#文件: app/Http/Controllers/Backend/Auth/RegisterController.php
<?php
// 修改命名空间为Backend\Auth
namespace App\Http\Controllers\Backend\Auth;

use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RegistersUsers;
use App\Http\Controllers\Controller;
use App\Models\Admin;

class RegisterController extends Controller
{

    use RegistersUsers;

    // 修正为/backend/home,原来为home
    protected $redirectTo = '/backend/home';

    // 修正中间件为guest:admin
    public function __construct()
    {
        $this->middleware('guest:admin');
    }

    // 验证时针对的表改为unique:admins(原来为unique:users)
    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\Models\Admin   改为\App\Models\Admin
     */
    protected function create(array $data)
    {
        // 模型修改为Admin
        return Admin::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
        ]);
    }
    // 新增,注册成功后登陆
    protected function guard()
    {
        return Auth::guard('admin');
    }

    // 新增,修正视图
    public function showRegistrationForm()
    {
        return view('backend.auth.register');
    }
}

#文件: app/Http/Controllers/Backend/Auth/ResetPasswordController.php
<?php
// 修正命名空间为:Backend\Auth
namespace App\Http\Controllers\Backend\Auth;

// 新加Auth和Pawword
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Password;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class ResetPasswordController extends Controller
{
    use ResetsPasswords;

    // 修正为/backend/home,原来为home
    protected $redirectTo = '/backend/home';

    // 修正中间件为guest:admin
    public function __construct()
    {
        $this->middleware('guest:admin');
    }

    // 新增,指定broker为admins(config/auth.php中设置)
    public function broker()
    {
        return Password::broker('admins');
    }

    // 新增,重设密码后需要自动登录,设置使用正确的Guard
    protected function guard()
    {
        return Auth::guard('admin');
    }

    // 新增,修正视图
    public function showResetForm(Request $request, $token = null)
    {
        return view('backend.auth.passwords.reset')->with(
            ['token' => $token, 'email' => $request->email]
        );
    }
}

3 布局视图等
进入resources/views/layouts,拷贝app.blade.php为app_backend.blade.php。
进入resources/views,建立目录backend,把resources/views下的auth和home.php拷贝到新建的目录中(backend目录)。

进入到backend目录,对各个文件修改引用布局文件由app改为app_backend(@extends(‘layouts.app_backend’))。

然后对各个视图文件(包括resources/views/layouts/app_backend.blade.php)做修改(没有忽略):

@guest 改为 @guest('admin')
Auth::user() 改为 Auth::guard('admin')->user()
route('login') 改为 route('backend.login')
route('password.request') 改为 route('backend.password.request')
route('password.email') 改为 route('backend.password.email')

4 修改配置

#文件:config/auth.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,
        ],
        // 新增,provider需要准确对应providers数组
        'admins' => [
            'provider' => 'admins',
            'table' => 'admin_password_resets',
            'expire' => 60,
        ],
    ],
];

5 调整中间件

#文件 app/Http/Middleware/RedirectIfAuthenticated
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;

class RedirectIfAuthenticated
{
    public function handle($request, Closure $next, $guard = null)
    {
        // 检查是否处于登录状态
        if (Auth::guard($guard)->check()) {
            if ($guard === 'admin') {
                return redirect('/backend/home');
            } else {
                return redirect('/home');
            }
        }

        return $next($request);
    }
}

注意,RedirectIfAuthenticated中间件别名是guest,实际也是调用对应Guard来判断是否是Guest。 这个Guest中间件只针对认证路由,用来判断当访问认证路由时如果已经登录那么将跳转到什么地方(这个跟auth中间件完全不同,auth中间件用于需要认证的路由)。

#涉及路由
backend/login
backend/register
backend/password/reset
backend/password/email
backend/password/reset/{token}

注意,backend/logout被排除了,logout不需要检查是否处于登录状态。

6 异常渲染调整
访问一个需要认证的页面(比如backend/home),这个时候如果检查到没有登录(认证不成功,auth中间件),那么这个时候就需要重定向到首页(如果是JSON,则返回JSON),默认直接跳转到/home没有问题,但是由于现在多加了一个登录,那么就必须针对这个做处理,否则总是跳转到/home。认证不通过抛出\Illuminate\Auth\AuthenticationException异常,这个异常中包括是由什么Guard认证不通过而抛的异常,异常处理自定义可以通过app/Exception/Handler.php来处理:

public function render($request, Exception $exception)
    {
        if ($exception instanceof \Illuminate\Auth\AuthenticationException) {
            $guards = $exception->guards();
            if (is_array($guards) && in_array('admin', $guards)) {
                return $request->expectsJson()
                    ? response()->json(['message' => $exception->getMessage()], 401)
                    : redirect()->guest(route('backend.login'));
            }
        }
        return parent::render($request, $exception);
    }

这里判断如果是admin这个Guard认证不通过抛的\Illuminate\Auth\AuthenticationException异常,则让其跳转到backend.login。

7 其它
对于使用了admin这个Guard保护的路由,需要使用auth:admin(默认是auth)。

如果登录了,session(‘status’)是ture,前后台登录都是如此。所以前后台登录由于使用不同的Guard,所以不存在问题。登出就有问题,如果前后台都已登录,其一登出,那么全部登出(由于销户Session的原因),不过这个没有什么关系了。