将 Keycloak 适配器与 Wildfly 26 一起使用不提供 "KEYCLOAK" 作为机制

Using Keycloak adapter with Wildfly 26 does not provide "KEYCLOAK" as mechanism

我在 WildFly 中部署了一个 JAX-RS 应用程序。应用程序的端点应由 Access Type: bearer-only 的 Keycloak 保护。这适用于最高 24 的 WildFly 版本。

从 WildFly 25 开始,Keycloak 适配器已被弃用,应该迁移到新的 Elytron 子系统。根据此 WildFly 问题 https://issues.redhat.com/browse/WFLY-15485,但是 OIDC 适配器尚未准备好与 bearer-only 一起使用。但有人提到,使用 Keycloak Wildfly 适配器应该还是可以的。

latest Keycloak documentation and this thread in Google Groups 也说明了这一点。

所以我从这个位置安装了适配器和 运行 安装脚本:

https://github.com/keycloak/keycloak/releases/download/16.1.1/keycloak-oidc-wildfly-adapter-16.1.1.zip

./bin/jboss-cli.sh --file=bin/adapter-elytron-install-offline.cli -Dserver.config=standalone-full.xml

部署应用程序时,我收到以下错误消息:

java.lang.IllegalStateException: The required mechanism 'KEYCLOAK' is not available in mechanisms [BASIC, CLIENT_CERT, DIGEST, FORM] from the HttpAuthenticationFactory

设置

