LoginSignup
29
22

More than 3 years have passed since last update.

NginxをOpenID ConnectのRelying Partyとして実装する

Last updated at Posted at 2019-12-23

この記事は認証認可アドベントカレンダーの24日目の記事です。

はじめに

本記事ではNginxをOpenID ConnectのRelying Partyとして実装する方法を紹介します。

NginxをOpenID ConnectのRelying Partyとして実装することによって、既存の実装に依存せずOpenID Connectによる認証の導入が期待できます。

今回、NginxのOpenIDConnectのRelying Partyとしての実装にlua-resty-openidcを利用します。
Nginx+ではnginx-openid-connectが利用できます。まず、こちらを試してみるのが良いかもしれません。

本記事ではサンプル実装も用意しています。最後に動作確認方法も記載しているので、合わせて確認していただければ幸いです。
https://github.com/kg0r0/lua-resty-openidc-example

サンプル実装はAuthorization Code Flowをサポートしており、下記のシーケンスのように動作しているはずです。
lua_resty_openidc_diagram.png

Nginxモジュールについて

今回、Nginxで動作するモジュールの開発にOpenRestyを利用します。
OpenRestyを利用することにより、NginxでLuaJITや便利なライブラリが利用できるようになります。
また、LuaにはLuarocksというパッケージ管理システムが存在し、こちらも合わせて利用します。
OpenResty、Luarocksの詳しい利用方法については、どちらもOpenRestyの公式ページに記載されているのでこちらを確認してください。

