NGINX PlusでJWT認証をやってみた!
今回は、NGINX Plusでしか使えない機能の1つ「JWT認証」について詳しく説明します。
みなさん、こんにちは。
東京エレクトロンデバイスでエンジニアをやっているあつふみです。
今回は、grasys様とリレー形式のブログがTEDとしては第1回目ということで、私からはセキュリティをテーマとしてNGINX Plusを使ったJWT認証について紹介したいと思います。ちなみにgrasys様については、以下を見てみてください。すごい会社です。
grasys様は3大クラウドの活用と世界トップレベルのプラットフォームを組み合わせ、お客様のインフラを最適化されております。
ホームページに書かれている通り高い技術力が売りの会社となりますので、クラウドでシステム構築を検討している方は是非ご相談頂ければと思います。
株式会社grasys:https://www.grasys.io/
それでは、本題に入ります。NGINX Plusでしか使えない機能の1つにJWT認証があります。
JWT認証はAPIクライアントの認証ではメジャーな手法かと思いますが、NGINX PlusではNativeで対応しています。OSSのNGINXだと、実装するのにいろいろな準備が必要だったりするのですが、NGINX Plusだと楽ちんです。
では、まずJWT認証についてです。
JWTとはJSON Web Tokenの略称で、JSONデータ構造で表現したトークンです。このトークンを使って認証することをJWT認証といいます。仕様はRFC7519で定められています。
https://datatracker.ietf.org/doc/html/rfc7519
このJWTの構造は、ヘッダー、ペイロード、署名の3つの構造で成り立っています。
非常に見づらいですが、以下のように.「ドット」で区切られています。前から順番に、ヘッダー「青色文字」、ペイロード「茶色文字」、署名「赤文字」という構造になっていて、それぞれbase64でエンコードされている値になっています。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICAgInN1YiI6ICJsYzEiLAogICAgImVtYWlsIjog
ImxpYW0uY3JpbGx5QG5naW54LmNvbSIsCn0=.VGYHWPterIaLjRi0LywgN3jnDUQbSsFptUw99g2slfc
NGINX PlusでJWT認証をするためには、このJWTをつくります。
JSON Web Keyの登録
JWTを作成する前に、まずNGINX Plusに登録するJWTのキーを作成します。任意の文字列を決め、以下のコマンドでbase64でエンコードします。
#
echo -n <任意の文字列> | base64 | tr ‘+/’ ‘-_’ | tr -d ‘=’
この値を、以下ような形式でファイルに保存しておきます。k:には、↑の手順で作成したbase64でエンコードしたキーを入力します。
{"keys":
[{
"k":"dGVkbmdpxxxxxxx",
"kty":"oct",
"kid":"0001"
}]
}
このファイルをNGINX Plusに以下のようなファイル名にして配置しておきます。ディレクトリやファイル名は任意ですが、拡張子は、jwkにしておきます。
/etc/nginx/conf.d/jwt/api_secret.jwk
なお、ここのktyやkidについては、以下のRFCを見てみてください、詳しく書いてあります。
https://datatracker.ietf.org/doc/html/rfc7517
NGINX Plusを使う場合は、ktyは、oct(octet sequence)、kidは任意のシリアル番号を入れるようです。
JWTヘッダーの作成
ここからJWTを作成していきます。まずJWTヘッダーを作成します。以下のようにJSON形式でヘッダーを作成し、base64でエンコードし、その値をメモしておきます。
JWTヘッダー
{
"typ":"JWT",
"alg":"HS256",
"kid":"0001"
}
base64でエンコード
# echo -n {"typ":"JWT","alg":"HS256","kid":"0001"} | base64 | tr '+/' '-_' | tr -d '='
dHlwOkpXVCBhbGc6SFMyNTYga2lkOjAwMDE
ここのtypは、typeを意味しているのでJWT、algはどのアルゴリズムを使って署名するかなので、HMAC SHA256を指定します。最後のkidはどのシリアルのJSON Web Keyを使って署名されるものかを指定します。
algでサポートされているアルゴリズムについては以下です。
https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-jwt-authentication/
JWTペイロードの作成
次はJWTペイロードを作成します。以下のようなペイロードを用意し、先ほどと同じようにbase64でエンコードし、その値をメモしておきます。
JWTペイロード
{
"name":"Quotation System",
"sub":"quotes",
"iss":"My JWT Auth"
}
base64でエンコード
# echo -n {"name":"Quotation System","sub":"quotes","iss":"My JWT Auth"} | base64 | tr '+/' '-_' | tr -d '='
bmFtZTpRdW90YXRpb24gU3lzdGVtIHN1YjpxdW90ZXMgaXNzOk15IEpXVCBBdXRo
JWTペイロードの各フィールドの内容は、基本的にはユニークのものでもいいようです。
ヘッダーとペイロードを結合
これまで作成したJWTヘッダーとJWTペイロードの値を「.」で結合します。
以下のコマンドは、bashなどのコマンドプロンプト環境で実施しています。
HEADER_PAYLOAD=<JWTヘッダー>.<JWTペイロード>
例)
HEADER_PAYLOAD=dHlwOkpXVCBhbGc6SFMyNTYga2lkOjAwMDE.bmFtZTpRdW90YXRpb24gU3lzdGVtIHN1YjpxdW90ZXMgaXNzOk15IEpXVCBBdXRo
キーで署名してエンコード
先の手順でつくったものをさらにキーで署名し、base64でエンコードします。署名する際のアルゴリズムは、JWTトークンで指定したHMAC SHA256を使います。
echo -n $HEADER_PAYLOAD | openssl dgst -binary -sha256 -hmac nginxtedjwt | base64 | tr '+/' '-_' | tr -d '='
nM39cZqCadgWGW8U4_nnNNpDIvtX59Ct8X8-YfiV54Y
そして、エンコードされた署名をヘッダーとペイロードに追加し、ファイルに書き出します。
echo $HEADER_PAYLOAD.nM39cZqCadgWGW8U4_nnNNpDIvtX59Ct8X8-YfiV54Y > quotes.jwt
これで、JWTの完成です。
nginxの設定ファイルにJWTの設定を記載する
以下のように、nginxのconfigrationファイルのlocationブロックに「auth_jwt “API”;」 と「auth_jwt_key_file;」ディレクティブを追記します。「auth_jwt_key_file」のパラメーターは、Json Web Token キーで作成したファイルのパスを指定します。
location /jwt {
auth_jwt "API";
auth_jwt_key_file /etc/nginx/conf.d/jwt/api_secret.jwk;
proxy_pass http://jwt-pool;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
動作確認
リクエストを投げて、JWT認証ができているか確認します。
次のようなヘッダーを追加してリクエストを送信します。Bearer 以降の値は作成したquotes.jwtの値になります。
例)
curl -H Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJI.............." https://xxxxxxxx
curlコマンドでリクエスト送信する場合は長いので、以下のようにcatで読み込む形でもOKです。
-H “Authorization: Bearer `cat quotes.jwt`”
例)
curl -H "Authorization: Bearer `cat /etc/nginx/conf.d/jwt/quotes.jwt`" https://xxxxxxxxxx
もしステータスコード401 Unauthorizedなどが返ってきた場合は、nginx -t やnginx -s reloadなどを実行して、設定に問題ないか確認してみてください。
補足
nginx.confを以下のように書くことで、クエリ形式で投げる形でJWT認証も可能です。
「auth_jwt “API”;」を「auth_jwt “API” token=$arg_apijwt;」のように記載
location /jwt {
# auth_jwt "API";
auth_jwt "API" token=$arg_apijwt;
auth_jwt_key_file /etc/nginx/conf.d/jwt/api_secret.jwk;
proxy_pass http://jwt-pool;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
この時のリクエストは次のようになります。ヘッダーはなく、クエリ形式です。
curl https://www.nginx.dev.tedlab.net/jwt/index.html?apijwt=`cat /home/ec2-user/environment/jwt/quotes.jwt`
なお、本内容は以下サイトにも詳細に書いてありますのでこちらも参考にしてみてください。
https://www.nginx.com/blog/authenticating-api-clients-jwt-nginx-plus/
今回の私のブログはここまでとなります。
<< grasys×F5×TED ブログ一覧 >>
4月27日 NGINX Plusの検証をした!(grasys様)
6月 2日 grasys×F5×TEDが徹底解析! NGINX Plusの仕組みがよくわかる!(TED)
6月9日 NGINX Plusのインストール方法とLBの基本設定(grasys様)
6月16日 NGINX PlusでJWT認証をやってみた!(TED)
6月22日 NGINX PlusのLB機能:アクティブヘルスチェック&セッション維持 (grasys様)
6月30日 NGINX Controllerをインストールしてみた!(TED)
7月7日 NGINX PlusのLB機能:DNSディスカバリー (grasys様)
7月14日 NGINX Plus Ingress Controller を触ってみた (TED)
7月25日 NGINX WAFを試してみよう! (TED)
【 株式会社grasys:https://www.grasys.io/ 】
大規模・高負荷・高集積・高密度なシステムを多く扱っているITインフラの会社です。Google Cloud、Amazon Web Services (AWS)、Microsoft Azureの最先端技術を活用してクラウドインフラやデータ分析基盤など、ITシステムの重要な基盤を設計・構築し、改善を続けながら運用しています。