在 KeyCLoak 15.0(即 WildFly 23.0)中,我尝试配置访问日志,以便在用户登录时也包含用户名(或用户的任何 ID)。在 中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}""
%u
我配置的文件中可以正确打印日志。但是或 的值%{REMOTE_USER}
始终为空(即-
)。
我发现记录某些用户 ID 的唯一方法是使用%{c,KEYCLOAK_SESSION}
(它包含realm/user-ID/secret
) 记录会话 cookie 值。这在生产中不是一个好主意。
关于如何在访问日志中记录用户名或用户 ID 有什么想法吗?
%u
即使在 KeyCLoak 中存在活动用户会话,或仍为空,这是 KeyCLoak 错误吗%{REMOTE_USER}
?或者是否可以在 KeyCLoak 中配置要输入哪些用户属性值REMOTE_USER
?
或者,如何将用户 ID 放在某个标题中以使用下列之一?
%{i,xxx}
对于传入标头%{o,xxx}
用于传出的响应标头%{c,xxx}
对于特定的 Cookie%{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,您会发现它基于 Wildfly,后者使用 Undertow 来托管 http 服务器功能。虽然在处理请求后会发出访问日志条目,但存在一些漏洞和抽象,这会使事情变得复杂。
从软件角度来看,有 undertow 处理程序,然后是 servlet,然后是 resteasy servlet,然后是 keycloak 应用程序和特定资源。当您使用 Keycloak 用户或管理控制台时,在大多数情况下,它是一个由 Web 浏览器呈现的“瘦”客户端。并且此浏览器调用 rest 资源。
如果您希望经常获取与用户相关的信息,则在会话中找不到该信息,因为 Kecloak 所做的大部分工作都是代表用户发放令牌。正式发送请求的客户端代表用户行事,这意味着它不是每个传入请求都可用的明确信息。此外,根据定义,大多数其余资源都是无状态的,这意味着它们确实以某种方式与用户一起操作,但不会在会话中填充太多内容。只有当用户实际登录并在用户帐户控制台内执行某些操作时,您才可以依赖对用户信息的访问。除此之外,它可能会失败,因为在大多数情况下,发放令牌的 keycloak 资源将处理客户端或客户端相关会话。
回到正题 - 我来到了我找到的执行访问日志格式解析的地方。它基于 UndertowExchangeAttribute
理念,允许为日志带来自己的宏。此宏可用于遍历内存结构以寻找必要的信息。对我来说,是 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;
}
}
}
要点 - 通过使用表单属性记录在登录表单的“用户名”字段中输入的值以获取“who”,然后您可以将其与浏览器将保留的会话 cookie 相结合。通过基本合并上述两个 Ace,您可以实现必要的结果。使用上述蓝图,您可以实现自己的功能并跟踪令牌和其他可以让您构建应用程序的内容。
当我找到一个粘合逻辑来正确地将附加属性注入 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>