Edited at

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

More than 1 year has passed since last update.


今日やること

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

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

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


  • プラットフォーム


    • Wildfly

    • JBoss EAP

    • JBoss AS

    • JBoss Fuse

    • Jetty

    • Tomcat

    • Javascript(クライアント)



  • プロトコル


    • OpenID Connect

    • SAML 2.0



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

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


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

今回のお試し環境はこのような感じです。

Keycloakサーバーには、demoレルムが作成されており(3日目の記事を参照)、ログイン可能なユーザーが存在していることが前提となります。

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アプリを利用)
・Tomcat 8用クライアント・アダプター(OpenID Connect版) 3.3.0.Final

ユーザー
ロール

user001
user

admin001
user,admin

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

動作確認時の各サーバーへのアクセスポートは以下のとおりです。


Keycloakサーバー側の設定


クライアントの追加・設定


  1. Keycloakの管理コンソールにログインします。

  2. 左メニューバーから、demoレルムを選択します。

  3. 左メニューバーで クライアント をクリックします。クライアント一覧が表示されます。

  4. 右上の 作成 ボタンを押下します。


  5. 以下のようなクライアント設定値を入力し、保存 ボタンを押下します




  6. 引き続き kc-tomcatの設定画面が表示されるので以下のような設定を行い、保存 ボタンを押下します。



  7. インストール タブを押下します。



  8. フォーマットオプションからKeycloak OIDC JSONを選択し、ダウンロードのテキストボックスに表示されているjsonを控えます。




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

クライアント・アダプターの設定では、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を導入します。

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/ に解凍します。

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連携やアプリケーション保護に必要な処理が実施されるようになります。

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


/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-requirednoneに変更します。


/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を指定します。1

ssl-required

HTTPSでのアクセスを強制するかどうか指定します。"all", "external", "none" のいずれかを指定します。デフォルトは、"external"なので、外部からはHTTPのアクセスは許可されません。

resource

Keycloak側のクライアント設定のクライアントID(client-id)を指定します。

credentials

Keycloak側のアクセスタイプで"confidential"を指定した場合は必須です。

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

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


WEB-INF/web.xmlの編集

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


/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ロールの権限が必要


/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起動します。

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/ が表示されます。



  5. adminロールが必要な http://kc-tomcat.example.com:8080/examples/websocket/echo.xhtml にアクセスします。


  6. 403(Forbidden)となり、ページは表示されません。




userおよびadminロールが必要なURL


  1. userおよびadminロールが必要な http://kc-tomcat.example.com:8080/examples/websocket/echo.xhtml にアクセスします。

  2. Keycloakに未ログインであるため、ログイン画面にリダイレクトが発生します。

  3. user / admin の双方のロールを持つユーザーのID/PWでログインします。


  4. http://kc-tomcat.example.com:8080/examples/websocket/echo.xhtml が表示されます。



  5. userロールが必要なhttp://kc-tomcat.example.com:8080/examples/ にアクセスします。



  6. http://kc-tomcat.example.com:8080/examples/ が表示されます。




ユーザー情報の取得確認

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

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


/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 が表示されます。

  5. HTTPセッションからKeycloakのユーザー情報が取得できるのが分かります。



まとめ

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

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

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


参考資料


脚注





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