いよいよ本格的にLaravelを使ってみるのですが。
まずはログイン周りをきちんと作ろう。
※Laravel Ver8の認証処理も作ってみたよ。最新版を使う人は、こっちも見てね。
環境
ホストOS:Windows10
laravel開発環境:Homestead(だからubuntu & MySQL)
laravelバージョン:5.8.17
phpバージョン:7.3.8
#仕様
##サンプルサイトの構成
以下の画面を作成。
・ログイン画面
・メニュー画面
・照会画面
・更新画面
照会画面、更新画面はとりあえずダミーで、表示するだけにしておきます。
##ログイン方法
ログイン画面でID,パスワードを入力することでログインする。
今時ログインはメールアドレスでするって知っているけど、IDでログインしたいんだよ。許してくれ。
テーブル定義はデフォルトのマイグレーションファイルで作られているusersテーブルを使用する。
#実装
##make:authしてみる
バージョン6から手順が変わっています!!
下記参照してください
Laravel 6.0 ログイン機能を実装する
https://blog.capilano-fw.com/?p=4576
おじさんはnpmインストールするところで結構はまったけど。~~suでnpmコマンド実行してね。~~npmはホストOSで実行してね。
--ここからlaravel バージョン5.8の手順です--
なにしろコマンド一発で基本的な設定ができるっていうじゃないか。
試してみる。
$ php artisan make:auth
新旧フォルダーでコンペアしてみたら、以下の差異があった。
・/app/Http/ControllersにHomeController.phpが追加されていた。
・/resource/viewにauthフォルダ、layoutsフォルダ、home.blade.phpが追加されていた。
・/routes/web.phpに/homeのルートが追加されていた。
Auth::routes();
Route::get('/home', 'HomeController@index')->name('home');
このままlaravelを起動してみる。
ブラウザで/loginでアクセスすると、なんともう画面ができている。
右上の「Register」をクリックすると、ユーザー登録画面が表示される。
まだテーブル作ってなければ、マイグレーションしてから項目入力してRegisterボタンをクリック。
念のためマイグレーションは下記コマンドで実行
$ php artisan migrate
入力例
なんと、パスワードを「test」にしたら、「8文字以上入れろよ!」というエラーになった。
何にもしてないのに、Validationまでしちまうのか。laravelすげー。
言われた通り8文字にしてみる。
「Register」をクリックすると、完了画面が表示される。
右上にログインユーザーのNameが表示される。
ユーザー名をクリックすると、「Logout」が表示される。
「Logout」をクリックすると、Laravelのメニュー画面に戻る。
なんだ、もう出来たじゃないか。
##大枠の流れを作ってしまう
本当はここから認証周りのカスタマイズをするのが記事としては正しい気がするが、気持ち的に自分のやりたい動きを先に作ってしまいたくなる。
自分の気持ちに正直に生きることにして、全体の流れを作ってしまう。
・Login後のリダイレクト先をメニュー画面にする。
・メニューから照会画面に遷移する。
ログインしないで照会画面を開こうとしても、ログインしていないから開けないようにする。
以下のページを参考にさせていただきました。
LaravelのAuth認証機能をカスタマイズし意図した挙動へ変更するTips
###自作のメニュー画面へ遷移させる
とりあえず細かい理屈は気にせず作ってみる。Don't think,Feel.
####メニュー画面を作る
/resources/viewフォルダに/testフォルダを追加して、その中にmenu.blade.phpを作る。
そして/homeにリダイレクトしているところをすべて/menuに変更してみる。
・・・表示されないよ。ネットに書いてあった通りにやったのに。やっぱネットなんてフェイクと嘘とデマばっかりなんだ。
なんて思考停止していないで、考える。やっぱthink大事。
普通にMVC経験していた人なら、conntrollerもなくてなんで処理動くの?って思うことでしょう。
laravelではartisanでcontrollerも作れちゃう。
とりあえずTestフォルダ内にMenuControllerを作ってみます。
$php artisan make:controller Test/MenuController
コマンドを実行すると、/app/Http/Controllersの下にTestフォルダ(なかったら新規作成してくれる)とMenuController.phpが作成される。
<?php
namespace App\Http\Controllers\Test;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class MenuController extends Controller
{
//
}
中身はこれだけでした。
これだけじゃさすがに何も動かないよね。でもextendsしてくれたりするので、いちから作るときは便利かも。
ここに作成したmenu.blade.phpを表示するよう処理を記述する。
<?php
namespace App\Http\Controllers\Test;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class MenuController extends Controller
{
// 以下の記述を追加
public function menu()
{
return view('test/menu');
}
// ここまで
}
/resource/view/test/menu.blade.phpを表示するだけの内容です。
あと、Routeも追加する。
/routes/web.phpの最後に以下の処理を記述する。
Route::get('/test/menu', 'Test\MenuController@menu');
ちなみにviewはまだ適当な内容しか書いていない。
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<a href="">照会処理</a><br />
<a href="">更新処理</a>
</body>
</html>
この状態で、ブラウザからmenuにアクセスしてみる。
適当なメニューが表示された。
###Login画面からメニューに遷移させる
Loginしたら、この適当なメニューを表示させたいんだけど。
なので、LoginController.phpのredirectToを変更してみる。
// $redirectTo = 'home';から変更
protected $redirectTo = '/test/menu';
変更したら、Laravelのログイン画面から適当なメニューに遷移するかテストしてみる。
ちゃんと遷移できました。
しかーし!!
このままでは、ログインしていなくても/test/menuにアクセスできてしまう。
ちゃんと認証のチェックしようよ。
###認証チェック処理を組み込む
ログイン済みかどうかの判断は、Facades\AuthにあるAuth::check()で行うことができる。
<?php
namespace App\Http\Controllers\Test;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
// Facades\Authを追加
use Illuminate\Support\Facades\Auth;
class MenuController extends Controller
{
public function menu()
{
// ログインしていたら、test/menuを表示
if (Auth::check()) {
return view('test/menu');
} else {
// ログインしていなかったら、Login画面を表示
return view('auth/login');
}
}
}
これだけで、認証済みか判断できる。簡単!
###Logoutできるようにする
このままだとログアウトできないので、ログアウト処理を組み込んでみる。
デフォルトの処理ではユーザー名をクリックすると「Logout」が表示される。
home.blade.phpの中を見てみると。
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">Dashboard</div>
<div class="card-body">
@if (session('status'))
<div class="alert alert-success" role="alert">
{{ session('status') }}
</div>
@endif
You are logged in!
</div>
</div>
</div>
</div>
</div>
@endsection
Logoutの処理はここには書いてないらしい。上のほうに書いてあるから、layouts.appの中を見てみる。
layouts.appは/resources/views/layoutsの中のapp.blade.phpが本体らしい。
ログアウトに絡む処理は下記の部分。
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
<a class="dropdown-item" 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
</form>
</div>
ドロップダウンにlogoutのルートが設定されて、クリックしたらlogout-formを呼び出す様子。
展開されるとこうなる。
Javascriptが駆使されていて、getElementByIdしつつpreventDefaultするという。日本語で言うと、「id='logout-form'を取り込むよ。画面には出さないけど。」ていう感じか。
とりあえずこれを丸パクリする。
<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
@csrfは入れておかないと、419のエラーページが表示される。csrfトークンが有効期限切れ、ということらしい。csrfもデフォルト対応とは、手厚くてありがたい。
これで適当なメニュー画面からログアウトできるようになりました。
ログアウトをクリックすると。
##認可処理を組み込む
さて、照会処理は誰でもできるけど、更新処理は特定の人しかできない、なんて処理はどのように組み込めばいいか?
###ユーザ属性の追加
今回は単純に更新できる人かそうでないかをbooleanで持たせることにする。
userテーブルに項目を追加する。
/databases/migrationsのの中身を変えなくても項目追加ができる。まじか。
まずはartisan make:migrationで項目追加する処理を作成する。
※artisanコマンドでphpファイルを追加しないとmigrateをするときに項目追加として扱われないから注意。
$ php artisan make:migration add_column_users_table --table=users
正常終了すると、/databases/migrationsの下にadd_column_users_table.phpが作成される。
中身は例によってほぼ空。
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddColumnUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
//
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
//
});
}
}
これに追加する項目の処理を追加する。
public function up()
{
Schema::table('users', function (Blueprint $table) {
//
$table->boolean('admin_chk')->default(0)->after('password'); //これを追加
});
}
public function down()
{
Schema::table('users', function (Blueprint $table) {
//
$table->dropColumn('admin_chk'); //これを追加
});
}
そしてmigrate:refreshでテーブルを再作成します。データ消えちゃうけどね。
画面にも項目追加する。
/resources/auth/register.blade.phpに追加したupdateを追加する。
//追加した部分だけ
<div class="form-group row">
<label for="admin_chk" class="col-md-4 col-form-label text-md-right">{{ __('Admin') }}</label>
<div class="col-md-6">
<div>
{{ Form::hidden('admin_chk', '0') }}
{{ Form::checkbox('admin_chk', '1') }}
</div>
</div>
</div>
/app/user.phpにも追加した項目を追加する。
protected $fillable = [
'name', 'email', 'password','admin_chk',
];
ここだけの話、チェックボックスで結構はまった。上記のようにFormを使ったのだが、デフォルトのままではFormがインストールされていない。
下記サイトを参考にしました。ありがとう。
checkboxが未チェックのときの値を設定する方法
Laravelで「Formクラスが無い」とエラーが出た時の対処法
ちなみにapp.phpは/configの中にあるやつです。
さて、画面はcssがちゃんと当たってないので微妙な表示だが、とりあえずこれで進めてしまう。
updateをチェックした人としていない人をそれぞれ登録する。
###ぞんざいな照会画面、更新画面を作る
とりあえずテストなので、ぞんざいな画面をふたつ作る。
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<h1>照会画面</h1>
こんにちは!{{ Auth::user()->name }}<br /><br />
何か表示するよ!そのうちね
<br />
{!! Form::open() !!}
<button type="submit" name="action" value="back">{{ __('back') }}</button>
<br />
</body>
</html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<h1>更新処理</h1>
こんにちは!{{ Auth::user()->name }}<br /><br />
入力してね!本当は何も更新しないけど<br />
@if(session('adminmsg'))
{{ Session('adminmsg') }}<br />
@endif
{!! Form::open() !!}
<input type="text" name="inputdata">
<br /><br />
<button type="submit" name="action" value="update">{{__('update')}}</button>
<button type="submit" name="action" value="back">{{ __('back') }}</button>
<br />
</body>
</html>
一応updateボタンを押したらそれっぽく更新画面を出すようにした。実際は何も更新してないけどね。
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<h1>更新完了画面</h1>
こんにちは!{{ Auth::user()->name }}<br /><br />
{!! Form::open() !!}
これを入力したよね?<br />
{{ $inputdata }}<br /><br />
<button type="submit" name="action" value="back">{{ __('back') }}</button>
</body>
</html>
web.phpに上記画面のrouteを追加する。
//下記を追加
Route::post('/test/search', 'Test\MenuController@search')->name('search');
Route::post('/test/update', 'Test\MenuController@update')->name('update');
Route::post('/test/complete', 'Test\MenuController@complete')->name('complete');
MenuController.phpにbackボタンを押された場合の処理を追加する。
//下記を追加
public function search(Request $request )
{
$action = $request->get('action', 'back');
$input = $request->except('action');
if($request->action === 'back') {
return view('test/menu');
}
}
public function update(Request $request )
{
$action = $request->get('action', 'back');
$input = $request->expect('action');
if($request->action === 'back') {
return view('test/menu');
}
}
public function complete(Request $request )
{
$action = $request->get('action', 'back');
$input = $request->except('action');
if($request->action === 'back') {
return view('test/menu');
}
}
これでとりあえずぞんざいな画面が表示できるようになった。
updateボタンをクリックすると、更新完了画面に遷移する。
###認可処理を実装する
####概要
さて。これらの画面に、以下のような制約をつけたい。
・usersのadmin_chkが1の人は、照会画面も更新画面もアクセス可能。
・usersのadmin_chkが0の人は、照会画面しかアクセスできない。
laravelの認可処理には、GateとPolicyの二種類の方法がありんす。
勝手な解釈で、以下のような違いかな、と。
・Gate
認可する処理ひとつひとつを個別に作成する方法。
1.defineで認可処理を記述する。
2.defineでつけた名前で認可処理を行う
・Policy
特定のモデルやリソースに紐付けたようなクラスが作成できる。
CRUDに紐づけた認可処理を書けるので、updateやdeleteの時の認可処理などが簡単に書ける。
どっちでも同じように認可処理は書けるけど、全体的な認可処理はgate、細かい設定をしたいときはpolicyにするのがよさそう。
今回はざっくりなので、gateで認可処理を書いてみる。
####実装1 更新画面に認可処理を入れる
gateは/app/providersの中にあるAuthServiceProvider.phpの中に設定する。
public function boot()
{
$this->registerPolicies();
//以下の処理を追加
@Gate::define('admin', function (User $user) {
return $user->admin_chk == true;
});
}
usersテーブルのadmin_chkがtrueだったらtrueを返す、という設定。
これを受けてMenuController.phpに認可処理を追加する。
public function update(Request $request )
{
$action = $request->get('action', 'update');
$input = $request->except('action');
if($request->action === 'update') {
//認可処理
if(Gate::allows('admin')) {
$res = $request->input('inputdata');
return view('test/complete', ['inputdata' => $res]);
} else {
session()->flash('adminmsg', 'あんた更新できないよ!!');
return view('test/update');
}
}
$action = $request->get('action', 'back');
$input = $request->except('action');
if($request->action === 'back') {
return view('test/menu');
}
}
AuthServiceProvider.phpに設定した「admin」という条件を満たしているかチェックを行い、条件を満たしているとき=認可されたときの処理と、条件を満たさなかったとき=認可されなかったときの処理を記述する。今回は認可されたら更新完了画面を表示、認可されなかったらメッセージを出力するようにした。
権限のないテストユーザーでログインして、更新画面を表示する。
これでupdateボタンを押すと、認可エラーになる。
できた!できたよ!
####実装2 メニュー画面に認可処理を入れる
ここでひとつ疑問が。
どうせ更新処理できないんだったら、そもそもメニューに出すなよ。
ごもっともで。
それじゃあ、bladeのほうに認可処理を入れてみよう。
AuthServiceProvider.phpに設定した認可処理がtrueの時、@canと@endcanで囲まれた処理を実行する。
<html>
<head>
<meta charset="utf-8">
</head>
<body>
こんにちは!{{ Auth::user()->name }}<br /><br />
<a href="/test/search">照会処理</a><br />
@can('admin')
<a href="/test/update">更新処理</a><br /><br />
@endcan
<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
</form>
</body>
</html>
更新処理を表示する部分を@can-@endcanで囲んだので、admin_chkがtrueの人しか更新処理が表示されない。
見やすくするためにユーザー名を出すようにしたよ。
admin_chkがfalseの人の場合、更新処理は表示されない。
###独自項目でログインできるようにする
昨今はメアドでログインするのが常識だって、よくわかっているけどさ。
レガシーな人たちは、ユーザーIDとかでログインしたがるんだよね。
そういう訳で、「会員番号」という独自項目を作って、「会員番号」とパスワードでログインできるように変更する。
####項目を追加する
admin_chkを追加したadd_column_users_table.phpに、「memberno」を追加する。
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->boolean('admin_chk')->default(0)->after('password');
$table->string('memberno')->after('id')->unique();
});
}
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('admin_chk');
$table->dropColumn('memberno');
});
}
項目を追加したuserテーブルの構成はこんな感じ。
データはこんな感じで入れてみた。
ログインIDは/Illuminate/Foundation/Auth/内のAuthenticatesUsers.phpの中にある、usernameメソッドで定義しているそうな。
ここに「email」を返すよう設定してあるから、emailがログインIDになっている。
これを好きな項目に変えれば、ログインIDがその項目に変わる。
AuthenticatesUsers.phpはLoginController.phpがuseしているので、LoginController.phpでusernameメソッドをオーバーライドしてあげればよい。
Illminate配下のソースはあまりいじりたくないし。
public function username()
{
return 'memberno';
}
ログイン画面のemailを、membernoに変更。
<div class="form-group row">
<label for="memberno" class="col-md-4 col-form-label text-md-right">{{ __('Member No') }}</label>
<div class="col-md-6">
<input id="memberno" type="text" class="form-control @error('memberno') is-invalid @enderror" name="memberno" value="{{ old('memberno') }}" required autocomplete="memberno" autofocus>
@error('memberno')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
ちゃんとログインできました。
これで認証・認可処理をざっくり実装できました。ありがとう!