Keycloakのインストールと構築例

2020年6月25日(木)
田畑 義之
連載2回目となる今回は、前回紹介したシステムの構築方法をご紹介します。

第二回は、第一回でご紹介した構成(図1)の構築方法をご紹介します。

図1:構成

図1:構成

認可サーバ(Keycloak)の構築

まずは認可サーバを構築します。本連載では、Keycloak 9.0.3を使用します。インストール先ホストのOSはCentOS 7.8とします。

Keycloakのインストール

Keycloakをインストールします。Keycloakのインストールは、zipファイルをダウンロードして解凍するのみと、非常に簡単です。

まずは事前準備として、Java Development Kitをインストールします。本連載では、OpenJDK 8を使用します。

リスト1:OpenJDK 8のインストール

$ sudo yum install java

次に以下のサイトから、KeycloakのStandalone server distributionをダウンロードします。

https://www.keycloak.org/archive/downloads-9.0.3.html

ダウンロードしたzipファイルを解凍します。

リスト2:Keycloackのzipファイルを解凍

$ unzip keycloak-9.0.3.zip

これでインストールは完了です。

Keycloakの起動

インストールしたKeycloakを起動します。まず、起動する前に、Keycloakの管理者アカウントを作成します。ここではユーザ名を「admin」とします。

リスト3:管理者アカウントの作成

$ cd keycloak-9.0.3
$ ./bin/add-user-keycloak.sh -u admin

次にKeycloakを起動します。localhost以外からも、管理コンソールにアクセスできるようにするために、「-b」オプションを指定します。

リスト4:Keycloakの起動

$ ./bin/standalone.sh -b 0.0.0.0

以下のようなログが出力されれば、起動成功です。Keycloakのログは、keycloak-9.0.3/standalone/log/server.logで確認できます。

リスト5:Keycloak起動の成功をログで確認

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)。

図2:Welcome画面

図2:Welcome画面

各種リソースの準備

最低限必要なリソースを準備します。最低限必要なリソースとしては、以下があります。

  • レルム: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)。

図3:ログイン画面

図3:ログイン画面

作成したKeycloakの管理者アカウントのユーザ名とパスワードを入力し、[Log In]ボタンをクリックします。MasterレルムのRealm SettingsタブのGeneralタブが表示されれば、ログイン成功です(図4)。

図4:Realm SettingsタブのGeneralタブ

図4:Realm SettingsタブのGeneralタブ

まずはレルムを作成します。左ペインの上部[Master]にカーソルを合わせると表示される[Add realm]ボタンをクリックすると、Add realm画面が表示されますので(図5)、レルム名(ここでは「sample_service」とします)を入力し、[Create]ボタンをクリックします。

図5:Add realm画面

図5:Add realm画面

次に外部アプリ用のクライアントを作成します。Clientsタブの[Create]ボタンをクリックすると、Add Client画面が表示されますので(図6)、クライアントID(ここでは「sample_application」とします)を入力し、[Save]ボタンをクリックします。クライアントIDは、RFC6749で定義されているClient Identifierと同義です。

図6:Add Client画面

図6:Add Client画面

外部アプリ用のクライアントの設定を行っていきます。ここでは簡単のため、外部アプリへのトークンの発行方法は、RFC6749で定義されているOAuth 2.0の認可フローのうち、Resource Owner Password Credentials Grant※2を使うこととします。Settingsタブにて、以下の通り設定します(表1、図7)。

表1:外部アプリ用のクライアントの設定項目

設定項目名説明設定値
Access TypeClient Typesと類義。「confidential」を設定したクライアントには、トークン発行時にクライアントシークレットなどの秘密情報を用いたクライアント認証が要求されるconfidential
Standard Flow EnabledAuthorization Code Grantの有効性。KeycloakではStandard Flowと呼ぶOFF
Direct Access Grants EnabledResource 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のセキュリティベストプラクティスに則ったシステム堅牢化方法は第三回以降で説明します。

図7:ClientsタブのSettingsタブ

図7:ClientsタブのSettingsタブ

次にAPIゲートウェイ用のクライアントを作成します。ここではクライアントIDを「sample_api_gateway」とします。Settingsタブにて、以下の通り設定します(表2、図8)。

表2:APIゲートウェイ用のクライアントの設定項目

設定項目名説明設定値
Access TypeClient Typesと類義。「bearer-only」を設定したクライアントは、トークン発行はできないが、トークンイントロスペクションはできるbearer-only
図8:ClientsタブのSettingsタブ

図8:ClientsタブのSettingsタブ

次にユーザを作成します。Usersタブの[Add user]ボタンをクリックして表示されるAdd user画面(図9)で、ユーザ名(ここでは「sample_user」とします)を入力し、[Save]ボタンをクリックします。

図9:Add user画面

図9:Add user画面

ユーザのパスワードの設定を行っていきます。パスワードの設定は、Credentialsタブにて設定します(図10)。ここでは設定したパスワードを永続的なパスワードにするために、TemporaryをOFFにしています※3

※3:TemporaryをONにすると、トークン発行が"Invalid user credentials"で失敗します。

図10:UsersタブのCredentialsタブ

