Expand my Community achievements bar.

SOLVED

Custom AuthenticationHandler and LoginModule on AEM6

Avatar

Level 4

Hi,

I'm trying to implement a custom AuthenticationHandler (http://sling.apache.org/documentation/the-sling-engine/authentication/authentication-authenticationh...) that extracts credentials from the request and authenticates them against an external service (let's say a REST API). I have implemented the AuthenticationHandler interface following this guide http://www.wemblog.com/2013/03/how-to-create-custom-authentication.html and the LoginModulePlugin interface (https://sling.apache.org/apidocs/sling6/org/apache/sling/jcr/jackrabbit/server/security/LoginModuleP...). When I call the path uder authentication my AuthenticationHandler is correctly called (extractCredentials). My problem is that the LoginModule's AuthenticationPlugin is not fired when the AuthenticationInfo is returned by extractCredentials. Any why I get this wrong behavior? I'm appending below the code for reference.


Thank you!

AuthenticationHandler

import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.osgi.framework.Constants; import org.osgi.framework.ServiceRegistration; import org.osgi.service.component.ComponentContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Properties; import org.apache.felix.scr.annotations.Property; import org.apache.felix.scr.annotations.Service; import org.apache.sling.auth.core.spi.AuthenticationFeedbackHandler; import org.apache.sling.auth.core.spi.AuthenticationHandler; import org.apache.sling.auth.core.spi.AuthenticationInfo; import org.apache.sling.auth.core.spi.DefaultAuthenticationFeedbackHandler; @Component(metatype = true, immediate = true, label = "Custom Auth Handler", description="Authenticates User Against a REST Service") @Service @Properties({ @Property(name = AuthenticationHandler.PATH_PROPERTY, value = "/content/custom-commerce-it/it/j_custom_security_check"), @Property(name = AuthenticationHandler.TYPE_PROPERTY, value = CustomAuthenticationHandler.AUTH_TYPE, propertyPrivate = true), @Property(name = Constants.SERVICE_VENDOR, value = "Custom"), @Property(name = Constants.SERVICE_DESCRIPTION, value = "Custom Auth Handler") }) public class CustomAuthenticationHandler extends DefaultAuthenticationFeedbackHandler implements AuthenticationHandler, AuthenticationFeedbackHandler { private final Logger log = LoggerFactory.getLogger(this.getClass()); protected static final String AUTH_TYPE = "CUSTOM_AUTH"; private ServiceRegistration loginModule; @Activate protected void activate(ComponentContext componentContext) { log.debug("*** CustomAuthenticationHandler activate ***"); try { this.loginModule = CustomLoginModule.register(this, componentContext.getBundleContext()); } catch (Throwable t) { log.error("Error while activating CustomAuthenticationHandler!!!", t); } } @Override public void dropCredentials(HttpServletRequest request, HttpServletResponse response) throws IOException { log.debug("*** CustomAuthenticationHandler dropCredentials ***"); } @Override public AuthenticationInfo extractCredentials(HttpServletRequest request, HttpServletResponse response) { log.debug("*** CustomAuthenticationHandler extractCredentials ***"); AuthenticationInfo authInfo = new AuthenticationInfo(CustomAuthenticationHandler.AUTH_TYPE, "userId", "pwd".toCharArray()); return authInfo; } @Override public boolean requestCredentials(HttpServletRequest request, HttpServletResponse response) throws IOException { log.debug("*** CustomAuthenticationHandler requestCredentials ***"); response.sendRedirect("/"); return true; } @Override public boolean authenticationSucceeded(HttpServletRequest request, HttpServletResponse response, AuthenticationInfo authInfo) { log.debug("*** CustomAuthenticationHandler authenticationSucceeded ***"); return super.authenticationSucceeded(request, response, authInfo); } @Override public void authenticationFailed(HttpServletRequest request, HttpServletResponse response, AuthenticationInfo authInfo) { log.debug("*** CustomAuthenticationHandler authenticationFailed ***"); super.authenticationFailed(request, response, authInfo); } }

LoginModulePlugin

import java.security.Principal; import java.util.Hashtable; import java.util.Map; import javax.jcr.Credentials; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.login.FailedLoginException; import javax.security.auth.login.LoginException; import org.apache.sling.jcr.jackrabbit.server.security.AuthenticationPlugin; import org.apache.sling.jcr.jackrabbit.server.security.LoginModulePlugin; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.ServiceRegistration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class CustomLoginModule implements LoginModulePlugin { private final Logger log = LoggerFactory.getLogger(this.getClass()); /** * The {@link CustomAuthenticationHandler} used to validate the credentials * and its contents. */ private final CustomAuthenticationHandler authHandler; /** * Creates an instance of this class and registers it as a * <code>LoginModulePlugin</code> service to handle login requests with * <code>SimpleCredentials</code> provided by the * {@link CustomAuthenticationHandler}. * * @param authHandler The {@link CustomAuthenticationHandler} providing *            support to validate the credentials * @param bundleContext The <code>BundleContext</code> to register the *            service * @return The <code>ServiceRegistration</code> of the registered service for *         the {@link CustomAuthenticationHandler} to unregister the service *         on shutdown. */ static ServiceRegistration register( final CustomAuthenticationHandler authHandler, final BundleContext bundleContext) { CustomLoginModule plugin = new CustomLoginModule(authHandler); Hashtable<String, Object> properties = new Hashtable<String, Object>(); properties.put(Constants.SERVICE_DESCRIPTION, "LoginModulePlugin Support for CustomAuthenticationHandler"); return bundleContext.registerService(LoginModulePlugin.class.getName(), plugin, properties); } /** * Private constructor called from * {@link #register(CustomAuthenticationHandler, BundleContext)} to create an * instance of this class. * * @param authHandler The {@link CustomAuthenticationHandler} used to validate *            the credentials attribute */ private CustomLoginModule(final CustomAuthenticationHandler authHandler) { this.authHandler = authHandler; } @Override public boolean canHandle(Credentials arg0) { log.debug("*** CustomLoginModule canHandle ***"); return true; } @Override public void doInit(CallbackHandler arg0, Session arg1, Map arg2) throws LoginException { log.debug("*** CustomLoginModule doInit ***"); } @Override public AuthenticationPlugin getAuthentication(Principal arg0, Credentials arg1) throws RepositoryException { log.debug("*** CustomLoginModule getAuthentication ***"); return new AuthenticationPlugin() { public boolean authenticate(Credentials credentials) throws RepositoryException { log.debug("*** CustomLoginModule authenticate ***"); return true; } }; } @Override public Principal getPrincipal(Credentials arg0) { log.debug("*** CustomLoginModule getPrincipal ***"); return null; } @Override public int impersonate(Principal arg0, Credentials arg1) throws RepositoryException, FailedLoginException { log.debug("*** CustomLoginModule impersonate ***"); return 0; } }
1 Accepted Solution

