- 投稿日:2019-10-21T23:45:52+09:00
LaravelにVue.jsを導入したらバグった
練習用の学習プロジェクトを仕様変更しようと思い立ち、せっかくだから勉強中のVue.jsを導入してみようとやってみた。
・・初っ端からいきなりレンダリングがバグった。
<input type = "number" id = "text_num{{$item->id}}" name="text_num{{$item->id}}" style="display:inline-block;width:30px;height:20px" min="1" max="9" value=1>ちょっとこのブログだとワードラップするのでわかりにくいけど、
上のコードは真ん中で改行が入ってます。
結構長めのタグで画面を開発画面を大きくはみ出て読めないので改行いれてました。今まで問題ありませんでしたが、なぜかbladeにvueを混ぜるとタグの間に改行入れると写真のように改行したところから下のコードがむき出しでレンダリングされました。
とりあえず改行して一行にしたら治りました。
何かエスケープコードかアノテーションみたいなのでキチンと直す方法があるのかもしれませんが今のところわかりません。コメントのご指摘より追記
改行のせいじゃなくて、全角スペースが入っている事が根本的な原因だったようです。
失礼しました。
- 投稿日:2019-10-21T23:29:14+09:00
メモ
Laradock を使って nginx, Laravel, PHP, MySQL, phpMyAdmin のdocker環境を構築
- ドキュメント 2.2 Installation A.2 の条件に書いてあるコマンドを実行してもmysqlのコンテナが落ちて動かない...
- MySQLのバージョンが8以上だと MySQLサーバー側で使われる認証方式がLaravel側でサポートされていないため認証に失敗する
- ~/laradock/.env の MYSQL_VERSION を latest から 5.7.28 に変更
- パスの指定がデフォルトの状態だとファイルが生成されてない
- ~/laradock/.env の DATA_PATH_HOST を ~/.laradock/data から .laradock/data に変更
- ~/laradock/.env の設定内容
- APP_CODE_PATH_HOST=../laravel/
- DATA_PATH_HOST=.laradock/data
- MYSQL_VERSION=5.7.28
- MYSQL_DATABASE=laravel
- MYSQL_USER=laravel
- MYSQL_PASSWORD=laravel
- MYSQL_PORT=3306
- MYSQL_ROOT_PASSWORD=root
ディレクトリ
project
├── laradock/ (<- git clone https://github.com/laradock/laradock.git)
└── laravel/ターミナルからlaradockディレクトリに移動して docker-compose up -d nginx mysql phpmyadmin workspace のコマンド(20分位かかる)を実行したあと docker-compose exec --user=laradock workspace bash コマンドで workspace に入って laravelをインストール composer config -g repositories.packagist composer https://packagist.jp && composer global require hirak/prestissimo && composer create-project laravel/laravel .
- 投稿日:2019-10-21T23:15:57+09:00
AH01630: client denied by server configuration | Lazy Service
Lazy Serviceを使う際のエラー対処メモ1
事象
Windows 10環境でLaravelプロジェクトを開発しているが、Apache動作がうまくできず、WEB上にForbiddenエラー発生してしまう。
操作手順は下記通り。
1.Laravelプロジェクト(myproject)を新規作成する。
Laravelプロジェクトの作成方法は「WindowsでのLaravel+SQLite3開発環境を構築する」を参照ください。
2.Lazy Serviceアプリを起動してDocumentRootに"C:\TEST\myproject\public"を指定した。
3.APACHE -> SAVEでhttpd.confを保存する。
4.SERVER -> START でApache2.4を起動する。
5.ブラウザに "http://www.example.com" を入力し表示すると、下記のForbiddenメッセージが表示される。
一旦、Lazy ServiceのApacheログ(C:\Program Files\LazyService\Apache\2.4\logs\error.log)を覗いてみる。
下記エラーログがあった。errors.log[Mon Oct 21 21:10:37.589626 2019] [authz_core:error] [pid 14772:tid 1180] [client 192.168.x.x:57461] AH01630: client denied by server configuration: C:/TEST/myproject/public/対応方法
APACHE -> Directory -> Section -> "Directory"を選択して、+ボタンをクリックする。
Direcotry設定子画面が表示され、以下のように各値を設定する
Options FollowSymlinks Includes AllowOverride All Require all granted設定後、「OK」ボタンをクリックする。赤枠1行が追加された。それで"C:/TEST/myproject/public/"へのアクセス権限を付与した。
続いで、APACHE -> SAVEでhttpd.confを保存し、SERVER -> START でAPACHEを再起動する。
ブラウザに "http://www.example.com" で再度表示すると今回OK。
Lazy Serviceの良いことはhttpd.confなど設定ファイルを開く必要がなく、画面操作だけ開発環境が楽にできてくれる。
- 投稿日:2019-10-21T20:09:12+09:00
LaravelでDBの区分値をEnum化していい感じに扱う
ソフトウェアでテーブル内の区分値を扱う方法は様々あるかと思いますが、個人的に扱いやすくできたので備忘録です。
本稿における区分値とは以下のような各種区分をDB上で扱いやすくするために、数値化などをおこなった値を指しています。性別区分[ 'gender' => [ 0 => 'not known', 1 => 'male', 2 => 'female', 9 => 'not applicable' ] ]権限区分[ 'roles' => [ 1 => 'developer', 2 => 'administrator', 3 => 'staff' ] ]TL;DR
myclabs/php-enum を使用して、区分値をEnum化し、Model内でよしなにする。
Enumって何
Enumとは
Enumerate
の略で列挙
を意味します。列挙型は、その名の通り定数を列挙したリストのようなものを作成することができ、区分値を扱う際によく利用されます。実装にもよりますが、値が同じでも異なるオブジェクトとして扱うことができるというメリットがあります。例えば、前述の性別と権限の区分値を定数として実装した場合
MALE === DEVELOPER
はどちらも値が1
であるためtrue
となり、バグを生み出してしまう可能性があります。Enumは、言語によっては組み込まれている場合もありますが、PHPには組み込まれていない型であるため、PHPで使用したい場合は、自作するか外部ライブラリを使用する必要があります。
一般的に定数を扱うよりも処理が遅くなるので、定数はすべて列挙型というわけではなく、区分値などの適した場面で使用するのがよいかと思います。
区分値をEnum化する
Enumライブラリのインストール
PHPのEnumライブラリは複数ありますが、本稿ではGitHubのStar数が多い & 汎用的に扱えることから、myclabs/php-enumを使用します。
以下のコマンドでインストールします。
$ composer require myclabs/php-enum
Enumの作成
ファイルを作成する場所はお好みですが、今回は
app
以下にEnums
ディレクトリを作成し、そこに定義します。権限区分を例にEnumを作っていきます。Role.php<?php namespace App\Enums; use MyCLabs\Enum\Enum; class Role extends Enum { const DEVELOPER = 1; const ADMINISTRATOR = 2; const STAFF = 3; }Enumの作成はこれだけです。
モデルからRoleのEnum値を取得できるようにする
User
のModel内にUser
のrole_code
属性のEnum値を取得するアクセサを定義します(LaravelのEloquentではgetHogeHogeAttribute
などのメソッドを定義することで、$user->hoge_hoge
のように取得することができるようになります)。まず、
User
に先程作成したEnumRole
をuseします。今回採用したEnumライブラリはコンストラクタにEnumに設定した値を渡すことで、定義した値を持つEnum値を得ることができます。以下のように記述し、
$user->role
で$user->role_code
に対応するEnum値が取得できるようします。
(モデルのソースはuseなど色々省略しています)User.php<?php namespace App\Models; use App\Enums\Role; class User { public function getRoleAttribute() { return new Role($this->role_code); } }Enumを使ってモデルに区分値を設定できるようにする
Userにミューテターを定義し、Userモデルの
role
属性にRole
のEnum値を代入することで、同時にrole_code
にも対応する区分値が代入されるようにします。setRoleAttributepublic function setRoleAttribute(Role $role) { $this->role_code = $role->getValue(); }Modelから権限を判定できるようにする
せっかくなので、
php-enum
に備わっているequals
を利用してModel内で権限も判定できるようにしておきます。equalsに渡す引数はメソッド呼び出しの形式であることに注意します。
()
をつけないとクラス変数という扱いになり、正しい比較ができません。 メソッド呼び出しの形式にすることで、 Enum型を返却してくれます。User.php<?php namespace App\Models; use App\Enums\Role; class User { public function getRoleAttribute() { return new Role($this->role_code); } public function setRoleAttribute(Role $role) { $this->role_code = $role->getValue(); } public function getIsDeveloperAttribute() { return $this->role->equals(Role::Developer()); } public function getIsAdministratorAttribute() { return $this->role->equals(Role::Administrator()); } public function getIsStaffAttribute() { return $this->role->equals(Role::STAFF()); } }これで権限を判定する場面で扱いやすくなりました。
Labelが欲しい
それぞれの権限名称をフロント側に表示したい場合などがあるかと思います。Laravelの機能を使ってスマートに取得できるようにします。
langファイルを作成する
Laravelには
app/resources/lang
以下に配置したディレクトリから、渡されたキーに対応する文字列を返却してくれる__
というヘルパーメソッドがいます。app/resources/lang
以下には locale名を関したディレクトリが存在し、configで設定されたlocaleと同じ名前を持つディレクトリから取得される仕組みになっています。今回はlocaleを
ja
に設定していますので、app/resources/lang/ja
以下にmy_app.php
を作成し、各区分の名称を記述します(ファイル名はなんでもいいです)。my_app.php<?php return [ 'developer' => '開発者', 'administrator' => '管理者', 'staff' => '担当者' ];これでコード内で
__('myapp.developer')
といったようにコールすることで、'開発者'
が取得できます。また、言語別にファイルを用意すればそれぞれの言語で取得できます。Enum内に
getLabel
メソッドを作成するメソッド名は何でもいいです。マジックメソッド
__toString
をオーバーライドしても良いのですが、デフォルトの挙動を破壊するのがなんとなく怖いので、今回は新たにメソッドを定義します。
getLabel
では lang に定義した、Enumに対応する文字列を取得できるようなロジックを記述します。
今回はEnumのKeyを小文字にするだけで、langに定義したKeyになることと、今後Role
が追加されたとしても同じように対応できるだろうという考えで、EnumインスタンスのKeyをstrtolower
で小文字にすることでlangのキーを生成しています。単純に小文字にするだけでは対応できない場合はEnumのキー => langのキー
のような連想配列を作成して取得するのがよさそうですね。Role.php<?php namespace App\Enums; use MyCLabs\Enum\Enum; class Role extends Enum { const DEVELOPER = 1; const ADMINISTRATOR = 2; const STAFF = 3; public function getLabel() { return __('my_app.'.strtolower($this->getKey())); } }Model内に
getRoleLabelAttribute
メソッドを作成する最後に以下のようなメソッドをモデル内に定義すれば、
$user->role_label
のような形でLabelを取得できます。getRoleLabelAttributepublic function getRoleLabelAttribute() { return $this->role->getLabel(); }
- 投稿日:2019-10-21T15:57:04+09:00
laradockでマイグレーション実行できるようにする
ファイル作成
ery@DESKTOP:/project/myapp$ php artisan make:migration db_table_name.envの設定
- project\myapp.env
DB_CONNECTION=mysql DB_HOST=mysql DB_PORT=3306 DB_DATABASE=default DB_USERNAME=default DB_PASSWORD=secretMySQLをdockerで実行できるようにpluginを設定
- 参考 : https://qiita.com/ntakuyan/items/42cbe7f45e85e16fe639
caching_sha2_password
->mysql_native_password
root@efd99ab69d3e:/# mysql -uroot -p mysql> SELECT user, host, plugin FROM mysql.user; +------------------+-----------+-----------------------+ | user | host | plugin | +------------------+-----------+-----------------------+ | default | % | caching_sha2_password | | root | % | caching_sha2_password | | mysql.infoschema | localhost | caching_sha2_password | | mysql.session | localhost | caching_sha2_password | | mysql.sys | localhost | caching_sha2_password | | root | localhost | caching_sha2_password | +------------------+-----------+-----------------------+ 6 rows in set (0.00 sec) mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root'; mysql> ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'root'; mysql> ALTER USER 'default'@'%' IDENTIFIED WITH mysql_native_password BY 'secret'; mysql> SELECT user, host, plugin FROM mysql.user; +------------------+-----------+-----------------------+ | user | host | plugin | +------------------+-----------+-----------------------+ | default | % | mysql_native_password | | root | % | mysql_native_password | | mysql.infoschema | localhost | caching_sha2_password | | mysql.session | localhost | caching_sha2_password | | mysql.sys | localhost | caching_sha2_password | | root | localhost | mysql_native_password | +------------------+-----------+-----------------------+ 6 rows in set (0.00 sec)laradockのmysqlの設定
- laradock/mysql/my.cnf に下記の通り追加
[mysqld] sql-mode="STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION" character-set-server=utf8 default_authentication_plugin= mysql_native_password # <- 追加アプリが動いているコンテナに入って実行。
- ※laravelのドキュメントより、マイグレーションはコンテナ内に入って実行するとのこと。
ery@DESKTOP:/project/laradock$ docker exec -it laradock_workspace_1 bash root@ery0606060:/var/www# php artisan migrate -v実行後mysqlのコンテナ入ってテーブルを確認
ery@DESKTOP:/project/laradock$ docker exec -it laradock_mysql_1 bash root@ery0606060:/# mysql -udefault -psecret default mysql> show tables; +-------------------------------------+ | Tables_in_default | +-------------------------------------+ | db_table_name | | failed_jobs | <- laravelデフォルトで必要なテーブル | migrations | +-------------------------------------+ 3 rows in set (0.00 sec) mysql> show create table db_table_name; +-------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Table | Create Table | +-------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | db_table_name | CREATE TABLE `db_table_name` ( `id` int(11) NOT NULL AUTO_INCREMENT, `hogehoge` int(11) NOT NULL, `created_at` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci | +-------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec)
- 投稿日:2019-10-21T11:49:27+09:00
Laravelのフォームリクエストで認可(authorize)が不可だった場合のエラーメッセージの変更方法
Laravelのフォームリクエストの
authorize
メソッドがfalseだった場合に返されるエラーメッセージを日本語にしたかったので方法を調べました。フォームリクエストクラス内で
failedAuthorization
メソッドをオーバーライドして、AuthorizationException
にメッセージを引き渡してやれば、メッセージを任意のテキストできました。protected function failedAuthorization() { throw new \Illuminate\Auth\Access\AuthorizationException('この操作は許可されていません。'); }ちなみ、
FormRequest
クラスでは以下のように定義されており、デフォルトだとここで指定されているメッセージが返されるようです。vendor/laravel/framework/src/Illuminate/Foundation/Http/FormRequest.php/** * Handle a failed authorization attempt. * * @return void * * @throws \Illuminate\Auth\Access\AuthorizationException */ protected function failedAuthorization() { throw new AuthorizationException('This action is unauthorized.'); }より適切なエラーメッセージの変更方法があれば、コメントいただければ幸いです。
- 投稿日:2019-10-21T11:36:12+09:00
Laravel tymon/jwt-auth による JWT 認証
環境
- Laravel 6.0
- tymon/jwt-auth 1.0.0-rc.5 (Laravel 6 には、1.0.0-rc.5 以降が必要)
インストール手順
参考) jwt-auth > Docs > Laravel Installation
インストール
$ composer require tymon/jwt-auth:1.0.0-rc.5
設定ファイルを publish
設定を変更しない場合は不要。
$ php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"シークレット作成
php artisan jwt:secret.env に秘密鍵 JWT_SECRET が追記される。
User モデルに JWTSubject を実装
参考) jwt-auth > Docs > Quick Start
Tymon\JWTAuth\Contracts\JWTSubject を実装する。
app/User.php
<?php use Tymon\JWTAuth\Contracts\JWTSubject; class User extends Authenticatable implements JWTSubject { ... public function getJWTIdentifier() { // JWT トークンに保存する ID を返す return $this->getKey(); } public function getJWTCustomClaims() { // JWT トークンに埋め込む追加の情報を返す return []; } }JWTGuard を使うように設定
参考) Laravel > Authentication > Adding Custom Guards
config/auth.php
<?php 'defaults' => [ 'guard' => 'api', 'passwords' => 'users', ], ... 'guards' => [ 'api' => [ 'driver' => 'jwt', 'provider' => 'users', ], ],認証エンドポイントの実装
まずは公式ドキュメントの Quick start にある AuthController をそのまま実装して使ってみる。
認証API
- POST /api/auth/login : ログイン
- POST /api/auth/logout : ログアウト
- POST /api/auth/refresh : トークンをリフレッシュ
- POST /api/auth/me : ユーザー情報取得
認証
ログインAPIにユーザー名/パスワードを渡し、アクセストークンを取得する。
$ curl -X POST -H "Accept: application/json" \ -F "email=test@example.jp" \ -F "password=password" \ http://localhost/api/auth/login | jq { "access_token": "(JWTトークン)", "token_type": "bearer", "expires_in": 3600 }認証エラー時
$ curl -X POST -H "Accept: application/json" \ -F "email=test@example.jp" \ -F "password=incorrect" \ http://localhost/api/auth/login | jq { "message": "Unauthenticated." }ユーザー情報取得
認証つきAPI要求時は Authorization ヘッダにアクセストークンを付与する。
$ curl -X POST -H "Accept: application/json" \ -H "Authorization: Bearer (JWTトークン)" \ http://localhost/api/auth/me | jq { "id": 1, "name": "Test User", "email": "test@example.jp", "email_verified_at": null, "created_at": "2019-05-10 06:51:16", "updated_at": "2019-05-10 06:51:16" }リフレッシュ
アクセストークンには有効期限があるので、それを超えて使う場合は新しいトークンを取得する必要がある。再度認証をおこなわずに新しいトークンに更新するには、リフレッシュAPIを使用する。
$ curl -X POST -H "Accept: application/json" \ -H "Authorization: Bearer (古いJWTトークン)" \ http://localhost/api/auth/refresh | jq { "access_token": "(新しいJWTトークン)", "token_type": "bearer", "expires_in": 3600 } $ curl -X POST -H "Accept: application/json" \ -H "Authorization: Bearer (古いJWTトークン)" \ http://localhost/api/auth/refresh | jq { "message": "Unauthenticated." }デフォルトの有効期限は発行から1時間、リフレッシュ有効期限は発行から2週間(この期限内であれば無効になったトークンを使って新しいトークンを取得することができる)。有効期限を変更する場合は以下の設定を .env に追記する。
設定 デフォルト値 説明 JWT_TTL 60 (1時間) トークンの有効期限 (分) JWT_REFRESH_TTL 20160 (2週間) トークンのリフレッシュ有効期限 (分) リフレッシュをおこなうとすぐに古いトークンは使えなくなる。リフレッシュと並列に古いトークンでアクセスをおこなうと更新直後に認証が通らなくなってしまう。古いトークンをすぐに失効させたくない場合は、猶予期間を .env に設定する。
設定 デフォルト値 説明 JWT_BLACKLIST_GRACE_PERIOD 0 (無効) ブラックリスト猶予期間 (秒) ログアウト
ログアウトするとそのアクセストークンは使えなくなる。jwt-auth では発行したトークンを管理しているわけではなく、逆に無効化したトークンをキャッシュに保持して拒否する実装となっているため、キャッシュをクリアすると再度使用できるようになるので注意。リフレッシュで無効になったトークンも同様。
$ curl -X POST -H "Accept: application/json" \ -H "Authorization: Bearer (JWTトークン)" \ http://localhost/api/auth/logout {"message":"Successfully logged out"} $ curl -X POST -H "Accept: application/json" \ -H "Authorization: Bearer (JWTトークン)" \ http://localhost/api/auth/me {"message":"Unauthenticated."}セキュリティの確認
JWTトークン
JWTトークンはユーザーIDを含む JSON をエンコードして署名をつけただけのもの(
(ヘッダー).(ペイロード).(署名)
という構成)。サーバー側に設定された秘密鍵を持たないと改竄はできないが、中身を知ることはできる。受信したJWTトークンをデコードしてみる。
$ echo "(JWTトークンのヘッダー)" | base64 --decode - | jq { "typ": "JWT", "alg": "HS256" } $ echo -n "(JWTトークンのペイロード)" | base64 --decode - | jq { "iss": "http://localhost/api/auth/login", "iat": 1557471077, "exp": 1557474677, "nbf": 1557471077, "jti": "qyDgabCpUk6atauK", "sub": 9, "prv": "87e0af1ef9fd15812fdec97153a14e0b047546aa" }
"sub"
がユーザーIDをあらわしている。署名なしのJWTトークン
JWTトークンの署名アルゴリズムは選択することができ、"none" (署名なし) もJWTトークンとしては Valid である。
署名アルゴリズムに "none" を指定して、JWT トークンをねつ造してみる。
$ echo -n '{"typ":"JWT","alg":"none"}' | base64 eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0= $ echo -n '{"iss":"http:\/\/localhost:8000\/api\/auth\/login","iat":1557471077,"exp":1557474677,"nbf":1557471077,"jti":"qyDgabCpUk6atauK","sub":1,"prv":"87e0af1ef9fd15812fdec97153a14e0b047546aa"}' | base64 eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODAwMFwvYXBpXC9hdXRoXC9sb2dpbiIsImlhdCI6MTU1NzQ3MTA3NywiZXhwIjoxNTU3NDc0Njc3LCJuYmYiOjE1NTc0NzEwNzcsImp0aSI6InF5RGdhYkNwVWs2YXRhdUsiLCJzdWIiOjEsInBydiI6Ijg3ZTBhZjFlZjlmZDE1ODEyZmRlYzk3MTUzYTE0ZTBiMDQ3NTQ2YWEiこのJWTトークンで認証してみる。
$ curl -X POST -H "Accept: application/json" -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3Q6ODAwMFwvYXBpXC9hdXRoXC9sb2dpbiIsImlhdCI6MTU1NzQ3MTA3NywiZXhwIjoxNTU3NDc0Njc3LCJuYmYiOjE1NTc0NzEwNzcsImp0aSI6InF5RGdhYkNwVWs2YXRhdUsiLCJzdWIiOjEsInBydiI6Ijg3ZTBhZjFlZjlmZDE1ODEyZmRlYzk3MTUzYTE0ZTBiMDQ3NTQ2YWEifQ." http://localhost/api/auth/me {"message":"Unauthenticated."}大丈夫っぽい。
参考
- 投稿日:2019-10-21T11:36:12+09:00
Laravel: Vessel によるローカル開発用コンテナ環境を使う
環境
- Laravel 6.0
- Vessel
概要
Laravel のローカル開発環境を Docker Compose で動作させるための設定とショートカットコマンドを提供してくれる。
導入
インストール
$ composer require --dev shipping-docker/vessel設定の取り込み
$ php artisan vendor:publish --provider="Vessel\VesselServiceProvider"これにより以下が生成される。
- docker/ 配下 ... イメージ作成のための設定
- app/
- mysql/
- node/
- docker-compose.yml
- vessel ... コマンド
docker/ 配下はファイルがいろいろあるが、これをベースに直接カスタマイズしていくのでコミットしてしまって良いと思う。
初期化
$ bash vessel init
このコマンドでおこなわれること。
composer require predis/predis
- キャッシュとセッションに Redis を使うためにインストール
.env の設定変更
DB_HOST=mysql CACHE_DRIVER=redis SESSION_DRIVER=redis REDIS_HOST=redis
- データベースのホストを mysql コンテナに向ける設定
- キャッシュとセッションの保存先を Redis に向ける設定
※.env.example は変更されないので、必要があれば転記してコミットする
- vessel コマンドへの実行権限追加
例)
起動
$ ./vessel start
docker-compose up -d と同じ。デフォルトで以下のコンテナが起動する。
- app : Ubuntu 18.04 ベース。supervisord (nginx + php-fpm)
- mysql:5.7
- redis:alpine
データベース名、ユーザー等は .env から取得して初回起動時に作成される。DB_USERNAME に root を指定するとユーザーを作成しようとしてエラーになるので別のものにする。
他に必要なものがあれば docker-compose.yml を編集する。
停止
$ ./vessel stop
docker-compose down と同じ。
コンテナイメージの再ビルド
Dockerfile は docker/ フォルダ配下にあるので、必要に応じて編集し、再ビルドする。
$ ./vessel build
docker-compose up -d と同じ。
ログイン
$ ./vessel exec app bashdocker-compose exec app bash と同じ。
composer コマンド実行
$ ./vessel composer (コマンド)app コンテナで composer を実行するショートカット。
artisan コマンド実行
$ ./vessel artisan (コマンド)app コンテナで php artisan を実行するショートカット。
- 投稿日:2019-10-21T11:36:10+09:00
Laravel tymon/jwt-auth でトークンを Cookie に保存する
環境
- Laravel 6.0
- tymon/jwt-auth 1.0.0-rc.5
概要
jwt-auth では、JWT トークンを以下の順番で検索する。
- Authorization ヘッダ (Bearer)
- Request->query('token')
- Request->input('token')
- Route パラメタ (token)
- Cookie (token)
ということで、トークンを発行したら、
token
という名前の Cookie を設定してあげればよい。設定手順
Cookie 設定例
class AuthController extends Controller { public function login(Request $request, CookieJar $cookie): Response { $credentials = request(['email', 'password']); $token = auth()->attempt($credentials); ... return response(...) ->withCookie($cookie->make( 'token', $token, config('jwt.refresh_ttl'), // minutes null, // path null, // domain $request->getScheme() === 'https', // secure true // httpOnly )); } }
- 投稿日:2019-10-21T11:36:09+09:00
Laravel での tymon/jwt-auth による JWT トークンの自動更新
環境
- Laravel 6.0
- tymon/jwt-auth 1.0.0-rc.5
概要
JWT トークンはトークン自体に有効期限が埋め込まれており、期限を更新するにはトークン自体を置き換えなければならない。
フロントで有効期限を管理してトークンのリフレッシュをせずに、スライディングセッションを実現したい。トークンは Cookie でフロントに保存するものとする。サーバー側で期限切れを検出したら新しいトークンを Cookie に設定してあげる。
トークンの有効期限(JWT_TTL:デフォルト1時間)が切れても、リフレッシュ有効期限(JWT_REFRESH_TTL:デフォルト2週間)内であればそのトークンを用いて新しいトークンを発行することができる。
ログインシーケンス
トークン更新シーケンス
実装
サーバー側でトークンの有効期限切れを検出し、リフレッシュをおこなって返すミドルウェアを作成する。不正なトークンや更新に失敗したりした場合はこのあとの通常処理で 401 になるので、何もしない。
namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Tymon\JWTAuth\Exceptions\JWTException; use Tymon\JWTAuth\Exceptions\TokenExpiredException; use Tymon\JWTAuth\Http\Middleware\BaseMiddleware; class RefreshToken extends BaseMiddleware { public function handle($request, Closure $next) { $token = $newToken = null; try { $token = $this->auth->parseToken(); $token->authenticate(); } catch (TokenExpiredException $e) { // Token expired: try refresh try { $newToken = $token->refresh(); } catch (JWTException $e) { // Refresh failed (refresh expired) } } catch (JWTException $e) { // Invalid token } $response = $next($request); if ($newToken) { // Send the refreshed token back to the client. $response->withCookie(cookie( 'token', $newToken, config('jwt.refresh_ttl'), // minutes null, // path null, // domain $request->getScheme() === 'https', // secure true // httpOnly )); } return $response; } }このミドルウェアを有効にする。
namespace App\Http; use Illuminate\Foundation\Http\Kernel as HttpKernel; class Kernel extends HttpKernel { ... protected $middlewareGroups = [ 'api' => [ ... \App\Http\Middleware\RefreshToken::class, // JWTトークン更新 ※bindings の前に置く 'bindings', ... ], ]; }ブラックリスト猶予期間を設定
リフレッシュがおこなわれると、即時古いトークンは使えなくなるため、フロントから同時に複数のリクエストをおこなった場合、一部のリクエストが失敗してしまう可能性がある。これを防止するため、一定時間は古いトークンも使用できるようにする。.env にブラックリスト猶予期間(JWT_BLACKLIST_GRACE_PERIOD)を設定しておく。(例: 15秒)
JWT_BLACKLIST_GRACE_PERIOD=15
- 投稿日:2019-10-21T11:36:08+09:00
Laravel: 開発時だけ他ホストからの要求を許可する (CORS)
背景
開発時のみフロントエンドを別ホスト・ポートで動作し、クロスサイトでのリクエストを許可したい。本番には適用しない。
実装
CORS ヘッダを設定するミドルウェアを作成する。
クロスサイトで認証情報つき(withCredentials)リクエストをおこないたい場合は
Access-Control-Allow-Origin: *
が許可されないため、リクエスト元のホスト名を入れて返している。
参考) Reason: Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ‘*’namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; class Cors { public function handle(Request $request, Closure $next): Response { $response = $next($request); if (env('APP_DEBUG') && $request->header('Origin')) { $response ->header('Access-Control-Allow-Credentials', 'true') ->header('Access-Control-Allow-Origin', $request->header('Origin')) ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') ->header('Access-Control-Allow-Headers', 'Origin, Authorization, Accept, Content-Type, X-XSRF-Token') ->header('Access-Control-Expose-Headers', 'Authorization'); } return $response; } }このミドルウェアを有効にする。
namespace App\Http; use Illuminate\Foundation\Http\Kernel as HttpKernel; class Kernel extends HttpKernel { ... protected $middleware = [ ... \App\Http\Middleware\Cors::class ]; ... }
- 投稿日:2019-10-21T11:36:07+09:00
Laravel tymon/jwt-auth のブラックリストをデータベースに保存する
Laravel/Lumen 用の JWT 認証パッケージである tymon/jwt-auth では、発行した有効なトークンを保持するのではなく、ログアウトしたりリフレッシュされた古いトークンをブラックリストという形で保存しておく。デフォルトではキャッシュに保存されるので、Redis などで共有していないとスケールアウトできない。ということで、データベースに保存してみる。
環境
- Laravel 6.0
- tymon/jwt-auth 1.0.0-rc.5
手順
テーブル jwt_auth_storage を作成
database/migrations 配下に定義してマイグレーションする。
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateJwtAuthStorageTable extends Migration { public function up(): void { Schema::create('jwt_auth_storage', function (Blueprint $table) { $table->bigIncrements('id'); $table->timestamps(); $table->timestamp('expires_at')->nullable(); $table->string('key')->index(); $table->string('value'); }); } public function down(): void { Schema::dropIfExists('jwt_auth_storage'); } }jwt_auth_storage テーブルの Eloquent Model を作成
<?php namespace App\Model; use Illuminate\Database\Eloquent\Model; class JwtAuthStorage extends Model { protected $table = 'jwt_auth_storage'; protected $fillable = [ 'key', 'value', 'expires_at', ]; protected $dates = ['expires_at']; }Storage を継承したアクセスクラスを作成
Tymon\JWTAuth\Contracts\Providers\Storage を継承し、各メソッドを実装したクラスを作成する。デフォルトでは Tymon\JWTAuth\Providers\Storage\Illuminate::class が使用されるので、これを参考にする。
<?php namespace App\Repository; use App\Model\JwtAuthStorage; use DateTime; use Tymon\JWTAuth\Contracts\Providers\Storage; class JwtAuthStorageRepository implements Storage { protected $jwtAuthStorageModel; public function __construct(JwtAuthStorage $jwtAuthStorageModel) { $this->jwtAuthStorageModel = $jwtAuthStorageModel; } public function add($key, $value, $minutes): void { $expiresAt = (new DateTime('now'))->modify('+ ' . $minutes . ' minutes'); $this->jwtAuthStorageModel->newQuery() ->create([ 'key' => $key, 'value' => serialize($value), 'expires_at' => $expiresAt, ]); } public function forever($key, $value): void { $this->jwtAuthStorageModel->newQuery() ->create([ 'key' => $key, 'value' => serialize($value), ]); } public function get($key) { $now = new DateTime('now'); $data = $this->jwtAuthStorageModel->newQuery() ->where('key', $key) ->where('expires_at', '>', $now) ->orderBy('expires_at', 'desc') ->first(); if ($data) { return unserialize($data->value); } else { return null; } } public function destroy($key): bool { return !!$this->jwtAuthStorageModel->newQuery() ->where('key', $key) ->delete(); } public function flush() { } }設定ファイルを publish
設定を変更するため、config/jwt.php をプロジェクトに展開する。
$ php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"設定
設定ファイル(config/jwt.php)の provider.storage に、カスタマイズしたアクセスクラスを指定する。ブラックリストの読み書きはこのクラスを経由しておこなわれる。
<?php return [ ... 'providers' => [ ... 'storage' => App\Repository\JwtAuthStorageRepository::class, ], ];
- 投稿日:2019-10-21T02:38:29+09:00
LaravelのプロジェクトをGithubで管理する流れ[自学習メモ]
はじめに
Github初心者がGithubでプロジェクトを管理をする方法を自分用にまとめたものです。
もし、同じような初学者の参考になれば幸いです。環境(Gitバージョン)
$ git --version git version 2.21.0 (Apple Git-122)作業の流れ
この記事で行う作業の流れは、
Githubでリモートリポジトリを作成→ローカルにクローンを作成→Laravelプロジェクト作成→ Github上にプッシュ
となります。Githubでリポジトリを作成する
「New repository」からリポジトリを作成します。この時、「Initialize this repository with a README」にチェックしましょう。
※ここでチェックし忘れても、githubのリポジトリのページに記載されている下記のようなコマンドを、次の作業で作成するクローンフォルダ内に移動した上で実行することでREADMEファイルを作成することができます。echo "# プロジェクト名" >> README.md user-MBP:プロジェクト名 username$ git init Reinitialized existing Git repository in /Users/username/Desktop/プロジェクト名/.git/ user-MBP:プロジェクト名 iusername$ git add README.md user-MBP:プロジェクト名 username$ git commit -m "first commit" //このコマンドは参考用です。そのままターミナルに貼り付けても意味はありません。ローカルにリポジトリをクローンする
ローカル(自分のPC)にGithubで作成したリモートリポジトリ のクローンを作成します。
クローンはデスクトップ上にworkという作業用フォルダを作成し、その中に作成します。今後、何かしらのプロジェクトをGithubで管理する際はこのworkフォルダ内で作業を行うようにします。
まず、workフォルダを作成します。
$ cd Desktop //userからDesktopディレクトリに移動します。 $ mkdir work //workという名前の空のフォルダがデスクトップ上に作成されます。 $ ls //Desktopディレクトリ上のファイルを一覧できます。workフォルダが作成されたことを確認しましょう。 $ cd work //workフォルダ内に移動します。次に、GithubのリポジトリのWeb URLをコピーします。URLは上記の画像のように、「Clone or download」をクリックした先に表示されるClone with HTTPSのURLです。このURLを介してローカル上にリポジトリがクローンされます。
$ git clone <リポジトリのURL> //workフォルダ内にリモートリポジトリがクローンされます。 $ ls //クローンされて、ローカルリポジトリが作成されているかを確認しましょう。Laravelのプロジェクトを作成する
次に作業を行うLaravelプロジェクトを作成します。
$ cd <ローカルリポジトリのフォルダ名> //フォルダを移動します。 $ laravel new <プロジェクト名> //ローカルリポジトリ内にLaravelプロジェクトが作成されます。 $ls //プロジェクトが作成されているか確認しましょう。基本的にLaravelプロジェクトはクローンフォルダ内で作成したほうが良いようです。もし、すでに作成したLaravelプロジェクトを管理したい場合はフォルダごと全てをクローン内に移しましょう。
addからpushまでの流れ
次にLaravelプロジェクトをgithubで管理します。今回は作成したばかりのLaravelプロジェクトをGithubにpushします。
$ git branch //現在、masterブランチにいることを確認します。masterブランチにいれば、masterという文字が緑色で表示されます。
特に何も表示されない場合は自分がいるディレクトリの位置を確認する、masterブランチが作成されているか確認するなどの確認をしましょう。
※もしみつからない場合は、先ほどご説明したように、READMEファイルの作成を行ってみてください(READMEを作り忘れた場合)。$ git add . //ローカルリポジトリ内のファイルをインデックス登録してコミットする準備を行う。 $ git status //コミットの準備がされているファイル一式を確認することができます。「git add .」はカレントディレクトリ(作業中のディレクトリ)内の変更があった全てのフォルダがaddされるコマンドです。
今回は、初めてLaravelプロジェクトをaddしたので、Laravelプロジェクトのファイルが全てaddされます。今後、Laravelプロジェクト内のファイルやフォルダ情報を変更した場合は、変更したファイルやフォルダのみがaddされます。
初めて「git add .」コマンドを入力すると無反応なので心配になりますが、「git status」コマンドを入力することでgit addが実行されていることを確認できます。このように初めは一つの作業ごとに確認を行ったほうが安心して作業できます。
$ git commit -m "コミットメッセージ" //addしたファイル一式がcommitされます。 $ git log //commit情報を確認できます。 $ git push origin master ////masterブランチにpushします。以上です。Githubのリモートリポジトリを確認するとpushされたファイル一式が確認できると思います。
最後に
自分用のメモではありますが、初めてのQiitaへの記事投稿となります。
もし、ここ間違えているよ!、ここはこうしたほうがいいよ!という意見がありましたら、教えていただけると勉強になりますのでよろしくお願いします!