図10:UsersタブのCredentialsタブ

以上でリソースの準備は完了です。

トークンの発行 - Keycloakの動作確認

Keycloakを用いて、トークンを発行してみましょう。RFC6749に則って、Keycloakのトークンエンドポイントをコールします。Resource Owner Password Credentials Grantですので、grant_typeに「password」を指定します。また、client_secretに指定するクライアントシークレット(client secret)は、ClientsタブのCredentialsタブより確認します。

リスト6:トークンを発行してみる

$ 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種類のトークンが発行されます。

リスト7:トークン発行に成功した際のレスポンスの例

{
  "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が、「.」で結合しています。

リスト8:アクセストークンの形式

base64url(Header).base64url(Payload).base64url(Signature)

試しに、Payloadの部分をbase64urlデコードしてみます。デフォルトでは以下のような情報が入っています。

リスト9: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)を作成します。

リスト10:NGINX用のyumリポジトリの作成

[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をインストールします。

リスト11:NGINXのインストール

$ sudo yum install nginx

バージョンを確認します。

リスト12:インストールしたNGINXのバージョン確認

$ nginx -v
nginx version: nginx/1.18.0

これでインストールは完了です。

NGINXの起動

インストールしたNGINXを起動します。

リスト13:NGINXの起動

$ sudo systemctl start nginx

http://<hostname>にアクセスすると、NGINXのWelcome画面が表示されます(図11)。

図11:NGINXのWelcome画面

図11:NGINXのWelcome画面

リバースプロキシの設定

NGINXにリバースプロキシの設定をし、NGINX経由でAPIをコールできるようにします。まずは構成定義ファイル(/etc/nginx/conf.d/sample.conf)を作成します。ここでは8008ポートを使うこととします。

リスト14:リバースプロキシの設定

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>;
    }
}

構成定義ファイルをリロードします。

リスト15:構成定義ファイルのリロード

$ sudo nginx -s reload

APIのコール - リバースプロキシの動作確認

NGINX経由でAPIをコールしてみましょう。APIサーバには、「Hello!」と返すメソッド「/echo」が実装されているものとします。

リスト16:リバースプロキシの動作確認

$ curl http://<APIゲートウェイhostname>:8008/echo
Hello!

NGINX経由でのAPIコールに成功しました。

トークンイントロスペクションの設定

NGINXにトークンイントロスペクションの設定をし、APIコール時にアクセストークンの有効性をチェックできるようにします(図12)。

図12:トークンイントロスペクション

図12:トークンイントロスペクション

まずはNGINX JavaScript Moduleをインストールします。

リスト17:JavaScript Moduleのインストール

$ sudo yum install nginx-module-njs

nginx.conf(/etc/nginx/nginx.conf)のトップレベルのディレクティブに、NGINX JavaScript Moduleをロードする設定を書きます。

リスト18:JavaScript Moduleの設定を追加

load_module modules/ngx_http_js_module.so;
load_module modules/ngx_stream_js_module.so;

作成した構成定義ファイル(/etc/nginx/conf.d/sample.conf)に、トークンイントロスペクションの設定を書きます。

リスト19:トークンイントロスペクションの設定を追加

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)に書きます。

リスト20:ロジックの記述を追加

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);
            }
        }
    );
}

構成定義ファイルをリロードします。

リスト21:構成定義ファイルのリロード

$ sudo nginx -s reload

APIのコール - トークンイントロスペクションの動作確認

NGINX経由でAPIをコールし、トークンイントロスペクションの動作を確認してみましょう。まずはアクセストークンを付与せずにAPIをコールしてみます。アクセストークンを付与せずにAPIをコールすると、403 Forbiddenが返ってきます。

リスト22:アクセストークンなしだと「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が返ってきます。

リスト23:有効期限切れのアクセストークンでも「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をコールしてみます。

リスト24:APIコールに成功

$ curl http://<APIゲートウェイhostname>:8008/echo -H "Authorization: Bearer <access token>"
Hello!

APIコールに成功しました。

次回は、OAuth 2.0のセキュリティベストプラクティスに則って、今回構築したシステムをより堅牢にしていきましょう。

株式会社 日立製作所
OSSソリューションセンタにて、API管理や認証周りのOSSの開発/サポート/普及活動に従事。3scaleおよびkeycloakコミュニティのコントリビュータであり、多数のコードをコミットしている。

連載バックナンバー

運用・管理技術解説
第7回

コンテナ上のマイクロサービスの認証強化 ~StrimziとKeycloak~

2021/2/16
前回に引き続き、マイクロサービスの認証強化を実現する最先端の機能を紹介します。
運用・管理技術解説
第6回

コンテナ上のマイクロサービスの認証強化 ~QuarkusとKeycloak~

2021/1/19
連載6回目となる今回は、マイクロサービスの認証強化を実現する最先端の機能を紹介します。
セキュリティ技術解説
第5回

コンテナ上のマイクロサービスの認証強化 ~IstioとKeycloak~

2020/12/15
連載5回目となる今回は、Istioを用いたマイクロサービスのシステムをKeycloakを用いて認証強化する手順を紹介します。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています