Laravel 多用戶認證系統改造方案

原文發表於 http://www.jianshu.com/p/d6c112f27661php

背景

項目包含若干子站點,不一樣站點功能各異,但共享底層數據及邏輯。爲開發及運維效率期間,決定在一個 Laravel 應用內實現整套系統。html

本文基於 Laravel 5.2,主要介紹如何針對多站點分別進行用戶認證的改造,用意是最大限度利用 Laravel 自帶的認證系統。不過默認的認證都是根據 『email』和『password』字段進行的。以後有時間可能再追加自定義字段好比『phone』的改造方案,本文暫不涉及。laravel

具體方案

爲清晰起見,項目按照不一樣站點組織成不一樣模塊。在 Laravel 原有目錄結構基礎內,分別給各個站點創設目錄。bash

laravel 5.2 project
├── app
│   └── Http
│       └── Controllers
│           ├── Site1
│           └── Site2
└── resources
    └── views
        ├── site1
        └── site2

本文以 Admin 爲例進行說明,如需增長其餘站點,進行相似改動便可。session

Structure

執行下列命令生成默認路由、控制器及視圖。app

php artisan make:auth

將默認的控制器和視圖結構分別複製到子模塊下,並建立相關模型、遷移表、修改路由、認證配置。框架

本例中,分別在 app/Http/Controllers 及 resources/views 下新建 Admin 及 admin 目錄,全部該站點相關的 Controller 及 view 均放置在上述兩目錄下。運維

最終涉及到的文件樹以下。dom

laravel 5.2 project
├── app
│   ├── Exceptions
│   │   └── Handler.php (變動)
│   ├── Http
│   │   ├── Controllers
│   │   │   └── Admin (新建)
│   │   │       ├── Auth
│   │   │       │   ├── AuthController.php
│   │   │       │   └── PasswordController.php
│   │   │       └── HomeController.php
│   │   └── routes.php (變動)
│   └── Admin.php (新建)
├── config
│   └── auth.php (變動)
├── database
│   └── migrations
│       ├── 2014_10_12_000000_create_admins_table.php (新建)
│       └── 2014_10_12_100000_create_admin_password_resets_table.php (新建)
└── resources
    └── views
        └── admin (新建)
            ├── auth
            │   ├── emails
            │   │   └── password.blade.php
            │   ├── login.blade.php
            │   ├── passwords
            │   │   ├── email.blade.php
            │   │   └── reset.blade.php
            │   └── register.blade.php
            ├── errors
            │   └── 503.blade.php
            ├── home.blade.php
            ├── layouts
            │   └── app.blade.php
            └── welcome.blade.php

Config

針對 Admin 新建 provider(下面的 admins),使用 Admin 的 Model。
針對 Admin 新建 guard,使用新建的 provider——『admins』。ide

config/auth.php

'guards' => [
    'admins' => [
        'driver' => 'session',
        'provider' => 'admins',
    ],
],
'providers' => [
    'admins' => [
        'driver' => 'eloquent',
        'model' => App\Admin::class, // 使用自定義的 Model(Admin)
    ],
],

Admin 站點使用 Auth 門面時均需指定 guard 爲 admins,例如得到當前登陸用戶:Auth::guard('admins')->user()。

Router

這裏的做用固然是讓訪問 Admin 站點的請求被轉入該站點的 Controller。
本例中,不一樣系統是以站點域名來區分的,固然也能夠用別的方法。

app/Http/routes.php

Route::group(['domain' => 'admin.example.com', 'namespace' => 'Admin'], function () { // 以前將默認認證相關類保持結構複製到了 Admin 下,此時只需簡單指定公共命名空間便可
    Route::auth(); // 各類註冊、登陸、找回密碼的默認路由
    Route::group(['middleware' => ['auth:admins']], function () { // 指定 auth 的 guard 爲 新建的 admins
        Route::get('/', 'HomeController@index'); // 登陸成功才能訪問的部分放在認證保護內
    });
});

View

咱們須要針對不一樣的站點使用不一樣的樣式或者用戶認證邏輯,因此將原 resources/views 下文件複製到 resources/views/admin 下,同時針對路徑變動修改代碼。