Avatar

Correct answer by
Employee

Hi,

The LoginModulePlugin interface has never been supported when running inside AEM. If you need to create a custom LoginModule in AEM6, it depends upon whether you are using CRX2 or Oak. With CRX2, you would write a traditional LoginModule and use JAAS configuration to enable it (similar to how the LDAPLoginModule is configured). With Oak, you can use an OSGi-based LoginModuleFactory (see http://felix.apache.org/documentation/subprojects/apache-felix-jaas.html). However, in Oak, you probably wouldn't need to implement a LoginModule directly, instead you should use the ExternalIdenittyProvider inteface. See http://jackrabbit.apache.org/oak/docs/security/authentication/externalloginmodule.html for more information on that. You can also refer to the Oak LDAP EIP for a fully functional example.

Incidentally, the path in your AuthenticationHandler looks wrong. You would typically set this path to be the root or some higher level content path representing the portion of the URL space your AuthenticationHandler applies to.

Regards,

Justin

View solution in original post

6 Replies

Avatar

Correct answer by
Employee

Hi,

The LoginModulePlugin interface has never been supported when running inside AEM. If you need to create a custom LoginModule in AEM6, it depends upon whether you are using CRX2 or Oak. With CRX2, you would write a traditional LoginModule and use JAAS configuration to enable it (similar to how the LDAPLoginModule is configured). With Oak, you can use an OSGi-based LoginModuleFactory (see http://felix.apache.org/documentation/subprojects/apache-felix-jaas.html). However, in Oak, you probably wouldn't need to implement a LoginModule directly, instead you should use the ExternalIdenittyProvider inteface. See http://jackrabbit.apache.org/oak/docs/security/authentication/externalloginmodule.html for more information on that. You can also refer to the Oak LDAP EIP for a fully functional example.

Incidentally, the path in your AuthenticationHandler looks wrong. You would typically set this path to be the root or some higher level content path representing the portion of the URL space your AuthenticationHandler applies to.

Regards,

Justin

Avatar

Level 4

Hi and thank you for your help!

The reason why I tried to implement the LoginModulePlugin is basically this blog post where for CQ > 5.6 version the advice is to implement org.apache.sling.jcr.jackrabbit.server.security.LoginModulePlugin.

I have also found this on the OpenIDAuthenticationHandler Sling doc page: "The OpenID authentication handler can be integrated in two ways into the Jackrabbit authentication mechanism which is based on JAAS LoginModule. One integration is by means of a LoginModulePlugin which plugs into the extensible LoginModule architecture supported by the Sling Jackrabbit Embedded Repository bundle."

This sentence is not completely clear to me but as far as I can understand the LoginModulePlugin is a possible option. So why this should not be possible in AEM6 which relies on Sling and Jackrabbit?

Regards,

Stefano

Avatar

Employee

Hi,

The link above doesn't work for me, so I can't comment on the specifics of that post. As to your broader question, AEM does not use all of Sling (or all of Jackrabbit for that matter) and in this particular case, the "Sling Jackrabbit Embedded Repository bundle" is part of Sling which AEM does not use. AEM/CQ uses its own "embedded repository" bundle (actually in CQ 5.4, the repository wasn't embedded at all, but that's a different story). With Oak, the repository bundles are now provided by Oak, which should, in the long run, lead to avoiding this type of confusion.

Justin

Avatar

Level 2

Hi Stefano, are you able to to implement custom login successfully?

Avatar

Level 4

Hi,

I have just fixed the link above :-)

One more thing that is still far to be clear. How the AuthenticationHandler and LoginModule are linked together? I mean how the AuthenticationInfo are passed to the custom LoginModule?

Thank you,

Stefano

Avatar

Employee

Thanks. I posted a comment asking Yogesh to remove the pointer to LoginModulePlugin. There was discussion of supporting that in AEM6 early on, but the Oak way is far superior.

In terms of your second question - the relationship between the AuthenticationHandler and the LoginModule is brokered by the core Sling Authentication system. Each appropriate AuthenticationHandler (based on the path) is called in order, the first to return a non-null AuthenticationInfo "wins". This AuthenticationInfo is transformer into a Credentials object which is then passed to the login() method of the Repository. The Repository implementation then calls the LoginModules and follows JAAS rules to authentication (or not) the user.

Regards,

Justin