背景・動機
既存のSymfonyプロジェクトに署名付きのREST APIを追加することになり、他の処理に影響を与えないよう特定のControllerのみ追加する必要があったため、備忘録を兼ねて実装方法を投稿します。
環境
Symfony 3.0.1
PHP 7.0.2
手順
ざっくりと以下のような手順になります。
- 署名認証したいControllerに設定するインターフェースを追加。
- イベントリスナーを作成しサービスコンテナに登録。
- イベントリスナー内でControllerを参照し、インターフェースを見て署名認証を行う。
- 署名認証に失敗すれば例外をスロー。
1. 署名認証したいController用のインターフェースを作成
空のインターフェースでOKです。
署名認証が必要なControllerを特定する目的で利用します。
interface SignatureAuthenticatedController {}
2. イベントリスナーを作成
以下のようなイベントリスナーを作成します。
class SignatureListener {
public function onKernelController(FilterControllerEvent $event) {
// Controllerを取得
$controller = $event->getController();
if (!is_array($controller)) {
return;
}
// Controllerがインターフェースを実装しているかチェック
if ($controller[0] instanceof SignatureAuthenticatedController) {
// Requestを取得
$request = $event->getRequest();
// ヘッダーから署名を取得
$signature = $request->headers->get('x-hogehoge-signature');
// Requestを使ってダイジェスト値を計算
// 署名の方法については適宜実装してください
$canonicalString = $request->getMethod() . "\n" .
$request->getHost() . "\n" .
$request->getPathInfo() . "\n" .
$request->getQueryString() . "\n" .
$request->getContent();
$digest = hash_hmac('sha256', base64_encode($canonicalString), 'secret');
// ダイジェスト値と署名が違っていたら例外をスロー
if ($signature !== $digest) {
throw new AccessDeniedHttpException('Invalid signature.');
}
}
}
}
3. services.ymlにリスナーを追加
app.action_listener:
class: AppBundle\EventListener\SignatureListener
tags:
- { name: kernel.event_listener, event: kernel.controller, method: onKernelController }
- { name: kernel.event_listener, event: kernel.exception, method: onKernelException }
4. 署名で保護したいControllerにインターフェースを実装
class SecretController extends Controller implements SignatureAuthenticatedController {
}
今回はControllerとExceptionのイベントリスナーを利用しましたが、他にも色々あるようですのでいつか試してみたいと思います。
もし似たような問題を抱えている方がおられましたら、割と簡単な手順で実装できますので、お試しください。