resources/views/vendor 因爲框架中若干處硬編碼,因此無法直接移走,用到相關的功能需另想辦法處理。

  1. view() 方法
    例如 view('auth、view('home、view('layouts、view('welcome,初始狀態下,這種形式的使用場所以下。

    app/Http/Controllers/HomeController.php:27:        return view('home');
    app/Http/routes.php:15:    return view('welcome');
    vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/controllers/HomeController.stub:27:        return view('home');
    vendor/laravel/framework/src/Illuminate/Foundation/Auth/AuthenticatesUsers.php:37:        return view('auth.login');
    vendor/laravel/framework/src/Illuminate/Foundation/Auth/RegistersUsers.php:33:        return view('auth.register');
    vendor/laravel/framework/src/Illuminate/Foundation/Auth/ResetsPasswords.php:49:            return view('auth.passwords.email');
    vendor/laravel/framework/src/Illuminate/Foundation/Auth/ResetsPasswords.php:52:        return view('auth.password');
    vendor/laravel/framework/src/Illuminate/Foundation/Auth/ResetsPasswords.php:194:            return view('auth.passwords.reset')->with(compact('token', 'email'));
    vendor/laravel/framework/src/Illuminate/Foundation/Auth/ResetsPasswords.php:197:        return view('auth.reset')->with(compact('token', 'email'));

    app 目錄下的 view 直接加上 admin. 的前綴就好。
    vendor 目錄下的都是經過 trait 的形式被 AppHttpControllersAuthAuthController 使用的,並且都留好了自定義屬性可供改寫,例以下方先判斷若不存在 $this->loginView,才使用默認的 auth.login。因此咱們只須要繼承默認的 AuthController 並指定這些相關屬性便可。(參見 Controller 部分介紹)

    vendor/laravel/framework/src/Illuminate/Foundation/Auth/AuthenticatesUsers.php

    public function showLoginForm()
    {
        $view = property_exists($this, 'loginView')
                    ? $this->loginView : 'auth.authenticate';
    
        if (view()->exists($view)) {
            return view($view);
        }
    
        return view('auth.login');
    }
  2. @include()@extends() 方法
    一樣,仍是針對 auth、home、layouts、welcome 進行處理,初始狀態下 Laravel 只使用到了 layouts.app 這個文件。

    resources/views/auth/login.blade.php:1:@extends('layouts.app')
    resources/views/auth/passwords/email.blade.php:1:@extends('layouts.app')
    resources/views/auth/passwords/reset.blade.php:1:@extends('layouts.app')
    resources/views/auth/register.blade.php:1:@extends('layouts.app')
    resources/views/home.blade.php:1:@extends('layouts.app')
    resources/views/welcome.blade.php:1:@extends('layouts.app')
    vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/views/auth/login.stub:1:@extends('layouts.app')
    vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/views/auth/passwords/email.stub:1:@extends('layouts.app')
    vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/views/auth/passwords/reset.stub:1:@extends('layouts.app')
    vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/views/auth/register.stub:1:@extends('layouts.app')
    vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/views/home.stub:1:@extends('layouts.app')
    vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/views/welcome.stub:1:@extends('layouts.app')

    resources 目錄下的只須要簡單加上前綴 admin. 便可。
    vendor 目錄下的文件在運行時沒有做用,因此不用處理。

  3. Auth 門面
    因爲 Admin 站點新建了 guard,因此使用默認 Auth 門面的場合也須要更改。

    resources/views/layouts/app.blade.php:56:                    @if (Auth::guest())
    resources/views/layouts/app.blade.php:62:                                {{ Auth::user()->name }} <span class="caret"></span>
    vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/views/layouts/app.stub:56:                    @if (Auth::guest())
    vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/views/layouts/app.stub:62:                                {{ Auth::user()->name }} <span class="caret"></span>

    resources 目錄下,用 Admin::guard('admins')-> 替換 Auth:: 來指定 guard。
    vendor 目錄下的文件在運行時沒有做用,因此不用處理。

Migration & Model

畢竟是新增的站點,參考自帶的 users/password_resets 新建相關的 table 及 model 便可。

Controller

將原 app/Http/Controllers 下相關結構複製到 Admin 下後,修改 namespace,而後針對 Admin 相關進行變更。

此處以 AuthController 爲例,PasswordController 相似:

app/Http/Controllers/Admin/Auth/AuthController.php

use App\Http\Controllers\Auth\AuthController as BaseAuthController;

class AuthController extends BaseAuthController // 簡單起見直接繼承默認類
{

    /* 這裏的屬性都是給 trait 用的,因此不能指定爲 private */
    protected $guard = 'admins'; // 指定 config/auth.php 中 guard
    protected $loginView = 'admin.auth.login'; // 指定登陸用 view
    protected $registerView = 'admin.auth.register'; // 指定註冊用 view

    protected function validator(array $data) // 註冊時使用的驗證規則
    {
        return Validator::make($data, [ // 根據 Admin 所需的信息修改驗證配置
            'name' => 'required|max:255',
            'email' => 'required|email|max:255|unique:admins', // 使用 admins 表
            'password' => 'required|confirmed|min:6',
            'terms' => 'required',
        ]);
    }

    protected function create(array $data)
    {
        return Admin::create([ // 使用自定義的 Model(Admin)
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
        ]);
    }
}

Exception

若是須要自定義 error 頁面(以前把 resources/views/errors 移到 resources/views/admin/errors)的狀況下,因爲 Laravel 的異常固定由 AppExceptionsHandler 處理,因此須要在此類中針對不一樣站點分別指定錯誤處理 view。具體來講是修改指定錯誤頁的邏輯,在子類中根據請求的域名(本例的區分規則)指定該站點的錯誤頁 view。

app/Exceptions/Handler.php

class Handler extends ExceptionHandler
{
    protected $host;

    public function render($request, Exception $e)
    {
        $this->host = $request->getHost(); // 記錄請求站點的域名
        return parent::render($request, $e);
    }

    protected function renderHttpException(HttpException $e) // 修改並覆蓋父類的方法,用於指定子站點錯誤頁 view
    {
        switch($this->host)
        {
            case 'admin.example.com': // 針對 Admin 站點指定路徑中的 admin 
                $prefix = 'admin';
                break;
        }
        $prefix = empty($prefix) ? '' : $prefix . '.';

        $status = $e->getStatusCode();
        $errorView = $prefix . "errors.{$status}"; // 指定錯誤頁 view
        
        if (view()->exists($errorView)) {
            return response()->view($errorView, ['exception' => $e], $status, $e->getHeaders());
        } else {
            return $this->convertExceptionToResponse($e);
        }
    }
}

參考

整個改造過程當中,主要參考了以下資料來源,感謝各位做者的同時也一併放出參考。

相關文章
相關標籤/搜索