1、背景
项目包含若干子站点,不同站点功能各异,但共享底层数据及逻辑。为开发及运维效率期间,决定在一个 Laravel 应用内实现整套系统。
本文基于 Laravel 5.2,主要介绍如何针对多站点分别进行用户认证的改造,用意是最大限度利用 Laravel 自带的认证系统。不过默认的认证都是根据 『email』和『password』字段进行的。之后有时间可能再追加自定义字段比如『phone』的改造方案,本文暂不涉及。
2、具体方案
为清晰起见,项目按照不同站点组织成不同模块。在 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 由于框架中若干处硬编码,所以没法直接移走,用到相关的功能需另想办法处理。
- 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'); }
- @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 目录下的文件在运行时没有作用,所以不用处理。 - 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); } } }