No KeyCLoak 15.0 (que é WildFly 23.0), estou tentando configurar o log de acesso para incluir também o nome de usuário (ou qualquer ID do usuário) quando um usuário estiver logado keycloak/standalone/configuration/standalone.xml
.
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}""
O log é impresso corretamente no arquivo que configurei. No entanto, o valor de %u
or %{REMOTE_USER}
está sempre vazio (isto é -
).
A única maneira de registrar algum ID de usuário que encontrei foi registrar o valor do cookie de sessão com %{c,KEYCLOAK_SESSION}
(ele contém realm/user-ID/secret
). O que não é uma boa ideia fazer na produção.
Alguma idéia de como registrar o nome de usuário ou ID do usuário no log de acesso?
É um bug do KeyCLoak %u
ou %{REMOTE_USER}
está vazio mesmo quando há uma sessão de usuário ativa no KeyCloak? Ou é possível no KeyCLoak configurar qual valor de atributo do usuário entra REMOTE_USER
?
Alternativamente, como colocar o userID em algum cabeçalho para usar um dos seguintes?
%{i,xxx}
para cabeçalhos recebidos%{o,xxx}
para cabeçalhos de resposta de saída%{c,xxx}
para um cookie específico%{r,xxx}
onde xxx é um atributo no ServletRequest%{s,xxx}
onde xxx é um atributo no HttpSession
Entre outros, experimentei estes. Nenhum deles foi povoado.
%{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}
Responder1
Me deparei com um problema semelhante (meu cliente me pediu para registrar o ID do cliente) e acabei procurando uma solução. Observando o código-fonte e como o log de acesso é preenchido, posso dizer que há uma grande lacuna entre onde o log é formado e onde o trabalho real está sendo feito.
Se você der uma olhada no Keycloak, ele é baseado no Wildfly, que usa Undertow para hospedar a funcionalidade do servidor http. Embora a entrada do log de acesso seja emitida assim que a solicitação for atendida, existem algumas lacunas e abstrações que complicam as coisas.
Da perspectiva do software, há o manipulador de ressaca, depois o servlet, depois o servlet resteasy, depois o aplicativo keycloak e recursos específicos. Quando você usa o usuário ou console de administração do Keycloak, na maioria dos lugares ele é um cliente "fino" que é renderizado por um navegador da web. E este navegador chama o recurso rest.
Se você deseja obter informações relacionadas ao usuário com frequência, elas não serão encontradas na sessão, porque a maior parte do trabalho realizado pelo Kecloak consiste na emissão de tokens em nome dos usuários. Formalmente, o cliente que envia a solicitação atua em nome do usuário, o que significa que não há informações explícitas disponíveis para cada solicitação recebida. Além disso, a maioria dos recursos restantes, por definição, não tem estado, o que significa que eles operam de alguma forma com o usuário, mas não preenchem muito a sessão. Apenas uma parte na qual você pode contar com acesso às informações do usuário é quando o usuário realmente faz login e faz algo no console da conta do usuário. Além disso, a luta pode ser perdida, já que os recursos do keycloak que emitem tokens, na maioria dos casos, lidarão com clientes ou sessões relacionadas ao cliente.
Direto ao ponto - chego ao ponto onde localizei o local que faz a análise do formato do log de acesso. É baseado na ExchangeAttribute
ideia do Undertow que permite trazer sua própria macro para log. Esta macro pode ser utilizada para percorrer estruturas de memória em busca das informações necessárias. Para mim era um client_id que estava fazendo o trabalho. Para isso acabei com a implementação de um arquivo FormAttribute
. Ainda preciso encontrar uma maneira de conectá-lo, mas da perspectiva do teste de unidade ele já "clicou", veja como é o código básico:
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;
}
}
}
Ponto principal - usando o atributo de formulário para registrar o valor inserido no campo "nome de usuário" do formulário de login para obter "quem", você pode combinar isso com o cookie de sessão que será retido pelo navegador. Pela fusão básica dos dois ases acima, você pode alcançar o resultado necessário. Usando o modelo acima, você pode implementar suas próprias coisas e acompanhar tokens e outras coisas que permitirão construir seu aplicativo.
Posso atualizar a resposta quando encontrar uma lógica de cola para injetar adequadamente atributos adicionais no formato de log na ressaca. EDIT: Apenas uma maneira que encontrei até agora de injetar atributos extras é copiá-los $JBOSS_HOME/modules/system/layers/base/org/wildfly/extension/undertow/main/
e atualizá-los module.xml
neste diretório:
<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>