この記事は認証認可アドベントカレンダーの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をサポートしており、下記のシーケンスのように動作しているはずです。
Nginxモジュールについて
今回、Nginxで動作するモジュールの開発にOpenRestyを利用します。
OpenRestyを利用することにより、NginxでLuaJITや便利なライブラリが利用できるようになります。
また、LuaにはLuarocksというパッケージ管理システムが存在し、こちらも合わせて利用します。
OpenResty、Luarocksの詳しい利用方法については、どちらもOpenRestyの公式ページに記載されているのでこちらを確認してください。
- OpenRestyのインストール: https://openresty.org/en/installation.html
- Luarocksのインストール: https://openresty.org/en/using-luarocks.html
Nginxで利用するLua関連のディレクティブには実行順序が決まっています。
具体的には、Initialization Phase、Rewrite/Access Phase、Content Phase、Log Phaseが存在します。
認証処理はコンテンツの生成・取得よりも前に行う処理なので、Rewrite/Access Phaseで行うのが良さそうです。
よって、access_by_lua_file
ディレクティブで認証処理を記述しているLuaのファイルを呼び出します。
(引用元: 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()
に渡して利用します。
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のREADMEやWikiに記載されているので合わせて参照してください。
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は下記のようになります。
#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で下記のように記述することで指定できるようです。
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のサンプルは下記のようになります。
#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-fat
、centos
、xenial
であるからです。
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
を編集して下記の記述を追加します。
127.0.0.1 localhost keycloak
ここまでできたら実際にアクセスして動作確認を行います。
まず、http://localhost にアクセスします。すると、ログインページが表示されます。
ログインページが表示されたら、Username or emailにuser
、Passwordにpassword
をそれぞれ入力して[Log in]
を押下してください。
正しく動作していれば最終的にコンテンツが表示されます。
また、開発者ツールからCookieがセットされていることも確認できます。
最後にセッション情報が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による認証を導入する際の参考になったら幸いです。