KeyCloak アクセスログにユーザー名を記録する

KeyCloak アクセスログにユーザー名を記録する

KeyCLoak 15.0(WildFly 23.0)では、ユーザーがログインしたときにユーザー名(またはユーザーの任意のID)もアクセスログに含めるように設定しようとしています。ではkeycloak/standalone/configuration/standalone.xmlXML:/server/profile/subsystem[@xmlns="urn:jboss:domain:undertow:12.0"]/server/host/access-log/@pattern pattern="%h %l %u %t "%r" %s/%S %b %T %I "%{i,User-Agent}""

ログは設定したファイルに正しく出力されます。ただし、%uまたはの値%{REMOTE_USER}は常に空です (つまり-)。

私が見つけたユーザー ID をログに記録する唯一の方法は、%{c,KEYCLOAK_SESSION}( が含まれていますrealm/user-ID/secret) を使用してセッション クッキー値をログに記録することでした。これは、本番環境で実行するのはお勧めできません。

アクセスログにユーザー名またはユーザーIDを記録する方法について何かアイデアはありますか?

%uKeyCloak にアクティブなユーザー セッションがある場合でもまたは が空になるのはKeyCLoak のバグですか%{REMOTE_USER}? または、KeyCLoak でどのユーザー属性値が入るかを構成することは可能ですかREMOTE_USER?

あるいは次のいずれかを使用するために、ユーザー ID をヘッダーに配置するにはどうすればよいでしょうか?

  • %{i,xxx}受信ヘッダー用
  • %{o,xxx}送信レスポンスヘッダー用
  • %{c,xxx}特定のクッキー
  • %{r,xxx}ここでxxxはServletRequestの属性です
  • %{s,xxx}ここでxxxはHttpSessionの属性です

他にも、これらを試してみました。どれも入力されていませんでした。

%{s,user} 
%{s,userId} 
%{s,client_id} 
%{s,USER_ID} 
%{s,USER} 
%{s,org.keycloak.adapters.spi.KeycloakAccount} 
%{s,KeycloakAccount} 
%{s,org.keycloak.adapters.tomcat.CatalinaSessionTokenStore.SerializableKeycloakAccount} 
%{s,SerializableKeycloakAccount} 
%{s,org.keycloak.adapters.saml.SamlSession} 
%{s,SamlSession} 
%{s,org.keycloak.adapters.undertow.KeycloakUndertowAccount} 
%{s,KeycloakUndertowAccount} 
%{s,org.keycloak.KeycloakSecurityContext} 
%{s,KeycloakSecurityContext} 
%{s,io.undertow.servlet.util.SavedRequest} 
%{s,SavedRequest}

%{r,tokenInfo} 
%{r,KeycloakSecurityContext} 
%{r,ElytronHttpFacade} 
%{r,AdapterDeploymentContext} 
%{r,TOKEN_STORE_NOTE}

答え1

同様の問題 (クライアントからクライアント ID をログに記録するように依頼された) に遭遇し、解決策を探すことになりました。ソース コードとアクセス ログがどのように取り込まれるかを見ると、ログが作成される場所と実際の作業が行われる場所の間にはかなり大きなギャップがあることがわかります。

Keycloak を見てみると、これは Undertow を使用して http サーバー機能をホストする Wildfly に基づいています。アクセス ログ エントリはリクエストが処理されると出力されますが、いくつかのギャップと抽象化が複雑化を引き起こします。

ソフトウェアの観点から見ると、undertow ハンドラー、サーブレット、resteasy サーブレット、keycloak アプリケーション、および特定のリソースがあります。Keycloak ユーザーまたは管理コンソールを使用する場合、ほとんどの場所では、Web ブラウザーによってレンダリングされる「シン」クライアントになります。そして、このブラウザーは REST リソースを呼び出します。