web.xml

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">

    <!-- Security configuration -->
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>admin-api</web-resource-name>
            <url-pattern>/administration/*</url-pattern>
            <url-pattern>/operations/*</url-pattern>
            <url-pattern>/applications/*</url-pattern>
            <url-pattern>/entities/*</url-pattern>
        </web-resource-collection>
    </security-constraint>

    <login-config>
        <auth-method>KEYCLOAK</auth-method>
        <realm-name>this is ignored currently</realm-name>
    </login-config>

    <security-role>
        <role-name>*</role-name>
    </security-role>

</web-app>

我终于在没有 Keycloak 适配器的情况下工作了,即使用新的 built-in Elytron 子系统。

oidc.json(位于WEB-INF目录)

{
  "realm": "myrealm",
  "client-id": "my-client-app",
  "auth-server-url": "${keycloak.url}/auth",
  "provider-url": "${keycloak.url}/auth/realms/myrealm",
  "bearer-only": true,
  "enable-cors": true,
  "ssl-required": "none"
}

web.xml

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">

    <!-- other configuration -->

    <login-config>
        <auth-method>OIDC</auth-method>
    </login-config>

</web-app>

我设法在 WildFly 26 中使用 Keycloak 和 Elytron 实现了不记名令牌授权,以便控制对企业应用程序 (.war) 的 Web 模块 (.war) 中的 RESTful Web 服务的访问。耳),但解决方案并非没有问题。这就是我所做的:

定义一个 elytron 令牌领域

/subsystem=elytron/token-realm=xyz2ap112-token-realm/:add(\
    principal-claim=preferred_username,\
    oauth2-introspection={\
        client-id=xyz2ap112-web-api,\
        client-secret=${env.keycloak_client_secret},\
        introspection-url=${env.keycloak_introspection_url}\
    }\
)

定义一个elytron角色解码器

/subsystem=elytron/simple-role-decoder=xyz2ap112-realm-access-roles/:add(\
    attribute=realm_access_roles\
)

警告:Keycloak 领域的默认“令牌声明名称”是“realm_access.roles”。为了使这个角色解码器起作用,我必须将其更改为“realm_access_roles”(无点)。这个方案有什么问题以后再说。

定义一个 elytron 安全域

/subsystem=elytron/security-domain=xyz2ap112-token-security-domain/:add(\
    realms=[{realm="xyz2ap112-token-realm",role-decoder="xyz2ap112-realm-access-roles"}],\
    default-realm=xyz2ap112-token-realm,\
    permission-mapper=default-permission-mapper\
)

定义一个 elytron HTTP 认证工厂

/subsystem=elytron/http-authentication-factory=xyz2ap112-web-api-authentication-factory/:add(\
    security-domain=xyz2ap112-token-security-domain,\
    mechanism-configurations=[{\
        mechanism-name=BEARER_TOKEN,\
        mechanism-realm-configurations=[realm-name=xyz2ap112-token-realm]\
    }],\
    http-server-mechanism-factory=global\
)

定义两个应用程序安全域

ejb3 子系统

/subsystem=ejb3/application-security-domain=xyz2ap112-web-api-security-domain/:add(\
    security-domain=xyz2ap112-token-security-domain\
)

警告:包含 Web 服务的 war 不包含它需要的 EJB;这些在单独的 EJB 模块 (.jar) 中。我想这就是我必须在 ejb3 子系统中定义此应用程序安全域的原因。

暗流子系统

/subsystem=undertow/application-security-domain=xyz2ap112-web-api-security-domain/:add(\
    http-authentication-factory=xyz2ap112-web-api-authentication-factory,\
    override-deployment-config=true\
)

配置应用程序的jboss-web.xml和web.xml

<jboss-web>
    <context-root>/xyz2ap112-web-api</context-root>
    <resource-ref>
        <res-ref-name>jdbc/xyz2ap112</res-ref-name> <!-- Logical name only. -->
        <jndi-name>java:/jdbc/xyz2ap112</jndi-name> <!-- Real JNDI name. -->
    </resource-ref>
    <security-domain>xyz2ap112-web-api-security-domain</security-domain>
</jboss-web>

security-domain 是在 undertow 子系统中定义的应用程序安全域。

<login-config>
    <auth-method>BASIC</auth-method>
    <realm-name>xyz2ap112-web-api-security-domain</realm-name>
</login-config>

login-config中的real-name是undertow子系统中定义的应用程序安全域。

问题

正如我之前所说,这个解决方案并非没有问题。鉴于我的企业应用程序 (.ear) 还有另一个 Web 模块 (.war),它包含应用程序的 GUI 组件但没有 Web 服务,只要 auth-method 该解决方案有效第二个 Web 模块是 FORM 或 BASIC。而且,您可能已经猜到了,我想使用 OIDC。

ward 使用 OIDC 控制对应用程序的访问非常简单,正如 Farah Juma in her article Securing WildFly Apps with OpenID Connect 所正确解释的那样。但只要 Keycloak 领域的“令牌声明名称”是“realm_access.roles”(其默认值),它就可以工作。使用该名称,simple-role-decoder 不起作用。所以,我想自定义 role-decoder 是必需的。鉴于我的应用程序能够自行定义和管理角色和角色分配,而不是编写自定义 role-decoder,我使用 constant-role-mapper 来获取允许 Web 服务执行和执行的单个角色使用应用程序中定义的角色检查权限。再一次,只要第二个 Web 模块的 auth-method 是 FORM 或 BASIC,它就可以工作;使用 OIDC,不执行 Web 服务;客户端获得 HTTP 500(见下文)。 运行 WildFly(Keycloak 和应用程序)的日志中没有其他信息。

这是 GUI Web 模块的 oidc.json 文件:

{
    "client-id": "xyz2ap112-web",
    "confidential-port": 8543,
    "principal-attribute": "preferred_username",
    "provider-url": "http://localhost:8180/auth/realms/jrcam",
    "public-client": true,
    "ssl-required": "external"
}

这是客户端异常:

Exception in thread "main" javax.ws.rs.InternalServerErrorException: HTTP 500 Internal Server Error
    at org.glassfish.jersey.client.JerseyInvocation.convertToException(JerseyInvocation.java:1098)
    at org.glassfish.jersey.client.JerseyInvocation.translate(JerseyInvocation.java:883)
    at org.glassfish.jersey.client.JerseyInvocation.lambda$invoke(JerseyInvocation.java:767)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:316)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:298)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:229)
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:414)
    at org.glassfish.jersey.client.JerseyInvocation.invoke(JerseyInvocation.java:765)
    at org.glassfish.jersey.client.JerseyInvocation$Builder.method(JerseyInvocation.java:428)
    at org.glassfish.jersey.client.JerseyInvocation$Builder.get(JerseyInvocation.java:324)
    at org.xyz.jax.rs.client.base.AbstractFacadeServiceClient.find(AbstractFacadeServiceClient.java:28)
    at xyz2.BarrioFacadeClient.find(BarrioFacadeClient.java:40)
    at xyz2.BarrioFacadeClient.main(BarrioFacadeClient.java:24)

如果Web Services Web模块的auth-method为OIDC,客户端得到的响应为html对应Keycloak登录页面

<html xmlns="http://www.w3.org/1999/xhtml" class="login-pf">
    ...
                    <h1 id="kc-page-title">
                        Sign in to your account
                    </h1>
    ...
</html>

这是 Web 服务 Web 模块的 oidc.json 文件:

{
    "client-id": "xyz2ap112-web-api",
    "confidential-port": 8543,
    "principal-attribute": "preferred_username",
    "provider-url": "http://localhost:8180/auth/realms/jrcam",
    "ssl-required": "external",
    "bearer-only": true,
    "verify-token-audience": true,
    "realm-public-key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk3PD30r3SQBqnO15g/Jc5z3NFnt9HLA6QlQt2QLtxvGhLcerTD2rVWCst/4NSQev9dBscFnwxXyAoZAqTm7w0oPzlhw1Xbqt1dpKdNjMtbJxmpqzCRLTjmNatPmoAGx+9TWOPKw1qfEwZOy9xOqnCbBeT5eGCAXci+wvt8mpNX9lpAguFxgpFtyVc0at35Lw3BdZ13+6Ljxu6Z+mam1tQ9mwey0ubfhV3NK0eN8jruKWrCyGw6DRbmvKFTwQa5akDbMWt3H/HaSLMXBOrBKq9He6azVL3dkbdd40drgHtI8G+ANC1NhOPzjPtuifo9U2wHD6o8S03o35mm4xjJNcqQIDAQAB",
    "credentials": {
        "secret": "8c98045a-4640-46e7-9f68-74a289e43b7e"
    }
}

我希望这个部分解决方案对某人有所帮助,也希望有人能告诉我如何实施完整的解决方案。