Nginxリバースプロキシに認証システムを導入する方法をご紹介します!
ベーシック認証のような簡易的なものではなく、任意のプログラムでアクセス許可・拒否を実現できるようにします♪
auth_requestモジュールを使った認証システム
Nginxのauth_requestモジュールを活用すると、任意のプログラムでアクセス許可を制御することができます。
auth_request
を使うと、本番のアクセス前に外部のHTTPサーバーに認証リクエスト(サブリクエスト)を送り、アクセスを許可して良いか確認してくれます。許可が得られた場合のみ、リバースプロキシを介したアクセスを通してくれるようになります。
具体的な処理の流れは以下の通りです。
- クライアントのリクエストがNginxに到達する。
- Nginxから認証サーバーに向けてGETアクセスが飛ぶ(サブリクエスト)
- 指定された認証サーバーのパスに対して「アクセス許可していいですか?」というGETアクセスを行います。(これを”サブリクエスト”という)
- 認証サーバーがリクエストを処理。
- 認証サーバーはリクエストを受け取り、クライアントが認証されているかどうかを判断します。
- 認証サーバーの応答に基づいてアクセスが許可または拒否される。
- 認証サーバーがステータスコード200を返した場合は、Nginxは元のリクエストを処理し、設定通りにリバースプロキシします。
- 認証サーバーがステータスコード200以外を返した場合は、Nginxはアクセスを拒否し、エラーページを返します。
auth_request
モジュールを使用することで、Nginxに組み込まれた認証を使用する代わりに、外部の認証サーバーを利用してリクエストを検証できます。認証に失敗したときは、任意のログインページにリダイレクトさせることもできます。
OAuthと組み合わせたり、Google Authoricatorを導入したり、自由に認証をプログラムすることができるようになります。
NginxであればBASIC認証やDigest認証を使うことができますが、これらとは異なり、任意のフォームで認証ページを作ることができます。
ブラウザのパスワードマネージャーやTouchIDによるログインができるのも大きなメリットです!アクセシビリティが向上しますよ♪
また、auth_requestであれば任意の認証バックエンドと連携できますので、KeyCloackなどの認証専用アプリケーションと組み合わせたり、他の認証システムと組み合わせることも可能です。二段階認証も対応できますし、大規模なシステムや複雑なアクセスルールにも柔軟に対応できるようになります。
PHPでリバースプロキシ認証システムをつくる
さっそく構築していきましょう!まずは認証バックエンドを作成します。
今回はPHPでログインフォームをつくり、ログインに成功したときのみリバースプロキシを介したアクセスを許可するようにしてみます。
ログインされていない状態では、自動的にログインフォームに遷移し、ログインを促すようにします。
▼完成イメージ
認証サーバーをつくる
まずはPHPで簡易的な認証サーバーをつくります。
認証サーバーに求められることは次の通りです。
- 認証機能を持つこと
- Nginxのサブリクエストを処理するエンドポイントを用意する
- エンドポイントはログイン済みならステータスコード200を返す。ログインされていないならステータスコード200以外を返す。
KeyCloakなどの認証バックエンドを利用してもよいですが、今回は原理を理解するためにPHPで自作しました。
authディレクトリをつくり、上記のファイルを作成しました。
基本的な作り方は一般的なPHPのログインフォームと同じです。check.phpについてはステータスコードだけ返せばOKなので、以下のようなコードで十分です。(全体のサンプルコード作れば良かったのですが、時間なくて。すいません)
<?php
// セッション開始
session_name("NGINXAUTH_SESSION_ID");
@session_start();
// ログインしていれば200を返す
if (isset($_SESSION['username'])) {
http_response_code(200);
echo "200 ログインされています";
exit;
}else{
http_response_code(401);
echo "401 ログインが必要です";
exit;
}
?>
PHPのサーバー機能を立ち上げて、作成したauthディレクトリをホスティングします。
$ php -S localhost:4001
今回はlocalhost:4001で認証サーバーをホスティングします。
Nginxで認証サーバーによるアクセス制限を設定する
Nginxの設定ファイルを編集し、認証サーバーによるアクセス制限を設定します。
例として、もともと以下のようなリバースプロキシの設定があったとします。
server{
...略...
location /{
proxy_pass http://hogehoge.com/;
}
...以下略...
}
auth_requestの設定を追加していきます。太字部分が追加した箇所です。
server{
...略...
location /{
proxy_pass http://hogehoge.com/;
auth_request /auth/check;
auth_request_set $auth_status $upstream_status;
error_page 401 = @error401;
}
//認証バックエンドへのリバースプロキシ設定
location /auth/ {
proxy_pass http://localhost:4001/;
auth_request off;
auth_request_set $auth_status $upstream_status;
}
//サブリクエストを処理するエンドポイントへのリバースプロキシ設定
location = /auth/check {
internal;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header Host $http_host;
proxy_pass http://localhost:4001/check.php;
}
//アクセス拒否されたとき(未ログイン時)にログインページに飛ばす設定
location @error401 {
return 302 /auth/login.php?redirect_to=$request_uri;
//↑【補足】アクセス時のURIをクエリパラメータで渡しておくと、ログイン後にリダイレクトさせたりしやすいゾ
}
...以下略...
}
解説
このままだとわかりにくいので、動作の流れを解説しておきます。
上記の設定で最初にNginxにアクセスがあったときの流れは以下の通りです。複雑なので少しクドめに解説します。
- Nginxの/にアクセスがあると、locationディレクティブ(/) の設定にもとづき、auth_requestが作動します。
- auth_requestに/auth/checkが設定されているため、Nginxは/auth/checkにサブリクエストとしてGETを送信します。※なお、auth_request setの内容はお決まりとなっています。このままコピペしましょう。
- 別のlocationディレクティブ(location = /auth/check)で/auth/checkのproxy_passがhttp://localhost:4001/check.phpに設定されているため、2.で発生したサブリクエストは、PHPで自作した認証バックエンドにリバースプロキシされます。※なお、proxy_pass以外の設定内容はお決まりとなっています。このままコピペしましょう。
- アクセスを受けたcheck.phpはログイン状況を確認します。
ログインされていれば200を返します。
ログインされていなければ、401など200以外のステータスコードをNginxに返します。 - Nginxはサブリクエスト先から返されたステータスコードを判別します。
200ならばアクセスを許可します。location/ディレクティブで設定されたproxy_passにリバースプロキシし、クライアントは正常にアクセスできます。これで完了です。
返されたステータスコードが401ならば、401エラー処理に移行します。 - アクセス拒否された場合はlocation @error401ディレクティブにより、401エラーが処理されます。returnによりログインページへのリダイレクトを設定しているため、クライアントはログインページ(/auth/login.php)に誘導されます。/authはアクセス制限を設けていないため、未ログインの状態でもアクセスできますから、問題なくログインページが表示されます。
- login.phpからユーザーがログインします。POSTが/auth/login.phpに送られますが、/auth以下はアクセス制限していないため、ログインできます。
- ログインに成功したら、再度ブラウザから/にアクセスします。
再びサブリクエストがcheck.phpに行われますが、今度はログインが完了しているため200が返され、アクセスが許可されます。
サブリクエストはすべてのアクセスにつき行われます。
auth_requestを設定したlocationに該当するのならば、画像ファイルなどのアセット含め、すべてのアクセスにサブリクエストが生じます。アクセス数が多い場合は、認証バックエンドへの負担を減らす工夫が必要になりそうです。
まとめ
いかがでしたか?
今回はauth_requestを使った認証リバースプロキシの構築方法について解説しました。
実際に我が家の自宅サーバーで運用していますが、非常に快適です。IPアドレスやパスに応じた詳細なアクセス制限もかけられますし、BASIC認証と異なりブラウザのパスワードマネージャーにID・パスワードを記憶させることもできます。
認証以外の部分も自由に実装できるので、ログインされたらLINE Notifyに通知するなどといったことも可能です。さまざまな場で活躍してくれると思います。
不明点ありましたらコメントでご質問ください。
コメント