Keycloakのクライアント・アダプターを試してみる（Tomcat編）

# 今日やること

Keycloakアドベンドカレンダー5日目の今回は、Tomcat 8用のクライアント・アダプター(OpenID Connect版)を試してみます。

クライアント・アダプターとは、KeycloakとID連携(SSO)し、アプリケーション保護を容易にするためのプラットフォーム固有のライブラリ群です。基本的にはアプリケーションが導入されているミドルウェア上にクライアント・アダプターをインストール／設定した上で、ユーザー連携のため多少のコードカスタマイズを実施することになります。

現在サポートされている主なプラットフォームおよびプロトコルは以下のとおりです。

- プラットフォーム
 - Wildfly
 - JBoss EAP
 - JBoss AS
 - JBoss Fuse
 - Jetty
 - Tomcat
 - Javascript(クライアント)
- プロトコル
 - OpenID Connect
 - SAML 2.0

:information_source: 上記以外にも、コミュニティから提供されているクライアント・アダプターが複数あります。プラットフォームとプロトコルの組み合わせによってはサポートされていないものがあるので、詳しくは[参考資料の"Supported Platforms"](#参考資料)を参照ください。

今回はTomcat 8のサンプルアプリ(examples)に対してTomcat 8用のクライアント・アダプター(OpenID Connect版)を導入し、KeycloakとID連携を行う方法を試してみます。

# 動作確認用のサーバー構成

今回のお試し環境はこのような感じです。
Keycloakサーバーには、`demo`レルムが作成されており([3日目の記事を参照](https://qiita.com/tamura__246/items/4290a1035e1adcab733b))、ログイン可能なユーザーが存在していることが前提となります。

|FQDN|OS|JDK|構成|
|:---|:---|:---|:---|
|kc-server.example.com|CentOS 7.4.1708|OpenJDK 1.8.0.151|・Keycloakサーバー 3.3.0.CR2|
|kc-tomcat.example.com|CentOS 7.4.1708|OpenJDK 1.8.0.151|・Tomcat 8.5.23(examplesアプリを利用)<br>・Tomcat 8用クライアント・アダプター(OpenID Connect版) 3.3.0.Final|

|ユーザー|ロール|
|:---|:---|
|user001|user|
|admin001|user,admin|

Tomcatに付属するexapmlesアプリケーションでは、以下のようなアクセス制御をかけることを想定します。

![tomcat_acl01.png](https://qiita-image-store.s3.amazonaws.com/0/209129/5bb63ce9-1711-ce8f-6f2f-eade3cab555a.png)

動作確認時の各サーバーへのアクセスポートは以下のとおりです。
![tomcat_port01.png](https://qiita-image-store.s3.amazonaws.com/0/209129/e1b9b58b-9f82-d1c1-2b2c-6490b2bda7b0.png)


# Keycloakサーバー側の設定

## クライアントの追加・設定
1. Keycloakの管理コンソールにログインします。
1. 左メニューバーから、`demo`レルムを選択します。
1. 左メニューバーで `クライアント` をクリックします。クライアント一覧が表示されます。
2. 右上の `作成` ボタンを押下します。
3. 以下のようなクライアント設定値を入力し、`保存` ボタンを押下します
 - クライアントID: kc-tomcat
 - クライアントプロトコル: openid-connect
 - ルートURL: http://kc-tomcat.example.com:8080
![tomcat01.png](https://qiita-image-store.s3.amazonaws.com/0/209129/c52468ee-4f9e-a868-c1c8-db9658aa2750.png)


4. 引き続き kc-tomcatの設定画面が表示されるので以下のような設定を行い、`保存` ボタンを押下します。
 - アクセスタイプ: confidential
 - 有効なリダイレクトURI(1つ目): http://kc-tomcat.example.com:8080/examples/
 - 有効なリダイレクトURI(2つ目): http://kc-tomcat.example.com:8080/examples/websocket/echo.xhtml
 - 有効なリダイレクトURI(3つ目): http://kc-tomcat.example.com:8080/examples/getUserInfo.jsp
![tomcat02_01_opt.png](https://qiita-image-store.s3.amazonaws.com/0/209129/dc260700-2041-66ac-1eb5-81140180dd16.png)
:information_source: `有効なリダイレクトURI`は、Keycloak上ではワイルドカードによる指定も可能です。ただし、OIDCの仕様上では、redirect_uriと厳密に一致することが必須となっているため、ここではリダイレクト先となりえるURLはすべて指定しています。そのため、ここで指定していないURLへは未ログイン状態から直接遷移できません。
7. `インストール` タブを押下します。
8. `フォーマットオプション`から`Keycloak OIDC JSON`を選択し、`ダウンロード`のテキストボックスに表示されているjsonを控えます。
![tomcat03.png](https://qiita-image-store.s3.amazonaws.com/0/209129/c56393d9-4f49-5d8b-779c-13e3eb241eb2.png)


# クライアント・アダプター側の設定

クライアント・アダプターの設定では、WAR単位で連携設定が必要です。
Webアプリケーション単位で、以下の3つの手順を実施します。

1. $WAR_HOME/META-INF/context.xmlの配置
2. $WAR_HOME/WEB-INF/keycloak.jsonの配置
3. $WAR_HOME/WEB-INF/web.xmlの編集

以下、順を追って説明します。

## Tomcat 8の導入
連携確認用のサンプルアプリケーションとして、Tomcat 8を導入します。

```bash
cd  /tmp
wget http://ftp.tsukuba.wide.ad.jp/software/apache/tomcat/tomcat-8/v8.5.23/bin/apache-tomcat-8.5.23.tar.gz
tar zxvf apache-tomcat-8.5.23.tar.gz -C /usr/local
```

## Tomcat 8用のクライアント・アダプターのダウンロードと展開

Tomcat 8用のクライアント・アダプターをダウンロードして `/usr/local/apache-tomcat-8.5.23/lib/` に解凍します。

```bash
cd  /tmp
wget https://downloads.jboss.org/keycloak/3.3.0.Final/adapters/keycloak-oidc/keycloak-tomcat8-adapter-dist-3.3.0.Final.tar.gz
tar zxvf keycloak-tomcat8-adapter-dist-3.3.0.Final.tar.gz -C /usr/local/apache-tomcat-8.5.23/lib/
```

## META-INF/context.xml の配置

今回はTomcatのexamplesのWebアプリケーションに対して保護をかけるため、
対象のWARディレクトリ配下に`META-INF/context.xml`を作成します。

この設定ではTomcatのValveとして`KeycloakAuthenticatorValve`を設定しています。Valveとは、リクエストをフックするためのTomcat独自のプラグイン機構です。KeycloakとID連携を行うための`KeycloakAuthenticatorValve`を設定することにより、当該アプリケーションへのリクエストが事前にフィルタされ、KeycloakとのID連携やアプリケーション保護に必要な処理が実施されるようになります。

```bash
mkdir -p /usr/local/apache-tomcat-8.5.23/webapps/examples/META-INF/
```

```xml:/usr/local/apache-tomcat-8.5.23/webapps/examples/META-INF/context.xml
<Context path="/examples">
    <Valve className="org.keycloak.adapters.tomcat.KeycloakAuthenticatorValve"/>
</Context>
```

## WEB-INF/keycloak.json の配置

対象のWARディレクトリの`WEB-INF`以下に、クライアント・アダプターの設定用のJSONファイル(keycloak.json)を配置します。
基本的に、Keycloakの管理画面で控えたjsonをそのまま設定するだけですが、今回は`ssl-required`を`none`に変更します。

```json:/usr/local/apache-tomcat-8.5.23/webapps/examples/WEB-INF/keycloak.json
{
  "realm": "demo",
  "auth-server-url": "https://kc-server.example.com:8443/auth",
  "ssl-required": "none",
  "resource": "kc-tomcat",
  "credentials": {
    "secret": "{Keycloakサーバー側のシークレットの値}"
  }
}
```


|パラメータ名 |必須|  説明  |
|:-----------------|:-----------------:|:------------------|
|realm|○|Keycloak側のレルム名を指定します。|
|auth-server-url|○|Keycloak側のベースURLを指定します。[^auth-server-url]|
|ssl-required||HTTPSでのアクセスを強制するかどうか指定します。"all", "external", "none" のいずれかを指定します。デフォルトは、"external"なので、外部からはHTTPのアクセスは許可されません。|
|resource|○|Keycloak側のクライアント設定のクライアントID(client-id)を指定します。|
|credentials|△|Keycloak側のアクセスタイプで"confidential"を指定した場合は必須です。|

:information_source: adapter-configの設定は非常に多くあるため、動作確認に必要な最低限の項目のみ記載しています。その他のパラメータに関しては、[参考資料の"Java Adapter Config"](#参考資料)を参照ください。

:information_source: 今回は、Tomcatに8080ポートでアクセス可能にするため、`ssl-required`は、noneに変更しています。ただし、本来のOIDCではセキュリティ担保のためHTTPSの利用が前提となっています。そのため、実環境では`ssl-required`は`all`にすべきです。


## WEB-INF/web.xmlの編集

examples/WEB-INF/web.xml に元から定義されている `login-config` の設定があるため、こちらは一旦コメントアウトします。

```xml:/usr/local/apache-tomcat-8.5.23/webapps/examples/WEB-INF/web.xml
    ・
    ・
    <!-- Default login configuration uses form-based authentication -->
    <!-- COMMENT OUT
    <login-config>
      <auth-method>FORM</auth-method>
      <realm-name>Example Form-Based Authentication Area</realm-name>
      <form-login-config>
        <form-login-page>/jsp/security/protected/login.jsp</form-login-page>
        <form-error-page>/jsp/security/protected/error.jsp</form-error-page>
      </form-login-config>
    </login-config>
    -->
    ・
    ・
```

クライアント・アダプターによるID連携やアプリケーション保護はJavaEE標準のセキュリティ機能と連携するため、各アプリケーションにあわせてweb.xmlに固有の設定を行う必要があります。

今回のアクセス制御の設定例では、以下のように設定します。

- /examples/* へのアクセスはuserロールの権限が必要
- /examples/websocket/* へのアクセスはadminロールの権限が必要

```xml:/usr/local/apache-tomcat-8.5.23/webapps/examples/WEB-INF/web.xml
    ・
    ・
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>User Area</web-resource-name>
            <url-pattern>/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>user</role-name>
        </auth-constraint>
    </security-constraint>

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Admin Area</web-resource-name>
            <url-pattern>/websocket/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>admin</role-name>
        </auth-constraint>
    </security-constraint>

    <login-config>
        <auth-method>BASIC</auth-method>
        <realm-name>this is ignored currently</realm-name>
    </login-config>

    <security-role>
        <role-name>admin</role-name>
    </security-role>
    <security-role>
        <role-name>user</role-name>
    </security-role>
    ・
    ・
```

## Tomcatの起動

クライアント・アダプターの準備が整ったので、Tomcat起動します。

```bash
cd /usr/local/apache-tomcat-8.5.23/
./bin/startup.sh
```

# 動作確認
## user ロールが必要なURL
1. userロールが必要なhttp://kc-tomcat.example.com:8080/examples/ にアクセスします。
2. Keycloakに未ログインであるため、ログイン画面にリダイレクトが発生します。
3. userロールを持つユーザーのID/PWでログインします。
4. http://kc-tomcat.example.com:8080/examples/ が表示されます。
![tomcat04.png](https://qiita-image-store.s3.amazonaws.com/0/209129/726f8fa3-3098-fa43-4aa2-42c5415c02fc.png)

1. adminロールが必要な http://kc-tomcat.example.com:8080/examples/websocket/echo.xhtml にアクセスします。
2. 403(Forbidden)となり、ページは表示されません。
![tomcat05.png](https://qiita-image-store.s3.amazonaws.com/0/209129/85ec1a76-e32f-b284-4fe0-395d23295c7b.png)


## userおよびadminロールが必要なURL
1. userおよびadminロールが必要な http://kc-tomcat.example.com:8080/examples/websocket/echo.xhtml にアクセスします。
2. Keycloakに未ログインであるため、ログイン画面にリダイレクトが発生します。
3. user / admin の双方のロールを持つユーザーのID/PWでログインします。
1. http://kc-tomcat.example.com:8080/examples/websocket/echo.xhtml が表示されます。
![tomcat06.png](https://qiita-image-store.s3.amazonaws.com/0/209129/136df7b3-54d1-7e6c-408d-25045396e593.png)

1. userロールが必要なhttp://kc-tomcat.example.com:8080/examples/ にアクセスします。
4. http://kc-tomcat.example.com:8080/examples/ が表示されます。
![tomcat07.png](https://qiita-image-store.s3.amazonaws.com/0/209129/fd8a0363-6847-b3a6-b941-682f02aa790e.png)


## ユーザー情報の取得確認
上記の確認では、アクセス制御の確認はできましたがログインしたユーザー情報をどのように取得したらよいのかよく分かりません。クライアント・アダプターを利用している場合は、KeycloakのAPIを利用することで、OIDCで連携されたIDトークンの情報を簡単に取得することができます。以下のサンプル実装では、HTTPセッションからユーザー情報を取得するJSPを新たに配置して、ログイン済みのユーザー情報が取得できるかどうか確認しています。

以下の`getUserInfo.jsp`をWARディレクトリ直下に配置します。

```jsp:/usr/local/apache-tomcat-8.5.23/webapps/examples/getUserInfo.jsp
<%@ page import="org.keycloak.KeycloakSecurityContext" %>
<%@ page import="org.keycloak.representations.IDToken" %>

<%
    // HTTPセッションからKeycloakSecurityContextを取得
    KeycloakSecurityContext ksc = (KeycloakSecurityContext)session.getAttribute(KeycloakSecurityContext.class.getName());
    // KeycloakSecurityContextから、IDTokenを取得
    IDToken idToken = ksc.getIdToken();
%>

PREFERRED_USERNAME : <%=idToken.getPreferredUsername()%><br>
EMAIL: <%=idToken.getEmail() %><br>
GIVEN_NAME: <%=idToken.getGivenName()%><br>
FAMILY_NAME: <%=idToken.getFamilyName()%><br>
```

1. http://kc-tomcat.example.com:8080/examples/getUserInfo.jsp にアクセスします。
2. Keycloakに未ログインであるため、ログイン画面にリダイレクトが発生します。
3. userロールを持つユーザーのID/PWでログインします。
4. http://kc-tomcat.example.com:8080/examples/getUserInfo.jsp が表示されます。
6. HTTPセッションからKeycloakのユーザー情報が取得できるのが分かります。
![tomcat08.png](https://qiita-image-store.s3.amazonaws.com/0/209129/20cb60e3-e644-7ced-377a-e3472140a0a6.png)



# まとめ

今回はKeycloakとTomcat上のアプリケーションをID連携させる方法を確認しました。Tomcatとの連携に関しては、WAR内の設定ファイルを3つ追加／変更をするだけなので、比較的容易に組み込みが可能なクライアント・アダプターかと思います。

ただ、連携が必要なアプリケーションサーバーが多数ある場合や、そのプラットフォームが多種多様である場合は、それぞれのサーバーで導入／設定を実施していく必要があるので、作業が煩雑になる可能性があります。

このような構成の場合は、8日目の記事で私が解説する「Keycloakでリバプロ型構成を組んでみる（mod_auth_openidc編）」を利用したほうがよいかもしれません。


# 参考資料
- [Keycloak Documentation](http://www.keycloak.org/documentation.html)
 - [1.2. Supported Platforms](http://www.keycloak.org/docs/latest/securing_apps/index.html#supported-platforms)
 - [2.1.1. Java Adapter Config](http://www.keycloak.org/docs/latest/securing_apps/index.html#_java_adapter_config)
 - [2.1.5. Tomcat 6, 7 and 8 Adapters](http://www.keycloak.org/docs/latest/securing_apps/index.html#_tomcat_adapter)
- [NRI OpenStandia Keycloak日本語ドキュメント](http://keycloak-documentation.openstandia.jp/master/ja_JP/securing_apps/index.html#_tomcat_adapter)

# 脚注

[^auth-server-url]: Keycloakサーバーとのssl接続で自己署名サーバー証明書を利用する場合にはクライアント・アダプター側のJavaのtruststoreにサーバー証明書の取り込みが必要です。(クライアント・アダプターが、Keycloakサーバーと直接SSL通信するため)