PHPを使ったプログラムで、簡単なWebAPIを実装したい場合があります。
小さなWebAPIサーバなど、シンプルなサービスを実装する時にSlim Frameworkのようなマイクロフレームワークは助けになりますが、クライアントの認証がネックになることがあります。社内LANなど、閉じた環境でプログラムを実行したい場合なら認証なしでサービスを公開しても良いですが、インターネット上に公開されたサーバの場合、そうはいきません。
そこで、今回は、APIキーによる簡単なアクセス認証を実装してみます。
サーバ上に登録されたAPIキーをリクエストパラメータとして送ってきたクライアントからのみ処理を行い、キーを送って来ないリクエストはエラー応答を返します。
今回の記事では、Slim Frameworkのバージョン3を利用しています。
Slimにおけるミドルウェア機能
Slim Frameworkでは、サーバへのリクエストが来た時、リクエストを処理する前後にハンドラとして処理を挟み込めるミドルウェア機能というものがあります。ミドルウェア処理は\Slim\App::add()メソッドで登録することができます。
以下のコードは、ミドルウェアを利用した簡単なサンプルプログラムです。メインの処理の前後に、メッセージを表示させています。
<?php
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
require 'vendor/autoload.php';
$app = new \Slim\App;
class TestMiddleware
{
public function __invoke(Request $request, Response $response,callable $next)
{
$response->getBody()->write("before<hr />" . PHP_EOL);
$response = $next($request, $response);
$response->getBody()->write("after<hr />" . PHP_EOL);
return $response;
}
}
$app->add(new TestMiddleware());
$app->get('/', function (Request $request, Response $response) {
// WebAPIの実行
$response->getBody()->write("Hello.");
return $response;
});
$app->run();
上記のスクリプトの実行結果は下記の通りです。
before<hr />
Hello.
after<hr />
結果を見ると、本来のget処理の出力である”Hello."の前後に処理を挟み込めていることが分かります。
ミドルウェア作成時の注意点は、メソッドの戻り値として$responseを必ず(MUST)返さなければならないことです。
ミドルウェアの仕組み
前述の例で見た通り、ミドルウェアは\Slim\App::add()メソッドで登録することができます。
add()メソッドのパラメータはcallableオブジェクトか文字列です。例のようにパラメータにクラスのインスタンスを渡したときは__invoke()メソッドが呼ばれます。
callableオブジェクトが渡せるので、以下のようにクロージャーを直接指定できます。小さなシステムで認証を別クラスに分けるまでもない場合は、この書き方がシンプルになります。
$app->add(function (Request $request, Response $response,callable $next) {
...
});
ミドルウェアからは、$next()メソッドで本来の処理を呼び出すことができます。$next()の実行前にロジックを書けば前処理、後に処理を書けば後処理になります。
また、認証エラーなどでリクエストされた処理を実行したくない場合は、$next()自体を呼び出さなければよいです。
ミドルウェアを使ってAPIキー認証を作る
Slim Frameworkが提供するミドルウェアの基本を理解したところで、APIキーによる認証処理を作ってみます。
class KeyAuthMiddleware
{
const VALID_API_KEY = 'ABC123'; // APIキー: 本来は外部ファイルやDBなどの管理が望ましい
public function __invoke(Request $request, Response $response, callable $next)
{
// 認証を行う
$params = $request->getQueryParams();
if (!isset($params['apikey']) || $params['apikey'] != self::VALID_API_KEY) {
// 認証エラー: 401
$response = $response->withStatus(401, 'Unauthorized');
return $response;
}
// メインの処理を実行
$response = $next($request, $response);
return $response;
}
}
$app->add(new KeyAuthMiddleware);
$request->getQueryParams()でGETパラメータを取得しています。
apikeyのパラメータが存在しており、かつ、指定された値(self::VALID_API_KEY)かのチェックを行っています。実際は、たくさんのAPIキーを発行して、DB管理するといった実装になるかと思いますが、ここではconst値との比較をしています。
エラーだった時は、$next()をコールせず、HTTPの401エラーを返し、認証に成功した場合は$next()メソッドで本来のアクションを実行した上で、結果を返しています
ミドルウェアの多重定義
アプリによっては、認証処理のほかにアプリの応答時刻のロギングなど、複数のミドルウェアを定義したい場合があります。
この場合は、単に複数add()メソッドで処理を登録すればよいです。最初のミドルウェアに渡されるnext()メソッドは、本来のアクションではなく2つ目のミドルウェアの処理です。2つ目のミドルウェアに渡されるnext()メソッドが本来のアクションとなります。
複数登録した場合は、各ミドルウェアがネストした形になるわけです。
$app->add(function (Request $request, Response $response,callable $next) {
// Middleware 2
});
$app->add(function (Request $request, Response $response,callable $next) {
// Middleware 1(後で登録した方が先に呼ばれる)
});
複数のミドルウェアを登録した場合の呼び出し順ですが、直感と異なり、後で定義した方が先に呼ばれます。
これは後で定義したメソッドで、処理をさらに包んでいくイメージです。
特定のリクエストの時のみミドルウェアを実行させる
最初の例では、すべてのリクエストに対して一律で認証を行っていましたが、特定のリクエストのみミドルウェアを登録し、認証チェックを行うこともできます。
下記の例では'/admin/getSystemInfo'のリクエスト時のみ,認証処理を追加しています。追加の方法はget()の後にadd()をメソッドチェーンさせればよいです。このような定義方法をSlim frameworkではRoute middlewareと呼んでいます。
// 認証が不要な処理
$app->get('/user/getInfo', function (Request $request, Response $response) {
...
});
// 認証が必要な処理
$app->get('/admin/getSystemInfo', function (Request $request, Response $response) {
...
})->add(new KeyAuthMiddleware);
ミドルウェアから認証したユーザの情報を渡す
アプリケーションによっては、ユーザの情報を別途システム管理している場合もあります。このような場合、認証処理のミドルウェアから、メインのアクション処理へ対してユーザ情報を渡したい場合があります。
パラメータの受け渡し方法は色々ありますが、以下のように$requestオブジェクトのwithAttribute()メソッドを使と、シンプルでわかりやすいです。
class KeyAuthMiddleware
{
const VALID_API_KEY = 'ABC123'; // APIキー: 本来は外部ファイルやDBなどの管理が望ましい
public function __invoke(Request $request, Response $response, callable $next)
{
// 認証を行う
...
// ユーザ情報を取得して、メイン処理へ渡す
$userInfo = getUserInfo($apiKey);
$request->withAttribute('user', $userInfo);
// メインの処理を実行
$response = $next($request, $response);
return $response;
}
}
$app->add(new KeyAuthMiddleware);
One thought on “[PHP]Slim FrameworkでAPIキーによるアクセス認証を実装する”