我设法在 WildFly 26 中使用 Keycloak 和 Elytron 实现承载令牌授权,以控制对企业应用程序 (.ear) 的 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 服务的战争不包含它需要的 EJB;它们位于单独的 EJB 模块 (.jar) 中。我想这就是我必须在 ejb3 子系统中定义这个应用程序安全域的原因。
undertow 子系统
/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中的实名是undertow子系统中定义的应用安全域。
问题
正如我之前所说,这个解决方案并非没有问题。鉴于我的企业应用程序 (.ear) 还有另一个 Web 模块 (.war),其中包含应用程序的 GUI 组件且没有 Web 服务,只要该第二个 Web 模块的 auth-method 是 FORM 或基本的。而且,您可能已经猜到了,我想使用 OIDC。
使用 OIDC 控制对应用程序的访问非常简单,正如 Farah Juma 在她的文章 Securing WildFly Apps with OpenID Connect 中正确解释的那样。但只要 Keycloak 领域的“令牌声明名称”是“realm_access.roles”(其默认值),它就可以工作。使用该名称,简单角色解码器不起作用。所以,我想需要一个自定义角色解码器。鉴于我的应用程序能够自行定义和管理角色和角色分配,而不是编写自定义角色解码器,我使用常量角色映射器来获取允许 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$1(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,则客户端得到的响应是Keycloak登录页面对应的html。
<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"
}
}
我希望这个部分解决方案对某人有所帮助,也希望有人能告诉我如何实施一个完整的解决方案。