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

2016-08-17 補充 Exception 部分改造方案的內容
2016-08-13 補充 View 部分改造方案的內容

背景

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

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


具體方案

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

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

本文以 Admin 為例進行說明,如需增加其他站點,進行類似改動即可。

Structure

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

php artisan make:auth

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

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

最終涉及到的文件樹如下。

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』。

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 的形式被 App\Http\Controllers\Auth\AuthController 使用的,而且都留好了自定義屬性可供改寫,例如下方先判斷若不存在 $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');
}
  1. @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 目錄下的文件在運行時沒有作用,所以不用處理。

  1. 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 的異常固定由 App\Exceptions\Handler 處理,所以需要在此類中針對不同站點分別指定錯誤處理 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);
        }
    }
}

參考

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容