-
1. Re: How to use keycloak authentication for ejb remote client with wildfly elytron security?
schustephan May 10, 2019 1:22 PM (in response to marcoben73)I was researching a similar use case today. The information available online about this is pretty sparse but perhaps the following will be helpful:
As you already mentioned, the remote EJB calls use the remoting subsystem. The remoting connectors use /server/management/security-realms for authentication and authorization instead of the security-realms in the Elytron subsystem. The authenticators included in the Keycloak adapters, however, seem to be geared towards the Elytron security system, so no configuration I tried worked out of the box.
Fortunately you can just write a small custom authenticator that delegates everything to the existing Keycloak adapters. You can find a pretty comprehensive guide on writing an authentication/ authorization plugin here: https://docs.wildfly.org/14/Admin_Guide.html#Security_Realms_Plugins and some documentation on Keycloak Login Modules this works with here: https://www.keycloak.org/docs/3.2/securing_apps/topics/oidc/java/jaas.html
Here is a small spaghetti-coded proof of concept. You will have to install the two below classes as a module on Wildfly. (as described in the first link)
The keycloak.json file referenced in the standalone.xml is generated by Keycloak (Client > Installation in the web interface)
import java.io.IOException; import java.security.Principal; import java.util.Map; import javax.security.auth.Subject; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.TextOutputCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.LoginException; import org.jboss.as.domain.management.plugin.AbstractPlugIn; import org.jboss.as.domain.management.plugin.AuthorizationPlugIn; import org.jboss.as.domain.management.plugin.Credential; import org.jboss.as.domain.management.plugin.Identity; import org.jboss.as.domain.management.plugin.ValidatePasswordCredential; import org.keycloak.adapters.jaas.DirectAccessGrantsLoginModule; public class KeycloakManagementAuthenticator extends AbstractPlugIn { private DirectAccessGrantsLoginModule keycloakDelegate = new DirectAccessGrantsLoginModule(); private Map<String, String> configuration; @Override public void init(Map<String, String> configuration, Map<String, Object> sharedState) throws IOException { this.configuration = configuration; // This will allow an AuthorizationPlugIn to delegate back to this instance. sharedState.put(AuthorizationPlugIn.class.getName(), this); } @Override public Identity loadIdentity(final String userName, String realm) throws IOException { System.out.println("loadIdentity for " + userName + "@" + realm); return new SampleIdentity(userName, realm); } @Override public String[] loadRoles(String userName, String realm) throws IOException { //TODO return new String[0]; } private class SampleIdentity implements Identity { private final String userName; private final Credential credential; private SampleIdentity(final String userName, final String password) { System.out.println("creating new SampleIdentity"); this.userName = userName; this.credential = new ValidatePasswordCredential() { @Override public boolean validatePassword(final char[] password) { System.out.println("SampleIdentity: validating password: " + String.valueOf(password)); try { Subject subject = new Subject(); subject.getPrincipals().add(new Principal() { private final String name = userName; @Override public String getName() { return name; } }); subject.getPublicCredentials().add(password); subject.setReadOnly(); System.out.println("assembeled subject"); CallbackHandler callbackHandler = new CallbackHandler() { @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (int i = 0; i < callbacks.length; i++) { if (callbacks[i] instanceof TextOutputCallback) { // display the message according to the specified type TextOutputCallback toc = (TextOutputCallback) callbacks[i]; switch (toc.getMessageType()) { case TextOutputCallback.INFORMATION: System.out.println(toc.getMessage()); break; case TextOutputCallback.ERROR: System.out.println("ERROR: " + toc.getMessage()); break; case TextOutputCallback.WARNING: System.out.println("WARNING: " + toc.getMessage()); break; default: throw new IOException("Unsupported message type: " + toc.getMessageType()); } } else if (callbacks[i] instanceof NameCallback) { // prompt the user for a username NameCallback nc = (NameCallback) callbacks[i]; // ignore the provided defaultName nc.setName(userName); } else if (callbacks[i] instanceof PasswordCallback) { // prompt the user for sensitive information PasswordCallback pc = (PasswordCallback) callbacks[i]; pc.setPassword(password); } else { throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Callback"); } } } }; System.out.println("before init keycloak delegate"); keycloakDelegate.initialize(subject, callbackHandler, sharedState, configuration); System.out.println("before login"); boolean result = keycloakDelegate.login(); System.out.println("after login: " + result); return result; } catch (LoginException ex) { System.out.println("Login Exception"); ex.printStackTrace(); return false; } } }; } @Override public String getUserName() { return userName; } @Override public Credential getCredential() { return credential; } } }
import org.jboss.as.domain.management.plugin.AuthenticationPlugIn; import org.jboss.as.domain.management.plugin.AuthorizationPlugIn; import org.jboss.as.domain.management.plugin.Credential; import org.jboss.as.domain.management.plugin.PlugInProvider; public class KeycloakManagementPluginProvider implements PlugInProvider{ @Override public AuthenticationPlugIn<Credential> loadAuthenticationPlugIn(String name) { if ("Keycloak".equals(name)) { return new KeycloakManagementAuthenticator(); } return null; } @Override public AuthorizationPlugIn loadAuthorizationPlugIn(String name) { if ("Keycloak".equals(name)) { return new KeycloakManagementAuthenticator(); } return null; } }
module.xml
<?xml version="1.0" encoding="UTF-8"?> <module xmlns="urn:jboss:module:1.1" name="my.keycloak.auth.module"> <properties> </properties> <resources> <resource-root path="KeycloakManagementAuthenticator-1.0.0.jar"/> </resources> <dependencies> <module name="org.jboss.as.domain-management" /> <module name="org.keycloak.keycloak-adapter-core" /> </dependencies> </module>
standalone.xml
<management> <security-realms> ... <security-realm name="KeycloakRealm"> <plug-ins> <plug-in module="my.keycloak.auth.module"> </plug-in> </plug-ins> <authentication> <plug-in name="Keycloak" mechanism="PLAIN"> <properties> <property name="keycloak-config-file" value="/path/to/keycloak.json"></property> </properties> </plug-in> </authentication> </security-realm> </security-realms> ... <subsystem xmlns="urn:jboss:domain:remoting:4.0"> <http-connector name="http-remoting-connector" connector-ref="default" security-realm="KeycloakRealm"/> </subsystem>
This approach certainly needs some adjustments since, among other atrocities, it currently sends the plain credentials via http but it was the first proof of concept that worked for me.
Currently this works with user/pass but you should be able to switch to bearer tokens by simply delegating to org.keycloak.adapters.jaas.BearerTokenLoginModule instead of org.keycloak.adapters.jaas.DirectAccessGrantsLoginModule.
Would appreciate any hints if you come up with a better solution.
-
2. Re: How to use keycloak authentication for ejb remote client with wildfly elytron security?
marcoben73 Jul 5, 2019 7:57 AM (in response to schustephan)Hi Stephan,
thanks for your answer, but in my Wildfly configuration I completely disabled legacy security, so your approach which use JAAS is not suitable for me.
After a lot of deep dive into elytron code and searches in forum, I was able to configure ejb client and server to authenticate using JWT bearer token.
The most useful blog articles that put me in the right direction are:
-
3. Re: How to use keycloak authentication for ejb remote client with wildfly elytron security?
axeleast Dec 4, 2019 4:47 AM (in response to marcoben73)Hi Marco,
did you manage to secure the JNDI lookup of EJBs with the BEARER_TOKEN mechnism?
With the links you provided i managed to secure HTTP connection, but am at a loss when it comes to lookup of EJBs. Any pointers to the right direction would be appreciated!