Laravelのマルチ認証
の記事はたいていコピペ
を多用してます。
コピペの多用
はバグの温床
になりやすいので条件分岐
で極力減らしてみました。
前提条件: Laravel5.5 インストール
####.env(DB設定)
//DB接続設定
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE={データベース名}
DB_USERNAME={user}
DB_PASSWORD={password}
####AppServiceProvider.php
//DBの191文字超えエラー回避設定
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
++ \Illuminate\Support\Facades\Schema::defaultStringLength(191);
}
}
####Authとマイグレーション
//認証のインストール
$ php artisan make:auth
//認証用DBテーブル作成
$ php artisan migrate
//-> DBにusers・password_resetsテーブルが作成されたか確認。
####Adminモデルの生成
$ php artisan make:model Admin -m
//-> App/Admin.php
//-> database/migrations/{日付}_create_admins_table.php
####Adminモデル
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class Admin extends Authenticatable //modify
{
use Notifiable;
protected $fillable = [
'name', 'email', 'password',
];
protected $hidden = [
'password', 'remember_token',
];
}
####Adminテーブルのマイグレーション
create_users_table.php
の内容をコピーして変更
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateAdminsTable extends Migration
{
public function up()
{
Schema::create('admins', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('admins');
}
}
####adminテーブルのマイグレーション実行
$ php artisan migrate
//adminsテーブルが生成されたか確認
####tinker -> adminユーザーの生成
password_hash(公式)
$ php artisan tinker
>>> $admin = new App\Admin;
>>> $admin->name = 'admin';
>>> $admin->email = 'test@test.com';
>>> $admin->password = password_hash('password', PASSWORD_DEFAULT);
>>> $admin->save();
//phpMyadmin等でユーザー生成の確認
####auth.php(公式)
admin側の定義の追加
<?php
return [
'defaults' => [
'guard' => 'user', //modify
'passwords' => 'users',
],
'guards' => [
'api' => [
'driver' => 'token',
'provider' => 'users',
],
'user' => [ //modify
'driver' => 'session',
'provider' => 'users',
],
+ 'admin' => [
+ 'driver' => 'session',
+ 'provider' => 'admins',
+ ],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
+ 'admins' => [
+ 'driver' => 'eloquent',
+ 'model' => App\Admin::class,
+ ],
],
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
],
+ 'admins' => [
+ 'provider' => 'admins',
+ 'table' => 'password_resets',
+ 'expire' => 60,
+ ],
],
];
Laravelの認証機能は「ガード」
と「プロバイダ」
を中心概念として構成されています。ガードは各リクエストごと
に、どのようにユーザーを認証するか
を定義します。たとえば、Laravelにはセッションストレージ
とクッキー
を使いながら状態を維持するsessionガード
が用意されています。
####ログインしていないときのリダイレクト先
<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Auth\AuthenticationException; //add
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
protected $dontReport = [
//
];
protected $dontFlash = [
'password',
'password_confirmation',
];
public function report(Exception $exception)
{
parent::report($exception);
}
public function render($request, Exception $exception)
{
return parent::render($request, $exception);
}
//未ログイン時 -> ガード属性のログインページにリダイレクト
public function unauthenticated($request, AuthenticationException $exception)
{
if($request->expectsJson()){
return response()->json(['message' => $exception->getMessage()], 401);
}
if (in_array('admin', $exception->guards())) {
return redirect()->guest(route('admin.login'));
}
return redirect()->guest(route('login'));
}
}
####ルーティング
<?php
Auth::routes();
// User 認証不要
Route::get('/', function () {
return redirect('home');
});
// User ログイン後
Route::group(['middleware' => 'auth:user'], function() {
Route::get('home', 'HomeController@index')->name('home');
});
// Admin 認証不要
Route::group(['prefix' => 'admin'], function() {
Route::get('login', 'Admin\LoginController@showLoginForm')->name('admin.login');
Route::post('login', 'Admin\LoginController@login');
});
// Admin ログイン後
Route::group(['prefix' => 'admin', 'middleware' => 'auth:admin'], function() {
Route::post('logout', 'Admin\LoginController@logout')->name('admin.logout');
Route::get('home', 'HomeController@index')->name('admin.home');
});
####コントローラーの構成
adminフォルダ
を作成してAuthフォルダ
からLoginController.php
をコピーします。
####LoginController.php(Auth)
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request; //add
use Illuminate\Support\Facades\Auth; //add
class LoginController extends Controller
{
use AuthenticatesUsers;
protected $redirectTo = '/home';
public function __construct()
{
$this->middleware('guest')->except('logout');
}
public function logout(Request $request)
{
Auth::guard('user')->logout(); //modify
return redirect('login'); //modify
}
}
####LoginController.php(Admin)
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request; //add
use Illuminate\Support\Facades\Auth; //add
class LoginController extends Controller
{
use AuthenticatesUsers;
protected $redirectTo = 'admin/home'; //modify
public function __construct()
{
$this->middleware('guest:admin')->except('logout');//modify
}
public function showLoginForm()
{
//login.blade.phpは共用
return view('auth.login'); //modify
}
protected function guard()
{
return Auth::guard('admin'); //modify
}
//以下追加
public function logout(Request $request)
{
Auth::guard('admin')->logout();
return redirect('admin/login');
}
}
####ログイン後のリダイレクト先
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
{
public function handle($request, Closure $next, $guard = null) {
if (Auth::guard($guard)->check()) {
switch ($guard) {
case 'admin':
return redirect('/admin/home');
break;
default:
return redirect('/home');
}
}
return $next($request);
}
}
####ヘルパー関数の定義(権限を便利に利用するための関数)
参考:ヘルパ関数の自作
<?php
use Illuminate\Support\Facades\Auth;
//ユーザー情報の取得
if (! function_exists('userInfo')) {
function userInfo() {
return Auth::guard()->user();
}
}
//ログインしているか?
if (! function_exists('isLogin')) {
function isLogin() {
return !empty(userInfo());
}
}
//ユーザーの属性を取得する
if (! function_exists('getUserType')) {
function getUserType() {
if (isLogin()) {
switch (get_class(userInfo())) {
case 'App\Admin':
$userType = 'Admin';
break;
default:
$userType = 'User';
}
} else {
$userType = 'Guest';
}
return $userType;
}
}
//現在のページが管理者用か?-> adminのRouteには'admin'のプレフィクスをつける。
if (! function_exists('isAdminRoute')) {
function isAdminRoute() {
return strpos(\Route::currentRouteName(), 'admin') !== false;
}
}
if (! function_exists('isAdminLogin')) {
function isAdminLogin() {
return isAdminRoute() && strpos(\Route::currentRouteName(), 'login') !== false;
}
}
####コントローラーの使用例(条件分岐の例)
HomeController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; //add
class HomeController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function index()
{
//string: ユーザー属性を取得してテスト
$userType = getUserType();
dd($userType);
return view('home');
}
}
####ビュー
getUserType()で属性を判断できます。
ビュー
はコピペせずに条件分岐でuser
とadmin
の制御を行います。
####app.blade.php
~~略~~
<!-- Styles -->
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
@if (isAdminRoute())
<style>body{background-color: tomato;}</style>
@endif
~~略~~
<!-- Branding Image -->
@if (!isLogin())
<a class="navbar-brand" href="{{ url('/') }}">
{{ config('app.name', 'Laravel') }}</a>
@elseif (isLogin() && getUserType() == 'User')
<a class="navbar-brand" href="{{ route('home') }}">
{{ config('app.name', 'Laravel') }}</a>
@elseif (isLogin() && getUserType() == 'Admin')
<a class="navbar-brand" href="{{ route('admin.home') }}">
{{ config('app.name', 'Laravel') }}</a>
@endif
~~略~~
<!-- Authentication Links -->
@if (isLogin() && !isAdminLogin())
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false" aria-haspopup="true" v-pre>
{{ userInfo()->name }} <span class="caret"></span>
</a>
@elseif (!isLogin())
<li><a href="{{ route('login') }}">Login</a></li>
<li><a href="{{ route('register') }}">Register</a></li>
@elseif (!isLogin() || isAdminLogin())
<li><a href="{{ route('admin.login') }}">Login</a></li>
@endif
@if (isLogin() && getUserType() == 'User')
<ul class="dropdown-menu">
<li>
<a href="{{ route('logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">Logout</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
{{ csrf_field() }}
</form>
</li>
</ul>
@elseif (isLogin() && getUserType() == 'Admin')
<ul class="dropdown-menu">
<li>
<a 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_field() }}
</form>
</li>
</ul>
@endif
~~略~~
####Login.blade.php
~~略~~
@if ((!isLogin() && isAdminRoute()) || (getUserType() == 'User' && isAdminLogin()))
<form class="form-horizontal" method="POST" action="{{ route('admin.login') }}">
@elseif (!isLogin() && !isAdminRoute())
<form class="form-horizontal" method="POST" action="{{ route('login') }}">
@endif
{{ csrf_field() }}
####Home.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">
<?php $user = userInfo(); ?>
{{ $user->name . 'さん: ' }}
{{ $user->id . ', ' }}
{{ $user->email }}
{{ '属性: ' . getUserType() }}
</div>
<div class="panel-body">
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
You are logged in!
</div>
</div>
</div>
</div>
</div>
@endsection
いかがでしたでしょうか?
条件分岐
のやり方を押さえることができれば改変・応用
は簡単です。
少なくとも1つ機能を増やすためにファイル間を移動
することは確実に減る
でしょう。
改修
やデバッグ作業
も同様です。
管理権限を増やす
のも楽だと思います。
LGTMお願いします!
ストックのついでにお願いします!
モチベーションがあがります!
補足
オブジェクト指向を学び始めてから、この記事は中途半端だったなあと思います笑
1.UIはBaseを継承させる
2.Factoryで継承済みUIを自動でチョイスさせる
UI内でIfを使いまくってる部分を直したいw