Nginxで認証リバースプロキシをつくってみよう!(auth_requestの使い方)

サーバー

Nginxリバースプロキシに認証システムを導入する方法をご紹介します!

ベーシック認証のような簡易的なものではなく、任意のプログラムでアクセス許可・拒否を実現できるようにします♪

スポンサーリンク

auth_requestモジュールを使った認証システム

Nginxのauth_requestモジュールを活用すると、任意のプログラムでアクセス許可を制御することができます。

auth_requestを使うと、本番のアクセス前に外部のHTTPサーバーに認証リクエスト(サブリクエスト)を送り、アクセスを許可して良いか確認してくれます。許可が得られた場合のみ、リバースプロキシを介したアクセスを通してくれるようになります。

具体的な処理の流れは以下の通りです。

  1. クライアントのリクエストがNginxに到達する。
  2. Nginxから認証サーバーに向けてGETアクセスが飛ぶ(サブリクエスト)
    • 指定された認証サーバーのパスに対して「アクセス許可していいですか?」というGETアクセスを行います。(これを”サブリクエスト”という)
  3. 認証サーバーがリクエストを処理。
    • 認証サーバーはリクエストを受け取り、クライアントが認証されているかどうかを判断します。
  4. 認証サーバーの応答に基づいてアクセスが許可または拒否される。
    • 認証サーバーがステータスコード200を返した場合は、Nginxは元のリクエストを処理し、設定通りにリバースプロキシします。
    • 認証サーバーがステータスコード200以外を返した場合は、Nginxはアクセスを拒否し、エラーページを返します。

auth_requestモジュールを使用することで、Nginxに組み込まれた認証を使用する代わりに、外部の認証サーバーを利用してリクエストを検証できます。認証に失敗したときは、任意のログインページにリダイレクトさせることもできます。

OAuthと組み合わせたり、Google Authoricatorを導入したり、自由に認証をプログラムすることができるようになります。

auth_requestを使うメリット

NginxであればBASIC認証やDigest認証を使うことができますが、これらとは異なり、任意のフォームで認証ページを作ることができます。

ブラウザのパスワードマネージャーやTouchIDによるログインができるのも大きなメリットです!アクセシビリティが向上しますよ♪

また、auth_requestであれば任意の認証バックエンドと連携できますので、KeyCloackなどの認証専用アプリケーションと組み合わせたり、他の認証システムと組み合わせることも可能です。二段階認証も対応できますし、大規模なシステムや複雑なアクセスルールにも柔軟に対応できるようになります。

スポンサーリンク

PHPでリバースプロキシ認証システムをつくる

さっそく構築していきましょう!まずは認証バックエンドを作成します。

今回はPHPでログインフォームをつくり、ログインに成功したときのみリバースプロキシを介したアクセスを許可するようにしてみます。

ログインされていない状態では、自動的にログインフォームに遷移し、ログインを促すようにします。

▼完成イメージ

Nginxのリバースプロキシにアクセスすると、ログインページに飛ばされる
ログインされた場合のみ、リバースプロキシへのアクセスを許可する。[アクセスを続行]ボタンを押すと、最初にアクセスしたURLへ遷移する。

認証サーバーをつくる

まずはPHPで簡易的な認証サーバーをつくります。

認証サーバーに求められることは次の通りです。

  • 認証機能を持つこと
  • Nginxのサブリクエストを処理するエンドポイントを用意する
  • エンドポイントはログイン済みならステータスコード200を返す。ログインされていないならステータスコード200以外を返す。

KeyCloakなどの認証バックエンドを利用してもよいですが、今回は原理を理解するためにPHPで自作しました。

authディレクトリをつくり、上記のファイルを作成しました。

  • login.php(ログインページ)
  • index.php(ログイン後に表示されるページ。ログアウトボタンなどを配置)
  • logout.php(ログアウトボタンを押すとここに飛んでログアウトする)
  • check.php(サブリクエスト用のエンドポイント。ログイン状態ならステータスコード200を返す。)
  • functions.php(各ファイルで使う関数はここにまとめて置いている)

基本的な作り方は一般的な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にアクセスがあったときの流れは以下の通りです。複雑なので少しクドめに解説します。

  1. Nginxの/にアクセスがあると、locationディレクティブ(/) の設定にもとづき、auth_requestが作動します。
  2. auth_requestに/auth/checkが設定されているため、Nginxは/auth/checkにサブリクエストとしてGETを送信します。※なお、auth_request setの内容はお決まりとなっています。このままコピペしましょう。
  3. 別のlocationディレクティブ(location = /auth/check)で/auth/checkのproxy_passがhttp://localhost:4001/check.phpに設定されているため、2.で発生したサブリクエストは、PHPで自作した認証バックエンドにリバースプロキシされます。※なお、proxy_pass以外の設定内容はお決まりとなっています。このままコピペしましょう。
  4. アクセスを受けたcheck.phpはログイン状況を確認します。
    ログインされていれば200を返します。
    ログインされていなければ、401など200以外のステータスコードをNginxに返します。
  5. Nginxはサブリクエスト先から返されたステータスコードを判別します。
    200ならばアクセスを許可します。location/ディレクティブで設定されたproxy_passにリバースプロキシし、クライアントは正常にアクセスできます。これで完了です。
    返されたステータスコードが401ならば、401エラー処理に移行します。
  6. アクセス拒否された場合はlocation @error401ディレクティブにより、401エラーが処理されます。returnによりログインページへのリダイレクトを設定しているため、クライアントはログインページ(/auth/login.php)に誘導されます。/authはアクセス制限を設けていないため、未ログインの状態でもアクセスできますから、問題なくログインページが表示されます。
  7. login.phpからユーザーがログインします。POSTが/auth/login.phpに送られますが、/auth以下はアクセス制限していないため、ログインできます。
  8. ログインに成功したら、再度ブラウザから/にアクセスします。
    再びサブリクエストがcheck.phpに行われますが、今度はログインが完了しているため200が返され、アクセスが許可されます。

サブリクエストはすべてのアクセスにつき行われます。

auth_requestを設定したlocationに該当するのならば、画像ファイルなどのアセット含め、すべてのアクセスにサブリクエストが生じます。アクセス数が多い場合は、認証バックエンドへの負担を減らす工夫が必要になりそうです。

まとめ

いかがでしたか?

今回はauth_requestを使った認証リバースプロキシの構築方法について解説しました。

実際に我が家の自宅サーバーで運用していますが、非常に快適です。IPアドレスやパスに応じた詳細なアクセス制限もかけられますし、BASIC認証と異なりブラウザのパスワードマネージャーにID・パスワードを記憶させることもできます。

認証以外の部分も自由に実装できるので、ログインされたらLINE Notifyに通知するなどといったことも可能です。さまざまな場で活躍してくれると思います。

不明点ありましたらコメントでご質問ください。

コメント

タイトルとURLをコピーしました