はじめに
Webアプリケーションでは、署名付きURLを使用してURLリンクが改竄されていないか確認したい場面が出てくると思います。例えば、各ユーザー用にサービスの退会や購読の中止を行うAPIのリンクなどでよく使用されると思います。
Laravelではこういう場面で使用出来る署名付きURLを簡単に生成出来るので、紹介したいと思います。
今回のサンプル
以下のようなシンプルなビューを作成して、署名付きURLを生成して、利用してみたいと思います。
各ボタンをクリックするとURLが生成されて表示されます。
生成されたURLをクリックすると、署名確認を行い問題無ければsucceed画面に遷移します。
署名確認が出来ないと、403エラー画面に遷移します。
ビュー
シンプルに作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | <!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Unsubscribe</title> </head> <body> if you want to unsubscribe, please click the below button.<br> <form action="/generate_signed_url" method="POST"> @csrf <input name="user_id" type="hidden" value="{{ $user_id }}"> <br> <input type="submit" value="Generate Unsubscribe URL"> </form> @if($signed_url ?? '') <div> Generated Unsubscribe URL<br> <div> <a href="{{ $signed_url }}"> {{ $signed_url }} </a> </div> </div> @endif <form action="/generate_temporary_signed_url" method="POST"> @csrf <input name="user_id" type="hidden" value="{{ $user_id }}"> <br> <input type="submit" value="Generate Temporary Unsubscribe URL(3 sec)"> </form> <div style="margin-top: 10px"> </div> @if($temporary_signed_url ?? '') <div> Generated Temporary Unsubscribe URL<br> <div> <a href="{{ $temporary_signed_url }}"> {{ $temporary_signed_url }} </a> </div> </div> @endif </body> </html> |
1 2 3 4 5 6 7 8 9 10 11 12 13 | <!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Unsubscribe</title> </head> <body> Succeed </body> </html> |
ルーティング
署名付きURLの生成には、名前付きルートが必要なのでルーティング設定しておきます。ここでは
unsubscribe
と名前付けています。
1 2 3 4 5 6 7 8 | // トップページ Route::get('/unsubscribe','UnsubscribeController@index'); // 署名付きURLの生成 Route::post('/generate_signed_url','UnsubscribeController@generate_signed_url'); // 期限あり署名付きURLの生成 Route::post('/generate_temporary_signed_url','UnsubscribeController@generate_temporary_signed_url'); // 署名付きURLクリック時 Route::get('/unsubscribe/result','UnsubscribeController@unsubscribe')->name('unsubscribe')->middleware('signed'); |
コントローラー
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | class UnsubscribeController extends Controller { public function index() { // サンプルパラメーター $user_id = 1234; return view('unsubscribe.index',compact('user_id')); } public function generate_signed_url(Request $request) { // サンプルパラメーター $user_id = $request->input('user_id'); // 署名付きURLの生成 $signed_url = URL::signedRoute('unsubscribe', ['user_id' => $user_id]); return view('unsubscribe.index',compact([ 'user_id', 'signed_url' ])); } public function generate_temporary_signed_url(Request $request) { // サンプルパラメーター $user_id = $request->input('user_id'); // 期限3秒 $expire = now()->addMilliseconds(3000); // 期限あり署名付きURLの生成 $temporary_signed_url = URL::temporarySignedRoute('unsubscribe', $expire, ['user_id' => $user_id]); return view('unsubscribe.index',compact([ 'user_id', 'temporary_signed_url' ])); } public function unsubscribe() { // 署名付きURLをクリックして、署名チェックに成功したら、下記画面に遷移。 return view('unsubscribe.succeed'); } } |
署名付きURLの生成
URLファサードの
signedRoute
メソッドを使用します。第一引数にルート名、第二引数に任意のクエリパラメーターを付与できます。
サンプルでは、
user_id=1234
のクエリパラメーターを付与してます。
サンプルでは、
http://localhost:8080/unsubscribe/result?user_id=1234&signature=cf2c234bef0a53b1c2efcc2dfa67263e5fc004a619a42230d2fb486d084216de
というURLが生成されます。
このリンクのクエリパラメーターのuser_idかsignatureの値を変更してクリックしてみると、署名の確認に失敗して403エラーが返却されます。
期限ありの署名付きURLの生成
URLファサードの
temporarySignedRoute
メソッドを使用します。第一引数にルート名、第二引数に有効期限、第三引数に任意のクエリパラメーターを付与できます。
サンプルでは、
$expire = now()->addMilliseconds(3000);
で、有効期限を3秒としています。
サンプルでは、
http://localhost:8080/unsubscribe/result?expires=1637952579&user_id=1234&signature=af4b315820de0aff1274d9abf0857115289a5285db1a5f5ee6d893bec3dcde87
の様なURLが生成されます。
expiresは有効期限なので、生成の度に値が変わります。
こちらもクエリパラメーターの値を変更したり、有効期限経過後(サンプルは3秒)にクリックしてみると、署名の確認に失敗して403エラーが返却されます。
署名のチェック
ミドルウェアを使用します。Kernel.phpの$routeMiddlewareに記載されてなければ追記します。
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class
ミドルウェアでは
hasValidSignature
メソッドを使用して、署名のチェックを行っています。
おまけ
署名の仕組
署名の確認方法ですが、URL生成時は、signatureのクエリパラメーターを除いたURLをハッシュ化した値をsignatureに持たせています。
署名確認時も同様に、signatureのクエリパラメーターを除いたURLをハッシュ化した値を、クエリパラメーターのsignatureの値と比較して、改竄されているかどうかを確認しています。
意外とシンプルなロジックの様です。興味のある方は、メソッドの中身を一度見てみてみるとすぐに分かるかと思います。
さいごに
有効期限ありの署名付きURLを簡単に生成することが出来ました。
よくあるメールでユーザー個別のURLリンクを送信する事も、サンプルの応用で簡単に出来そうです。