ユーザー関連の情報を頻繁に取得したい場合、その情報はセッションでは見つからないでしょう。これは、Kecloak によって行われる作業のほとんどがユーザーに代わってトークンを発行することだからです。正式には、リクエストを送信するクライアントはユーザーに代わって動作します。つまり、着信リクエストごとに利用できる明示的な情報ではありません。さらに、残りのリソースのほとんどは定義上ステートレスです。つまり、ユーザーと何らかの形で動作しますが、セッションにはあまりデータを取り込みません。ユーザー情報へのアクセスを期待できるのは、ユーザーが実際にログインし、ユーザー アカウント コンソール内で何かを実行したときだけです。それ以外では、ほとんどの場合、トークンを発行する Keycloak リソースはクライアントまたはクライアント関連のセッションを処理するため、無駄な戦いになる可能性があります。

要点を言うと、アクセス ログ形式の解析を行う場所を見つけました。これは、ExchangeAttributeログに独自のマクロを導入できる Undertow のアイデアに基づいています。このマクロは、必要な情報を求めてメモリ構造をトラバースするために使用できます。私の場合、作業を実行したのは client_id でした。そのために、 を実装することになりましたFormAttribute。まだ接続方法を見つける必要がありますが、ユニット テストの観点からは、すでに「クリック」しています。基本的なコードがいかにシンプルであるかを確認してください。

package org.code_house.wildfly.stuff.undertow.attributes;
// remember to create META-INF/services/io.undertow.attribute.ExchangeAttributeBuilder
// with line containing class name, ie.
// org.code_house.wildfly.stuff.undertow.attributes.FormAttribute$Builder

/**
 * Expose form parameters within exchange attributes which can be logged in access log.
 * Use %{F,*} to dump all params or %{F,client_id} to render selected from field.
 *
 * @author Łukasz Dywicki @ code-house.org
 **/
public class FormAttribute implements ExchangeAttribute {

  private final String paramName;

  public FormAttribute(String paramName) {
    this.paramName = paramName;
  }

  @Override
  public String readAttribute(HttpServerExchange exchange) {
    FormData formData = exchange.getAttachment(FormDataParser.FORM_DATA);
    if ("*".equals(paramName)) {
      return "" + formData;
    }
    return formData == null ? "" : "" + formData.get(paramName);
  }

  @Override
  public void writeAttribute(HttpServerExchange exchange, String newValue) throws ReadOnlyAttributeException {
    throw new ReadOnlyAttributeException("Form", newValue);
  }

  public static final class Builder implements ExchangeAttributeBuilder {
    @Override
    public String name() {
      return "form";
    }

    @Override
    public ExchangeAttribute build(final String token) {
      if (token.startsWith("%{F,") && token.endsWith("}")) {
        final String paramName = token.substring(4, token.length() - 1);
        return new FormAttribute(paramName);
      }
      return null;
    }

    @Override
    public int priority() {
      return 0;
    }
  }
}

要点 - フォーム属性を使用してログイン フォームの「ユーザー名」フィールドに入力された値をログに記録し、「誰」を取得すると、それをブラウザーによって保持されるセッション クッキーと組み合わせることができます。上記の 2 つのエースを基本的に組み合わせることで、必要な結果を得ることができます。上記のブループリントを使用すると、独自のものを実装し、トークンやその他のものを追跡して、アプリケーションを構築できます。

ログ形式に追加の属性を適切に Undertow に挿入するための接着ロジックが見つかったら、回答を更新する可能性があります。編集: これまでに見つけた追加の属性を挿入する方法は、次のディレクトリに属性をコピーして$JBOSS_HOME/modules/system/layers/base/org/wildfly/extension/undertow/main/更新する方法だけですmodule.xml

<module name="org.wildfly.extension.undertow" xmlns="urn:jboss:module:1.5">
...
    <resources>
        <resource-root path="wildfly-undertow-20.0.1.Final.jar"/>
        <!-- put here name of jar you made -->
        <resource-root path="undertow-client-request-filter-1.0.0-SNAPSHOT.jar"/>
    </resources>
...
</module>

関連情報