Keycloakのインストールと構築例
第二回は、第一回でご紹介した構成(図1)の構築方法をご紹介します。
認可サーバ(Keycloak)の構築
まずは認可サーバを構築します。本連載では、Keycloak 9.0.3を使用します。インストール先ホストのOSはCentOS 7.8とします。
Keycloakのインストール
Keycloakをインストールします。Keycloakのインストールは、zipファイルをダウンロードして解凍するのみと、非常に簡単です。
まずは事前準備として、Java Development Kitをインストールします。本連載では、OpenJDK 8を使用します。
$ sudo yum install java
次に以下のサイトから、KeycloakのStandalone server distributionをダウンロードします。
https://www.keycloak.org/archive/downloads-9.0.3.html
ダウンロードしたzipファイルを解凍します。
$ unzip keycloak-9.0.3.zip
これでインストールは完了です。
Keycloakの起動
インストールしたKeycloakを起動します。まず、起動する前に、Keycloakの管理者アカウントを作成します。ここではユーザ名を「admin」とします。
$ cd keycloak-9.0.3 $ ./bin/add-user-keycloak.sh -u admin
次にKeycloakを起動します。localhost以外からも、管理コンソールにアクセスできるようにするために、「-b」オプションを指定します。
$ ./bin/standalone.sh -b 0.0.0.0
以下のようなログが出力されれば、起動成功です。Keycloakのログは、keycloak-9.0.3/standalone/log/server.logで確認できます。
13:54:22,252 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: Keycloak 9.0.3 (WildFly Core 10.0.3.Final) started in 14765ms - Started 590 of 885 services (601 services are lazy, passive or on-demand)
http://<hostname>:8080/auth/にアクセスすると、管理コンソールのWelcome画面が表示されます(図2)。
各種リソースの準備
最低限必要なリソースを準備します。最低限必要なリソースとしては、以下があります。
- レルム:Keycloak独自の名前空間。クライアントやユーザの入れ物として、最初に作成する必要がある。テナントやサービスの単位で作成することが多い
- クライアント:OAuth 2.0(RFC6749)の登場人物における、Resource ServerおよびClientをクライアントとして設定する。識別子としてクライアントIDを持つ。ここでは以下の2つのクライアントを設定する
- 外部アプリ用のクライアント:OAuth 2.0のClientに相当する。Keycloakに対してトークンを要求する
- APIゲートウェイ用のクライアント:OAuth 2.0のResource Serverに相当する。Keycloakに対してトークンイントロスペクションを要求する。その時のクライアント認証のためにResource ServerもKeycloakのクライアントとして設定する
- ユーザ:外部アプリを使うエンドユーザ。APIサーバに自らのリソース※1を持つ
※1:例えば、銀行が提供するAPIサーバであれば「口座残高」、SNSが提供するAPIサーバであれば、「投稿履歴」など。
管理コンソールで上記リソースを作っていきます。Welcome画面(図2)にて、[Administration Console]をクリックすると、管理コンソールのログイン画面が表示されます(図3)。
作成したKeycloakの管理者アカウントのユーザ名とパスワードを入力し、[Log In]ボタンをクリックします。MasterレルムのRealm SettingsタブのGeneralタブが表示されれば、ログイン成功です(図4)。
まずはレルムを作成します。左ペインの上部[Master]にカーソルを合わせると表示される[Add realm]ボタンをクリックすると、Add realm画面が表示されますので(図5)、レルム名(ここでは「sample_service」とします)を入力し、[Create]ボタンをクリックします。
次に外部アプリ用のクライアントを作成します。Clientsタブの[Create]ボタンをクリックすると、Add Client画面が表示されますので(図6)、クライアントID(ここでは「sample_application」とします)を入力し、[Save]ボタンをクリックします。クライアントIDは、RFC6749で定義されているClient Identifierと同義です。
外部アプリ用のクライアントの設定を行っていきます。ここでは簡単のため、外部アプリへのトークンの発行方法は、RFC6749で定義されているOAuth 2.0の認可フローのうち、Resource Owner Password Credentials Grant※2を使うこととします。Settingsタブにて、以下の通り設定します(表1、図7)。
設定項目名 | 説明 | 設定値 |
---|---|---|
Access Type | Client Typesと類義。「confidential」を設定したクライアントには、トークン発行時にクライアントシークレットなどの秘密情報を用いたクライアント認証が要求される | confidential |
Standard Flow Enabled | Authorization Code Grantの有効性。KeycloakではStandard Flowと呼ぶ | OFF |
Direct Access Grants Enabled | Resource Owner Password Credentials Grantの有効性。KeycloakではDirect Access Grantと呼ぶ | ON |
※2:Resource Owner Password Credentials Grantは、OAuth 2.0のセキュリティベストプラクティス(https://tools.ietf.org/html/draft-ietf-oauth-security-topics-15)では推奨されていません。OAuth 2.0のセキュリティベストプラクティスに則ったシステム堅牢化方法は第三回以降で説明します。
次にAPIゲートウェイ用のクライアントを作成します。ここではクライアントIDを「sample_api_gateway」とします。Settingsタブにて、以下の通り設定します(表2、図8)。
設定項目名 | 説明 | 設定値 |
---|---|---|
Access Type | Client Typesと類義。「bearer-only」を設定したクライアントは、トークン発行はできないが、トークンイントロスペクションはできる | bearer-only |
次にユーザを作成します。Usersタブの[Add user]ボタンをクリックして表示されるAdd user画面(図9)で、ユーザ名(ここでは「sample_user」とします)を入力し、[Save]ボタンをクリックします。
ユーザのパスワードの設定を行っていきます。パスワードの設定は、Credentialsタブにて設定します(図10)。ここでは設定したパスワードを永続的なパスワードにするために、TemporaryをOFFにしています※3。
※3:TemporaryをONにすると、トークン発行が"Invalid user credentials"で失敗します。
以上でリソースの準備は完了です。
トークンの発行 - Keycloakの動作確認
Keycloakを用いて、トークンを発行してみましょう。RFC6749に則って、Keycloakのトークンエンドポイントをコールします。Resource Owner Password Credentials Grantですので、grant_typeに「password」を指定します。また、client_secretに指定するクライアントシークレット(client secret)は、ClientsタブのCredentialsタブより確認します。
$ curl http://<hostname>:8080/auth/realms/sample_service/protocol/openid-connect/token -d "grant_type=password&client_id=sample_application&client_secret=<client secret>&username=sample_user&password=<password>&scope=openid"
以下のようなレスポンスが返ってくれば、トークン発行は成功です。アクセストークン、リフレッシュトークン、IDトークンの3種類のトークンが発行されます。
{ "access_token": "<access token>", "expires_in": 300, "refresh_expires_in": 1800, "refresh_token": "<refresh token>", "token_type": "bearer", "id_token": "<id token>", "not-before-policy": 0, "session_state": "75760e1a-853a-4e9c-94b7-7fe0cab77b6e", "scope": "openid profile email" }
アクセストークンの中身を見てみます。Keycloakが発行するアクセストークンは、JWT(JSON Web Token)の形式をとっており、base64urlエンコードされたHeader、Payload、Signatureが、「.」で結合しています。
base64url(Header).base64url(Payload).base64url(Signature)
試しに、Payloadの部分をbase64urlデコードしてみます。デフォルトでは以下のような情報が入っています。
{ "exp": 1589767810, "iat": 1589767510, "jti": "c5d29c74-d37f-4d9f-8ebc-bd56495900f7", "iss": " http://<hostname>:8080/auth/realms/sample_service", "aud": "account", "sub": "f8d84f8c-7237-414b-8c24-42deb0237eea", "typ": "Bearer", "azp": "sample_application", "session_state": "e16a38f4-5205-4ed7-80cf-f1c1b6bb7be4", "acr": "1", "realm_access": { "roles": [ "offline_access", "uma_authorization" ] }, "resource_access": { "account": { "roles": [ "manage-account", "manage-account-links", "view-profile" ] } }, "scope": "openid profile email", "email_verified": false, "preferred_username": "sample_user" }
APIゲートウェイ(NGINX)の構築
次にAPIゲートウェイを構築します。本連載では、NGINX 1.18.0を使用します。インストール先ホストのOSはCentOS 7.8とします。
NGINXのインストール
NGINXをインストールします。まずはyumリポジトリ(/etc/yum.repos.d/nginx.repo)を作成します。
[nginx-stable] name=nginx stable repo baseurl=http://nginx.org/packages/centos/$releasever/$basearch/ gpgcheck=1 enabled=1 gpgkey=https://nginx.org/keys/nginx_signing.key module_hotfixes=true
yumコマンドでNGINXをインストールします。
$ sudo yum install nginx
バージョンを確認します。
$ nginx -v nginx version: nginx/1.18.0
これでインストールは完了です。
NGINXの起動
インストールしたNGINXを起動します。
$ sudo systemctl start nginx
http://<hostname>にアクセスすると、NGINXのWelcome画面が表示されます(図11)。
リバースプロキシの設定
NGINXにリバースプロキシの設定をし、NGINX経由でAPIをコールできるようにします。まずは構成定義ファイル(/etc/nginx/conf.d/sample.conf)を作成します。ここでは8008ポートを使うこととします。
server { listen 8008; ignore_invalid_headers off; location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; proxy_set_header Connection close; proxy_pass http://<APIサーバhostname>:<port>; } }
構成定義ファイルをリロードします。
$ sudo nginx -s reload
APIのコール - リバースプロキシの動作確認
NGINX経由でAPIをコールしてみましょう。APIサーバには、「Hello!」と返すメソッド「/echo」が実装されているものとします。
$ curl http://<APIゲートウェイhostname>:8008/echo Hello!
NGINX経由でのAPIコールに成功しました。
トークンイントロスペクションの設定
NGINXにトークンイントロスペクションの設定をし、APIコール時にアクセストークンの有効性をチェックできるようにします(図12)。
まずはNGINX JavaScript Moduleをインストールします。
$ sudo yum install nginx-module-njs
nginx.conf(/etc/nginx/nginx.conf)のトップレベルのディレクティブに、NGINX JavaScript Moduleをロードする設定を書きます。
load_module modules/ngx_http_js_module.so; load_module modules/ngx_stream_js_module.so;
作成した構成定義ファイル(/etc/nginx/conf.d/sample.conf)に、トークンイントロスペクションの設定を書きます。
js_include oauth2.js; map $http_authorization $access_token { ~^Bearer\s+(\S+)$ $1; } server { … location / { auth_request /_oauth2_token_introspection; … } location = /_oauth2_token_introspection { internal; js_content introspectAccessToken; } location = /_oauth2_send_request { internal; proxy_method POST; proxy_set_header Content-Type "application/x-www-form-urlencoded"; proxy_set_body "token=$access_token&token_hint=access_token&client_id=sample_api_gateway&client_secret=<client secret>"; proxy_pass http://<認可サーバhostname>:8080/auth/realms/sample_service/protocol/openid-connect/token/introspect; } }
トークンイントロスペクションのロジックを、oauth2.js(/etc/nginx/oauth2.js)に書きます。
function introspectAccessToken(r) { r.subrequest("/_oauth2_send_request", function(reply) { if (reply.status == 200) { var response = JSON.parse(reply.responseBody); if (response.active == true) { r.return(204); } else { r.return(403); } } else { r.return(401); } } ); }
構成定義ファイルをリロードします。
$ sudo nginx -s reload
APIのコール - トークンイントロスペクションの動作確認
NGINX経由でAPIをコールし、トークンイントロスペクションの動作を確認してみましょう。まずはアクセストークンを付与せずにAPIをコールしてみます。アクセストークンを付与せずにAPIをコールすると、403 Forbiddenが返ってきます。
$ curl http://<APIゲートウェイhostname>:8008/echo <html> <head><title>403 Forbidden</title></head> <body> <center><h1>403 Forbidden</h1></center> <hr><center>nginx/1.18.0</center> </body> </html>
次に、先ほど発行したアクセストークンを付与してAPIをコールしてみます。しかし先ほど発行したアクセストークンはすでに有効期限(デフォルトでは5分)が切れていたため、やはり403 Forbiddenが返ってきます。
$ curl http://<APIゲートウェイhostname>:8008/echo -H "Authorization: Bearer <access token>" <html> <head><title>403 Forbidden</title></head> <body> <center><h1>403 Forbidden</h1></center> <hr><center>nginx/1.18.0</center> </body> </html>
新しいアクセストークンを発行し、そのアクセストークンを付与してAPIをコールしてみます。
$ curl http://<APIゲートウェイhostname>:8008/echo -H "Authorization: Bearer <access token>" Hello!
APIコールに成功しました。
次回は、OAuth 2.0のセキュリティベストプラクティスに則って、今回構築したシステムをより堅牢にしていきましょう。
連載バックナンバー
Think ITメルマガ会員登録受付中
全文検索エンジンによるおすすめ記事
- Keycloakを用いたハードニングの実装方法
- 3scaleのAPIゲートウェイの機能を拡張してみよう!
- APIセキュリティのハードニング
- コンテナ上のマイクロサービスの認証強化 ~StrimziとKeycloak~
- FAPI 1.0に準拠したクライアントアプリケーションと リソースサーバの作り方
- 3scaleの基本的な使い方
- コンテナ上のマイクロサービスの認証強化 ~IstioとKeycloak~
- FAPIとKeycloakの概要
- KeycloakによるAPIセキュリティの基本
- Oracle Cloud Hangout Cafe Season4 #4「マイクロサービスの認証・認可とJWT」(2021年7月7日開催)