Logging username in KeyCloak access-log
Solution 1:
I come over similar issue (my client asked me to log client id) and ended up looking for solution. From looking at the source code and how access log is populated I can tell you that there is pretty big gap between where log is formed and where actual work is being done.
If you have a look on Keycloak it is based on Wildfly which uses Undertow to host http server functionality. While access log entry is emitted once request was served there are few gaps and abstractions which complicate things.
From software perspective there is undertow handler, then servlet, then resteasy servlet, then keycloak application and specific resources. When you use Keycloak user or admin console then in most of places it is a "thin" client which is rendered by a web browser. And this browser calls rest resource.
If you wish to get user related information quite often it will not be found in the session, cause most of work being done by Kecloak is issuing tokens on behalf of users. Formally client who ships request acts on behalf of user which means that it is not a explicit information available for each incoming request. Additionally, most of rest resources by definition is stateless which means that they do operate somehow with user but do not populate session much. Only one part for which you can count on access for user information is when user actually logs in and does something within user account console. Beside that it might be lost fight since keycloak resources issuing tokens in most of cases will handle client or client related sessions.
To the point - I come to point where I located place which does the access log format parsing. It is based on Undertow ExchangeAttribute
idea which allows to bring your own macro for log. This macro can be utilized to either traverse memory structures in pursue of the necessary information. For me it was a client_id which was doing the work. For that I ended up with implementation of a FormAttribute
. I still need to find a way to wire it in, but from unit test perspective it already "clicked", see how basic code is:
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;
}
}
}
Main point - by using form attribute to log value entered in "username" field of login form to get "who", then you can combine that with session cookie which will be retained by browser. By basic merger of above two aces you can achieve necessary result. Using above blueprint you can implement your own thing and keep track of tokens and other things which will let you build your application.
I may update the answer when I find a glue logic to properly inject additional attribute into log format into undertow.
EDIT: Only one way I found so far to inject extra attributes is by copying them into $JBOSS_HOME/modules/system/layers/base/org/wildfly/extension/undertow/main/
and updating module.xml
within this directory:
<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>