Nginxで利用するLua関連のディレクティブには実行順序が決まっています。
具体的には、Initialization Phase、Rewrite/Access Phase、Content Phase、Log Phaseが存在します。
認証処理はコンテンツの生成・取得よりも前に行う処理なので、Rewrite/Access Phaseで行うのが良さそうです。
よって、access_by_lua_fileディレクティブで認証処理を記述しているLuaのファイルを呼び出します。
image.png
(引用元: https://github.com/openresty/lua-nginx-module)

OpenID Connectによる認証の導入

lua-resty-openidcを利用してNginxにOpenID Connectによる認証を導入していきます。
まず、Luarocksでlua-resty-openidcをインストールします。


$ /usr/local/openresty/luajit/bin/luarocks install lua-resty-openidc

一連の認証処理は、lua-resty-openidcのauthenticate()を呼び出すことで行います。
よって、まずはlua-resty-openidcやauthenticate()に渡すパラメーターを用意する必要があります。
具体的には、Redirect URI、OpenID Provider Configuration RequestのURL、Client ID、Client Secretといった値を事前にOpenID provider(OP)に登録・取得しておきます。
用意した値は下記のようにauthenticate()に渡して利用します。

main.lua
local opts = {
    ssl_verify = "no",
    redirect_uri = "https://<hostname>/secure/redirect_uri",
    discovery = "<issuer>/.well-known/openid-configuration",
    client_id = "<client_id>",
    client_secret = "<client_secret>"
}

local res, err = require("resty.openidc").authenticate(opts)
if err then
    ngx.log(ngx.ERR, err)
    ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
ngx.req.set_header("X-USER", res.id_token.sub)

ngx.var.pass="http://webapp:3000/

その他設定値についての詳細やGoogle Signinで利用する設定のサンプルなどはlua-resty-openidcのREADMEWikiに記載されているので合わせて参照してください。
authenticate()を呼び出し、エラーがなかった場合、戻り値として下記の通り、ID Token、Access Tokenやユーザーに関する情報が返ってきます。

-- at this point res is a Lua table with 3 keys:
-- id_token : a Lua table with the claims from the id_token (required)
-- access_token: the access token (optional)
-- user : a Lua table with the claims returned from the user info endpoint (optional)
(引用元: https://github.com/zmartzone/lua-resty-openidc/blob/master/README.md#sample-configuration-for-google-signin)

次に、上記の認証処理を行うスクリプトを呼び出す設定をnginx.confに記述します。
スクリプトの配置について、OpenRestyを利用する場合、OpenRestyに含まれるパッケージはデフォルトで/usr/local/openresty/以下に展開されるため、今回は認証処理を記述したLuaスクリプトも/usr/local/openresty/以下に配置します。
スクリプトの配置場所が決まったら、前述したとおりnginx.confのaccess_by_lua_fileディレクティブでスクリプトのパスを指定します。
ここまでのlua-resty-openidcを利用する設定を記述した場合、nginx.confは下記のようになります。

nginx.conf
#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

        server {
        listen       80;
        server_name  localhost;
        resolver local=on ipv6=off;

        location / {
                set $pass '';
                default_type 'text/html';
                access_by_lua_file /usr/local/openresty/main.lua;
                proxy_pass $pass;
        }

    }

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

以上でlua-resty-openidcの導入は終了です。問題なく設定ができていれば、ここまでで動作確認を行うことができるはずです。

Redisによるセッション管理の導入

実際の運用を考えた際、セッション情報をNginxのメモリ上ではなく、Redisなどに格納する必要がでてきます。
lua-resty-openidcは下記の通り、内部でlua-resty-sessionを利用しており、セッション情報の保管先の指定が可能とのことです。

It maintains sessions for authenticated users by leveraging lua-resty-session thus offering a configurable choice between storing the session state in a client-side browser cookie or use in of the server-side storage mechanisms shared-memory|memcache|redis.
(引用元: https://github.com/zmartzone/lua-resty-openidc/blob/master/README.md#lua-resty-openidc)

しかし、本記事の執筆時点(2019/12/21)ではlua-resty-openidcには設定方法が記載されていなかったため、lua-resty-sessionのドキュメントを参照して設定を行います。
lua-resty-sessionのREADMEのPluggable Storage Adaptersの章に設定方法は記載されています。
セッション情報の保管先は、nginx.confで下記のように記述することで指定できるようです。

nginx.conf
set $session_storage redis;

Redisの他にセッション情報の保管先として下記の指定が可能であると記載されています。

  • cookie aka Client Side Cookie (this is the default adapter)
  • shm aka Lua Shared Dictionary
  • memcache aka Memcached Storage Backend
  • redis aka Redis Backend

今回はセッション情報の保管先としてRedisを指定するので下記のパラメーターを利用して接続先などを指定します。
Redis以外のストレージを保管先として指定する際は適宜読み替えてください。


set $session_redis_prefix        sessions;
set $session_redis_socket        unix:///var/run/redis/redis.sock;
set $session_redis_host          127.0.0.1;
set $session_redis_port          6379;
set $session_redis_auth          password;
set $session_redis_uselocking    on;
set $session_redis_spinlockwait  10000;
set $session_redis_maxlockwait   30;
set $session_redis_pool_timeout  45;
set $session_redis_pool_size     10;

実際に接続先の指定を行ったnginx.confのサンプルは下記のようになります。

nginx.conf
#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

        server {
        listen       80;
        server_name  localhost;
        resolver local=on ipv6=off;

        location / {
                set $pass '';
                set $session_storage redis;
                set $session_redis_prefix        sessions;
                set $session_redis_host          redis;
                set $session_redis_port          6379;
                default_type 'text/html';
                access_by_lua_file /usr/local/openresty/main.lua;
                proxy_pass $pass;
        }

    }

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

以上でセッション管理の設定も終了になります。

動作確認

冒頭でも述べた通り、実装のサンプルを用意しているのでこちらの動作確認方法について触れていきます。
https://github.com/kg0r0/lua-resty-openidc-example

動作確認時、ブラウザに残っている情報により意図した動作をしないことがあります。
なるべくシークレットブラウザを利用して動作確認を行ってください。
もし意図した動作をしない場合などはIssueを利用していただけると幸いです。

サンプル実装について

サンプル実装はOpenResty Official Docker Imagesを利用しています。
サンプル実装で利用しているDocker Imageのflavorsはalpine-fatを選択しています。
これは、Luaのパッケージ管理であるLuaRocksが含まれているのがalpine-fatcentosxenialであるからです。

LuaRocks is included in the alpine-fat, centos, and xenial variants. It is excluded from alpine because it generally requires a build system and we want to keep that variant lean.
(引用元: https://hub.docker.com/r/openresty/openresty#luarocks)

また、サンプル実装では、OpenID provider(OP)として、Keycloakを利用しています。
今回利用するKeycloakは事前に動作確認に必要な情報が登録された状態で起動します。

手順

まず、下記の通りコマンドを実行して各コンポーネントを起動します。


$ git clone https://github.com/kg0r0/lua-resty-openidc-example.git
$ cd lua-resty-openidc-example
$ docker-compose build
$ docker-compose up -d

ここで、設定しているリダイレクトURLの関係上、http://keycloak でアクセスした際に、http://127.0.0.1 にアクセスされるように/etc/hostsを編集して下記の記述を追加します。

/etc/hosts
127.0.0.1   localhost keycloak

ここまでできたら実際にアクセスして動作確認を行います。
まず、http://localhost にアクセスします。すると、ログインページが表示されます。
ログインページが表示されたら、Username or emailにuser、Passwordにpasswordをそれぞれ入力して[Log in]を押下してください。

Screen Shot 2019-12-22 at 19.16.16.png

正しく動作していれば最終的にコンテンツが表示されます。
また、開発者ツールからCookieがセットされていることも確認できます。
Screen Shot 2019-12-22 at 19.19.34.png

最後にセッション情報がRedisに登録されているか確認します。
まず、redisのCONTEINER IDを確認します。


$ docker ps -a
CONTAINER ID        IMAGE                                COMMAND                  CREATED             STATUS              PORTS                    NAMES
521f1756ccfb        redis:alpine                         "docker-entrypoint.s…"   2 weeks ago         Up 2 weeks          6379/tcp                 lua-resty-openidc-example_redis_1

RedisのCONTEINER IDを確認できたらredis-cliで登録されているkeyを確認してみます。


$ docker exec -it 521f1756ccfb redis-cli
127.0.0.1:6379> keys *
1) "sessions:lfgDATCFRlSKF1ML5T7usw.."

Cookieとしてセットされていた値が登録されていました。
Redisへのセッション情報の登録も問題なく動作しているようです。

おわりに

本記事ではlua-resty-openidcを利用し、NginxをOpenID ConnectのRelying Partyとして実装する方法を紹介しました。
また、lua-resty-openidcでセッション管理をカスタマイズする方法も合わせて紹介しました。
OpenID Connectによる認証を導入する際の参考になったら幸いです。

参考

29
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29
22