laravelのライブラリであるlucadegasperi/oauth2-server-laravel
を使ってOAuth2用のサービスプロバイダー(OAuth2サーバ)を立てる。
OAuth2クライアントを実装する記事はググると沢山にヒットするがサーバの方は少ない。あっても古い。そして英語の記事...。
OAuth2自体ややこしい、oauth2-server-laravelについても記事が少ない。2度と面倒な調査をしたくないのでメモで残す。
1.環境とか
OSX 10.10 Yosemite
vagrant 1.8.1
VirtualBox 5.0
Cent OS 7.2
nginx 1.8.1
postgresql 9.3
PHP 7.0
Laravel 5.2
oauth2-server-laravel 5.1
OSX上にvagrantで2つのVMをVirtualBoxで立ちあげ。
両方共CentOS。1つはWeb用でnginxとphp。もう1つはDB用でpostgresqlでやってる。
ややこしいとういう文句は否定出来ない
2.OAuth2の説明
ライブラリ関係無く、OAuth2の基本的な仕組みを理解して無いと辛い。ライブラリのドキュメントを読むのにも用語がわかってないと意味不明になる。
自分は仕様のドキュメント(日本語訳)を読むと必ず寝てしまうという不治の病を患っているので以下の参考URLを読んで理解した。
2-1.参考URL
-
第1回 OAuthとは?―OAuthの概念とOAuthでできること:ゼロから学ぶOAuth|gihyo.jp
- 記事が古いので多分OAuth1で説明している。基本的な事は一緒なので参考になる
- 「OAuth」の基本動作を知る - @IT
- RFCとなった「OAuth 2.0」――その要点は? - @IT
- OAuth2読書会
以下OAuth2についてのまとめ。
2-2.認証と認可
OAuthは認証の為の仕組みでは無く、認可の為の仕組みらしい。
認証は誰であるかを特定する事。
認可は許可を出すこと。許可を出すには認証(誰であるかを特定)する必要がある。
2つを何かゴチャゴチャにしていると、この先理解が追いつかない。
2-3.3つの登場人物
サービスを
- 利用する人
- 提供する人
- 仲介する人
がいる。
2-3-1.利用する人
- ユーザとかリソースオーナーとかいう
- サービスに自分のリソース(画像、動画、記事等)を登録している人
2-3-2.提供する人
- OAuthサーバとかサービスとかサービスプロバイダーとかいう。
- ユーザのリソースを保持している
- OAuth2の仕様的にはリソースサーバ、認可サーバの2つに切り分けが出来る。
- 1つのサーバでも良い
- 認可サーバでクライアントがユーザのリソースにアクセスして良いかどうかの判定をする
- リソースサーバはいわゆるWebAPIサーバ。認可されたか判定してクライアントにリソース(通常json形式)を返す
2-3-3.仲介する人
- クライアントとかコンシュマーとかいう
- ユーザのリソースをユーザの代理でサービスプロバイダにアクセスする
- いわゆるモバイルアプリやWebアプリの事
2-4.アクセストークン
アクセストークンとはサービスプロバイダが発行した認可の証明書のような文字列。これがあればクライアントはユーザのリソースを許可された範囲で読み込みや操作が出来る。
有効期限がある。
2-4-1.有名ドコロの有効期限
有名ドコロの有効期限を調べた。
- facebook:
- web app:2hour
- mobile app:60days
- twitter:無期限
- github:無期限?
- qiita:記述無し。refreshが無いので多分無期限
- hatena:記述無し。refreshが無いので多分無期限
2-5.4つのGrantタイプ
アクセストークンを発行する時の処理の流れで種類が分かれる
- Authorization Code Grant:Webアプリ向け
- Implicit Grant:モバイルアプリ向け
- Resource Owner Password Credentials Grant:サービスプロバイダとクライアントが同じ企業向け
- Client Credentials Grant:Analytics情報など向け
2-6.Refresh Token
アクセストークンに有効期限があるので、有効期限をこえた場合はRefresh Tokenを使って再度アクセストークンを再発行する。
ユーザはログインする必要は無い。
2-7.スコープ
- サービスプロバイダによってはpermissionとか呼ばれる
- アクセストークンの権限の範囲的な意味
以下は有名ドコロのスコープ
2-7-1.facebook
- public_profile
- user_friends
- user_about_me
- user_actions.books
- user_actions.fitness
- …
以下略
2-7-2.twitter
- Read only
- Read and Write
- Read, Write and Access direct messages
2-7-3.qiita
- read_qiita
- read_qiita_team
- write_qiita
- write_qiita_team
2-7-4.hatena
- read_public
- write_public
- read_private
- write_private
2-8.OAuth2の処理の流れ
2-8-1.クライアントの登録
事前にクライアントはサービスプロバイダに登録する。この動作は1度のみ。
この時クライアントは
- クライアント名(アプリ名)
- リダイレクトURL
- 利用するスコープ
等を登録する。
登録した結果
- client_id
- client_secret
を取得
2-8-2.認証、認可(もしくはアクセストークンの取得)
基本的にはユーザがクライアントを利用開始した初回のみの動作。アクセストークンの有効期限が切れた時に繰り返す場合がある。
- ユーザがクライアントの利用を開始
- クライアントはclient_idやclient_secret等必要な情報を付与してサービスプロバイダへリダイレクトする
- ユーザはサービスプロバイダ上でログインする
- ユーザはサービスプロバイダに対してクライアントを承認する
- サービスプロバイダはクライアントが指定したリダイレクトURLへ移動する
- Grantの種類によってはここでアクセストークンの取得が完了
- クライアントはサービスプロバイダから取得したトークンを使ってアクセストークンをリクエスト
- サービスプロバイダはアクセストークンを返す
2-8-3.リソースサーバにリクエスト
ここの動作はクライアント上で常に繰り返す。はず。
- 取得したアクセストークンを使って、クライアントからサービスプロバイダに対してWebAPIを叩く
- サービスプロバイダはアクセストークンで判定。問題無ければリソースを返す
3.oauth2-server-laravel
oauth2-server-laravelはフレームワークLaravelにOAuth2サーバを立てる為のライブラリ。
thephpleague/oauth2-serverをLaravel用にラップしたもの。
バージョン5.1.xでLaravel5.2系に対応している。PHP5.5.9以上。
Grantタイプの内「Implicit Grant」はサポートしていない。
ドキュメントもある。基本的にはドキュメントの通りに進めれば良いが色々とハマり所がある。
4.実装の前に
4-1.OAuthの前にAuthを実装する
認証の仕組みが無いと動かない。自分はoauth2-server-laravelを先に実装してエラーになって途方にくれた。
この記事を読んでいる、0と1だけでプログラミングが出来る諸先輩方はこんな単純ミスをしないと思うが、一応念の為に書いておく。
Laravel公式のscaffold方式php artisan make:auth
で認証をサクッと実装する。
ここはググれば記事がたくさん出るので詳細は省く。
4-1-1.テスト用のユーザ
「OAuthの前にAuthを実装する」で作成された登録フォームからユーザーが新規追加出来るが、今後の説明・テストの為にここではテスト用のユーザはシーダーを作って追加する。
ターミナルで以下を実行
php artisan make:seeder UsersTableSeeder
database/seeds/UsersTableSeeder.phpが作成されるので、以下のように修正
<?php
use Illuminate\Database\Seeder;
class UsersTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
DB::table('users')->insert([
'name' => 'test_user',
'email' => 'test_user@gmail.com',
'password' => bcrypt('secret'),
]);
}
}
4-2.POSTMAN
今後のテストの為にhtmlとjavascriptを作るのは辛いので、WebAPIをテストできるPOSTMANというchromeアプリをインストールしておく。皆幸せになれる。ここもググれば記事がたくさん出るので詳細は省く。
5.実装手順
今回の説明では1つのサーバにリソースサーバと認可サーバがある事を想定している。
5-1.composerでインストール
composer.jsonに以下を追加。
"lucadegasperi/oauth2-server-laravel": "5.1.*"
composer update
を実行
5-2.設定ファイルへの書込み
config/app.phpに以下を追加。
'providers' => [
...
LucaDegasperi\OAuth2Server\Storage\FluentStorageServiceProvider::class,
LucaDegasperi\OAuth2Server\OAuth2ServerServiceProvider::class,
],
'aliases' => [
...
'Authorizer' => LucaDegasperi\OAuth2Server\Facades\Authorizer::class,
],
app/Http/Kernel.phpに以下を追加。
protected $middleware = [
...
\LucaDegasperi\OAuth2Server\Middleware\OAuthExceptionHandlerMiddleware::class,
];
protected $routeMiddleware = [
...
'csrf' => \App\Http\Middleware\VerifyCsrfToken::class,
'oauth' => \LucaDegasperi\OAuth2Server\Middleware\OAuthMiddleware::class,
'oauth-user' => \LucaDegasperi\OAuth2Server\Middleware\OAuthUserOwnerMiddleware::class,
'oauth-client' => \LucaDegasperi\OAuth2Server\Middleware\OAuthClientOwnerMiddleware::class,
'check-authorization-params' => \LucaDegasperi\OAuth2Server\Middleware\CheckAuthCodeRequestMiddleware::class,
];
「$middlewareGroups」にある「\App\Http\Middleware\VerifyCsrfToken::class」を削除。
公式のドキュメントでは「$routeMiddleware」に追加する'csrf' => App\Http\Middleware\VerifyCsrfToken::class,
となっているがAPP
の前にバックスラッシュが必要。これが無いと後でエラーになる。
5-3.テーブル作成と設定ファイルの作成
ターミナルで
php artisan vendor:publish
を実行。マイグレーションファイルとconfig/oauth2.php
が作成される。
oauth2用のテーブルをDBに作成する為に以下をターミナルで実行。
php artisan migrate
14個のテーブルが新規に追加される。
5-4.各Grant毎の実装とテスト
ここから各Grantのタイプで実装とテストが変わる。 Password Grant
、Auth Code Grant
、Refresh Token Grant
について説明する。
5-5.Password Grant
oauth2-server-laravelの英語のチュートリアル記事は、この形式ばかりあった。実装とテストが単純だからだと思われる。
5-5-1.設定の追加
config/oauth2.phpの「grant_types」配列に以下を追加
'grant_types' => [
'password' => [
'class' => '\League\OAuth2\Server\Grant\PasswordGrant',
'callback' => '\App\PasswordGrantVerifier@verify',
'access_token_ttl' => 3600 //sec
]
],
5-5-2.認証処理の追加
app/PasswordGrantVerifier.phpファイルを新規に作成。ここでユーザの認証処理を実装している。もし「email」カラムでは無く「username」カラム等他のカラムに変更する場合は、このファイルを編集する。
namespace App;
use Illuminate\Support\Facades\Auth;
class PasswordGrantVerifier
{
public function verify($username, $password)
{
$credentials = [
'email' => $username,
'password' => $password,
];
if (Auth::once($credentials)) {
return Auth::user()->id;
}
return false;
}
}
5-5-3.テスト用のデータを追加
テスト用にクライアントをDBに追加する。今回はシーダーを利用する。
ターミナルで以下を実行
php artisan make:seeder OauthClientsTableSeeder
database/seeds/OauthClientsTableSeeder.phpが作成されるので以下のように修正。
<?php
use Illuminate\Database\Seeder;
// use Hash;
class OauthClientsTableSeeder extends Seeder
{
/**
* Run the database seeds.
* php artisan db:seed --class=OauthClientsTableSeeder
* @return void
*/
public function run()
{
DB::table('oauth_clients')->insert([
'id' => 'abcdefghijklmiyOD9b6VQ81YTg5bNTRnqfPR2Lf',
'secret' => 'ABCDEFGHIJKLM2K7bOfgrYmMqfkEvPb8sWti0yOSz',
'name' => 'test_client',
]);
}
}
シーダーの実行。
php artisan db:seed --class=OauthClientsTableSeeder
* このシーダーは「Auth Code Grant」で共通。
5-5-4.ルートの設定
app/Http/routes.phpに以下を追加。
Route::post('oauth/access_token', function() {
return Response::json(Authorizer::issueAccessToken());
});
*このルートは「Auth Code Grant」と共通
5-5-5.アクセストークンの発行
POSTMANでテスト
- httpメソッド:POST
- URL:http://localhost/oauth/access_token
- Body:x-www-form-urlencodeed
- grant_type:password
- client_id: abcdefghijklmiyOD9b6VQ81YTg5bNTRnqfPR2Lf
- client_secret: ABCDEFGHIJKLM2K7bOfgrYmMqfkEvPb8sWti0yOSz
- username:test_user@gmail.com
- password:secret
urlは実際の環境に合わせて適宜変更する。
client_id、client_secret、username、passwordはDBに保存した内容を入れる。
「Send」ボタンクリックで以下のようなJSONが返ってくれば成功。
{
"access_token": "liGsCt56EHnUIOFwt3gnaItsrOF6RvsDalk1Dpvu",
"token_type": "Bearer",
"expires_in": 3600
}
5-6.Auth Code Grant
oauth2-server-laravelの英語のチュートリアル記事で、この形式は見つからなかった。実装もテストも少し面倒だからだと思われる。
5-6-1.設定の追加
config/oauth2.phpの「grant_types」配列に以下を追加
'grant_types' => [
'authorization_code' => [
'class' => '\League\OAuth2\Server\Grant\AuthCodeGrant',
'access_token_ttl' => 3600, //sec
'auth_token_ttl' => 3600 //sec
]
],
5-6-2.ルートの設定1
一時的なトークンを発行するためのWebビュー画面を追加する。app/Http/routes.phpに以下を追加。
Route::get('oauth/authorize', ['as' => 'oauth.authorize.get', 'middleware' => ['check-authorization-params', 'auth'], function() {
$authParams = Authorizer::getAuthCodeRequestParams();
$formParams = array_except($authParams,'client');
$formParams['client_id'] = $authParams['client']->getId();
$formParams['scope'] = implode(config('oauth2.scope_delimiter'), array_map(function ($scope) {
return $scope->getId();
}, $authParams['scopes']));
return View::make('oauth.authorization-form', ['params' => $formParams, 'client' => $authParams['client']]);
}]);
5-6-3.認可画面を追加
上のルートで使用するviewを追加する。resources/views/oauth/authorization-form.blade.phpを新規に作成する。
@extends('layouts.app')
@section('content')
<h2>{{$client->getName()}}</h2>
<form method="post" action="{{route('oauth.authorize.post', $params)}}">
{{ csrf_field() }}
<input type="hidden" name="client_id" value="{{$params['client_id']}}">
<input type="hidden" name="redirect_uri" value="{{$params['redirect_uri']}}">
<input type="hidden" name="response_type" value="{{$params['response_type']}}">
<input type="hidden" name="state" value="{{$params['state']}}">
<input type="hidden" name="scope" value="{{$params['scope']}}">
<button type="submit" name="approve" value="1">Approve</button>
<button type="submit" name="deny" value="1">Deny</button>
</form>
@endsection
「Approve」は「Allow」の方がよりOAuthっぽい気がする。
Laravelの標準Authの画面に合わせてcssフレームワークのbootstrapを使っても良いと思う。
5-6-4.ルートの設定2
一時的なトークンを発行するためのapiを追加する。app/Http/routes.phpに以下を追加。
Route::post('oauth/authorize', ['as' => 'oauth.authorize.post', 'middleware' => ['csrf', 'check-authorization-params', 'auth'], function() {
$params = Authorizer::getAuthCodeRequestParams();
$params['user_id'] = Auth::user()->id;
$redirectUri = '/';
// If the user has allowed the client to access its data, redirect back to the client with an auth code.
if (Request::has('approve')) {
$redirectUri = Authorizer::issueAuthCode('user', $params['user_id'], $params);
}
// If the user has denied the client to access its data, redirect back to the client with an error message.
if (Request::has('deny')) {
$redirectUri = Authorizer::authCodeRequestDeniedRedirectUri();
}
return Redirect::to($redirectUri);
}]);
5-6-5.ルートの設定3
app/Http/routes.phpに以下を追加。
Route::post('oauth/access_token', function() {
return Response::json(Authorizer::issueAccessToken());
});
*このルートは「Password Grant」と共通
5-6-6.テスト用のデータを追加1
テスト用にクライアントをDBに追加する。今回はシーダーを利用する。
ターミナルで以下を実行
php artisan make:seeder OauthClientsTableSeeder
database/seeds/OauthClientsTableSeeder.phpが作成されるので以下のように修正。
<?php
use Illuminate\Database\Seeder;
// use Hash;
class OauthClientsTableSeeder extends Seeder
{
/**
* Run the database seeds.
* php artisan db:seed --class=OauthClientsTableSeeder
* @return void
*/
public function run()
{
DB::table('oauth_clients')->insert([
'id' => 'abcdefghijklmiyOD9b6VQ81YTg5bNTRnqfPR2Lf',
'secret' => 'ABCDEFGHIJKLM2K7bOfgrYmMqfkEvPb8sWti0yOSz',
'name' => 'test_client',
]);
}
}
シーダーの実行。
php artisan db:seed --class=OauthClientsTableSeeder
* このシーダーは「Password Grant」で共通。
5-6-7.テスト用のデータを追加2
テスト用にクライアントのリダイレクトURLをDBに追加する。
ターミナルで以下を実行
php artisan make:seeder OauthClientEndpointsTableSeeder
database/seeds/OauthClientEndpointsTableSeederが作成されるので以下のように修正。
<?php
use Illuminate\Database\Seeder;
class OauthClientEndpointsTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
DB::table('oauth_client_endpoints')->insert([
'client_id' => 'abcdefghijklmiyOD9b6VQ81YTg5bNTRnqfPR2Lf',
'redirect_uri' => 'http://test_client.com/callback',
]);
}
}
シーダーの実行。
php artisan db:seed --class=OauthClientEndpointsTableSeeder
5-6-8.テスト1:一時トークンの発行
リダイレクトを含むのでPOSTMANではテスト出来ない模様。ブラウザでテスト。クライアントのリダイレクト先のページは取り敢えず作らなくてもテストになる。
ブラウザに以下のURLを打ち込む
http://localhost/oauth/authorize?response_type=code&client_id=abcdefghijklmiyOD9b6VQ81YTg5bNTRnqfPR2Lf&redirect_uri=http://test_client.com/callback
client_id、redirect_uriはDBに登録した内容を入力する。違うと「そんなクライアントいない」エラーが発生する。
ユーザがログインして無ければ「実装の前に」で作成したauthのログイン画面にリダイレクトする。
ユーザになった気持ちで
- E-Mail Address : test_user@gmail.com
- Password : secret
を入力する。
ログインに成功するとクライアントを認可・拒否の画面へリダイレクトする。
ユーザの気持ちになったまま、認可するをクリックすれば登録したリダイレクトURLにcode付きでリダイレクトする。
http://test_client.com/callback?code=jip9Rnp3NciFK7Jjr6zmWAcLyH2SwgxwCDjP6bSY
5-6-9.テスト2:アクセストークンの発行
POSTMANでテストする。
- httpメソッド:POST
- URL:http://localhost/oauth/access_token
- Body:x-www-form-urlencodeed
- grant_type : authorization_code
- client_id : abcdefghijklmiyOD9b6VQ81YTg5bNTRnqfPR2Lf
- client_secret : ABCDEFGHIJKLM2K7bOfgrYmMqfkEvPb8sWti0yOSz
- redirect_uri : http://test_client.com/callback
- code : jip9Rnp3NciFK7Jjr6zmWAcLyH2SwgxwCDjP6bSY
urlは実際の環境に合わせて適宜変更する。
client_id、client_secret、redirect_uriはDBに保存した内容を入れる。
codeはテスト1で発行された一時トークン(code以下のランダムな文字列)をコピペ。
「Send」ボタンクリックで以下のようなJSONが返ってくれば成功。
{
"access_token": "7l99LZqMQRGTU8LYe1Hohs7mzu1tnLM1xzev9A3P",
"token_type": "Bearer",
"expires_in": 3600
}
5-7.Refresh Token Grant
上記2つのGrantタイプと並行して使用する。アクセストークンの期限が切れた時に使う。
5-7-1.設定の追加
config/oauth2.phpの「grant_types」配列に以下を追加
'grant_types' => [
'refresh_token' => [
'class' => '\League\OAuth2\Server\Grant\RefreshTokenGrant',
'access_token_ttl' => 3600, //sec
'refresh_token_ttl' => 36000 //sec
]
],
* 実際は「grant_types」配列の中に「Password Grant」か「Auth Code Grant」がある。
5-7-2.ルートの設定
app/Http/routes.phpに以下を追加。
Route::post('oauth/access_token', function() {
return Response::json(Authorizer::issueAccessToken());
});
*このルートはPassword Grant」「Auth Code Grant」と共通
5-7-3.アクセストークンの発行:1回目
POSTMANで実行。
Password Grant」か「Auth Code Grant」のどちらでも良いのでアクセストークンの発行をするとJSONが以下のようになる。
{
"access_token": "qbdHCUKsu1w6GImz6wC9BZtvZPG8TtOmYlJgipSX",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "qgk1FdsbFu325p54IBdIahXFD635Nb0EA5QH4zqm"
}
「refresh_token」が追加されているはずなので確認する。
5-7-4.アクセストークンの発行:2回目以降
POSTMANで実行。
- httpメソッド:POST
- URL:http://localhost/oauth/access_token
- Body:x-www-form-urlencodeed
- grant_type : refresh_token
- client_id : abcdefghijklmiyOD9b6VQ81YTg5bNTRnqfPR2Lf
- client_secret : ABCDEFGHIJKLM2K7bOfgrYmMqfkEvPb8sWti0yOSz
- redirect_uri : http://test_client.com/callback
- refresh_token : qgk1FdsbFu325p54IBdIahXFD635Nb0EA5QH4zqm
urlは実際の環境に合わせて適宜変更する。
client_id、client_secret、redirect_uriはDBに保存した内容を入れる。
refresh_tokenはaccess tokenの発行の時のものをコピペ
「Send」ボタンクリックで以下のようなJSONが返ってくれば成功。
{
"access_token": "qI0wVEEUxWtJqpvnWvxDJNOdsChyGbyJ0GcKsAtA",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "kL7DdRfT4YqellA0DgUGyfLpfeWGsQR3OuoNQy2u"
}
5-8.スコープの追加
5-8-1.設定ファイルの修正
'scope_param' => true,
5-8-2.データのインサート
スコープをDBに登録する必要があるらしい。シーダーを作成する。
php artisan make:seeder OauthScopesTableSeeder
シーダークラスを以下のように修正する。
<?php
use Illuminate\Database\Seeder;
class OauthScopesTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
DB::table('oauth_scopes')->insert(
[
[
'id' => 'scope1',
'description' => 'test scope no1'
],
[
'id' => 'scope2',
'description' => 'test scope no2'
],
]);
}
}
シーダーの実行。
php artisan db:seed --class=OauthScopesTableSeeder
5-8-3.アクセストークンの発行
アクセストークン発行時にスコープの指定をする。
Password Grantの場合
アクセストークンの発行の時にPOSTMANのBodyのpasswordの次に
- scope : scope1,scope2
を追加してリクエストする。
Auth Code Grantの場合
一時トークンの発行の時のurlに
&scope=scope1,scope2
を追加する。
5-9.エンドポイントの保護
エンドポイント(WebAPIのパス)に今まで設定してきたOAuthの保護を追加する。具体的にはルート設定時にoauth
のミドルウェアを追加する。
例えばtest_endpoint
にoauthのチェックを追加する場合は以下のようにする。
Route::get('test_endpoint', ['middleware' => 'oauth', function() {
// return the protected resource
}]);
5-10.エンドポイントの保護(スコープを含む)
エンドポイントにスコープを含むOAuthの保護する場合。
Route::get('test_scope1', ['middleware' => 'oauth:scope1', function() {
// return the protected resource
}]);
複数のスコープで保護する場合。
Route::get('test_scope1_and_scope2', ['middleware' => 'oauth:scope1+scope2', function() {
// return the protected resource
}]);
複数のスコープを指定した場合は条件的に「AND」になる。「OR」で指定する方法はデフォルトでは無い模様。
もしその辺をカスタマイズしたい場合は\LucaDegasperi\OAuth2Server\Middleware\OAuthMiddleware.php
を参考にして新しく作って、app/Http/Kernel.php
の$routeMiddleware
のoauth
の設定を差し替えばいいのでは無かろうか。自分は試してない。
6.まとめ
取り敢えずこれでOAuth2に対応したサーバが出来たはず。
ライブラリ使えばわりかし簡単に出来た。皆さんも使ってみて下さい。
もし何かあればコメントか編集リクエストお願いします。
ではでは。