Registro de nombre de usuario en el registro de acceso de KeyCloak

Registro de nombre de usuario en el registro de acceso de KeyCloak

En KeyCLoak 15.0 (es decir, WildFly 23.0), estoy intentando configurar el registro de acceso para que también incluya el nombre de usuario (o cualquier ID del usuario) cuando un usuario inicia sesión. En keycloak/standalone/configuration/standalone.xml, configuré XML:/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}""

El registro se imprime correctamente en el archivo que configuré. Sin embargo, el valor de %uo %{REMOTE_USER}siempre está vacío (es decir -).

La única forma de registrar alguna ID de usuario que encontré fue registrar el valor de la cookie de sesión con %{c,KEYCLOAK_SESSION}(contiene realm/user-ID/secret). Lo cual no es una buena idea para hacer en producción.

¿Alguna idea sobre cómo registrar el nombre de usuario o ID de usuario en el registro de acceso?

¿Es un error de KeyCLoak que %uestá %{REMOTE_USER}vacío incluso cuando hay una sesión de usuario activa en KeyCloak? ¿O es posible en KeyCLoak configurar qué valor de atributo de usuario entra REMOTE_USER?

Alternativamente, ¿cómo poner el ID de usuario en algún encabezado para usar uno de los siguientes?

  • %{i,xxx}para encabezados entrantes
  • %{o,xxx}para encabezados de respuesta salientes
  • %{c,xxx}para una cookie específica
  • %{r,xxx}donde xxx es un atributo en ServletRequest
  • %{s,xxx}donde xxx es un atributo en HttpSession

Entre otros, probé estos. Ninguno de ellos estaba poblado.

%{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}

Respuesta1

Me encontré con un problema similar (mi cliente me pidió que registrara la identificación del cliente) y terminé buscando una solución. Al observar el código fuente y cómo se completa el registro de acceso, puedo decirles que existe una brecha bastante grande entre dónde se forma el registro y dónde se realiza el trabajo real.

Si echa un vistazo a Keycloak, está basado en Wildfly, que utiliza Undertow para alojar la funcionalidad del servidor http. Si bien la entrada del registro de acceso se emite una vez que se atendió la solicitud, existen pocas lagunas y abstracciones que compliquen las cosas.

Desde la perspectiva del software, hay un controlador de resaca, luego un servlet, luego un servlet resteasy, luego una aplicación de capa de claves y recursos específicos. Cuando utiliza el usuario de Keycloak o la consola de administración, en la mayoría de los lugares es un cliente "ligero" que se representa mediante un navegador web. Y este navegador llama recurso de descanso.

Si desea obtener información relacionada con el usuario con bastante frecuencia, no la encontrará en la sesión, porque la mayor parte del trabajo que realiza Kecloak es emitir tokens en nombre de los usuarios. Formalmente, el cliente que envía la solicitud actúa en nombre del usuario, lo que significa que no hay información explícita disponible para cada solicitud entrante. Además, la mayoría de los recursos de descanso, por definición, no tienen estado, lo que significa que operan de alguna manera con el usuario pero no completan mucho la sesión. Sólo una parte en la que puede contar con acceso a la información del usuario es cuando el usuario realmente inicia sesión y hace algo dentro de la consola de la cuenta de usuario. Además de eso, podría perderse la pelea, ya que los recursos de capa de claves que emiten tokens en la mayoría de los casos manejarán sesiones de cliente o relacionadas con el cliente.

Al grano: llego al punto donde ubiqué el lugar que analiza el formato del registro de acceso. Se basa en ExchangeAttributela idea de Undertow que permite traer su propia macro para el registro. Esta macro se puede utilizar para atravesar estructuras de memoria en busca de la información necesaria. Para mí era un client_id el que estaba haciendo el trabajo. Para eso terminé con la implementación de un FormAttribute. Todavía necesito encontrar una manera de conectarlo, pero desde la perspectiva de la prueba unitaria ya hizo "clic", vea qué tan básico es el código:

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

Punto principal: al utilizar el atributo de formulario para registrar el valor ingresado en el campo "nombre de usuario" del formulario de inicio de sesión para obtener "quién", luego puede combinarlo con la cookie de sesión que será retenida por el navegador. Mediante la fusión básica de los dos ases anteriores se puede lograr el resultado necesario. Usando el modelo anterior, puede implementar sus propios elementos y realizar un seguimiento de los tokens y otras cosas que le permitirán crear su aplicación.

Puedo actualizar la respuesta cuando encuentre una lógica de pegamento para inyectar correctamente un atributo adicional en el formato de registro en la resaca. EDITAR: Sólo una forma que encontré hasta ahora para inyectar atributos adicionales es copiándolos $JBOSS_HOME/modules/system/layers/base/org/wildfly/extension/undertow/main/y actualizándolos module.xmldentro de este directorio:

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

información relacionada