April 1, 2020
Estimated Post Reading Time ~

How to Create Custom Authentication Handler in CQ

Use Case:
You want to use custom Authentication handler instead of OOTB one for authentication.
Custom User RegistrationPre requisite:

http://sling.apache.org/site/authentication.html
http://sling.apache.org/site/authentication-authenticationhandler.html
http://sling.apache.org/apidocs/sling6/org/apache/sling/auth/core/spi/AuthenticationHandler.html

Available Authentication Handler in CQ:

How to create your Own:
1) Create custom class extending Sling Authentication Handler and override available methods

import org.apache.sling.auth.core.spi.AuthenticationFeedbackHandler;
import org.apache.sling.auth.core.spi.AuthenticationHandler;
import org.apache.sling.auth.core.spi.DefaultAuthenticationFeedbackHandler;

@Component(metatype = true, immediate = true, label = "My Custom Authentication Handelr",
description="Authenticates User Against Citrix One Web Service")
@Service
@Properties({
@Property(name = AuthenticationHandler.PATH_PROPERTY, value = "/"),
@Property(name = Constants.SERVICE_DESCRIPTION, value = "My Custom Authentication Handler") })
public class MyCustomAuthenticationHandler extends DefaultAuthenticationFeedbackHandler implements AuthenticationHandler,
AuthenticationFeedbackHandler {

private static final String REQUEST_METHOD = "POST";
private static final String USER_NAME = "j_username";
private static final String PASSWORD = "j_password";
static final String AUTH_TYPE = "YOGESH";

static final String REQUEST_URL_SUFFIX = "/j_mycustom_security_check";

/**
If you see most of the method under sling authentication handler, They have request and response object available. You can use that object to get information about user (Either by reading cookie or some other way).
*/
//Important methods
//Return true if succesful
public boolean authenticationSucceeded(HttpServletRequest request, HttpServletResponse response,
AuthenticationInfo authInfo) {

}
//Extract data from request Object

public AuthenticationInfo extractCredentials(HttpServletRequest request, HttpServletResponse response) {

//You can have logic like. This will read user name and password from form post and set credentials

if (REQUEST_METHOD.equals(request.getMethod()) && request.getRequestURI().endsWith(REQUEST_URL_SUFFIX)
&& request.getParameter(USER_NAME) != null) {

if (!AuthUtil.isValidateRequest(request)) {
AuthUtil.setLoginResourceAttribute(request, request.getContextPath());
}

SimpleCredentials creds = new SimpleCredentials(request.getParameter(USER_NAME), request.getParameter(PASSWORD).toCharArray());
//ATTR_HOST_NAME_FROM_REQUEST can be any thing this is just an example
creds.setAttribute(ATTR_HOST_NAME_FROM_REQUEST, request.getServerName());

return createAuthenticationInfo(creds);
}
return null;
}

//Custom Create AuthInfo. Not required but you can create
private AuthenticationInfo createAuthenticationInfo(Credentials creds) {
//Note that there is different signature of this method. Use one that you need.
AuthenticationInfo info = new AuthenticationInfo(AUTH_TYPE);
//this you can use it later in auth process
info.put("Your Custom Attribute", creds);
return info;
}

//Do something when authentication failed.
public void authenticationFailed(HttpServletRequest request, HttpServletResponse response,
AuthenticationInfo authInfo) {

}


2) Create a form (Your custom Login form)
which will be something like this

String action = currentPage.getPath() +"/j_mycustom_security_check";

<form method="POST" action="<%= xssAPI.getValidHref(action) %>">

Enter User Name: <input name="j_username" type="text" />
Enter Passord: <input name="j_password" type="text" />
<input type="button" name="Click Here to login">
</form>


You can also use Ajax post or something to see if response is 200 (Which mean successful login)

3) Then under apache sling post servlet, Make sure that you allow parameter you are posting. In this case j_*


4) Add your custom authentication prefix to sling authenticator service


5) Once you have your bundle deployed, You should see your additional authentication handler.


Integrate it with Custom Pluggable Login Module (AEM 6)

Step1 : create pluggable login Module

import java.security.Principal;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;

import javax.jcr.Credentials;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.security.auth.callback.CallbackHandler;

import org.apache.commons.lang3.StringUtils;
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;


/**
* The <code>OpenIDLoginModulePlugin</code> is a simple Sling LoginModulePlugin
* enabling authentication of OpenID identifiers as Jackrabbit Repository users
*/
class CustomPluggableLoginModule implements LoginModulePlugin {

private final CustomAuthenticationHandler authHandler;

/** default log */
private final Logger log = LoggerFactory.getLogger(getClass());

/**
* 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 OpenIDAuthenticationHandler}.
*
* @param authHandler
* The {@link OpenIDAuthenticationHandler} 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 OpenIDAuthenticationHandler} to unregister the service
* on shutdown.
*/
static ServiceRegistration register(final CustomAuthenticationHandler authHandler, final BundleContext bundleContext) {
CustomPluggableLoginModule plugin = new CustomPluggableLoginModule(authHandler);

Hashtable<String, Object> properties = new Hashtable<String, Object>();
properties.put(Constants.SERVICE_DESCRIPTION, "LoginModulePlugin Support for OpenIDAuthenticationHandler");
properties.put(Constants.SERVICE_VENDOR, bundleContext.getBundle().getHeaders().get(Constants.BUNDLE_VENDOR));

return bundleContext.registerService(LoginModulePlugin.class.getName(), plugin, properties);
}

private CustomPluggableLoginModule(final CustomAuthenticationHandler authHandler) {
this.authHandler = authHandler;
}

/**
* This implementation does nothing.
*/
@SuppressWarnings("unchecked")
public void doInit(final CallbackHandler callbackHandler, final Session session, final Map options) {
return;
}

/**
* Returns <code>true</code> indicating support if the credentials is a
* <code>SimplerCredentials</code> object and has an authentication data
* attribute.
* <p>
* This method does not validate the data just checks its presence.
*
* @see CookieAuthenticationHandler#hasAuthData(Credentials)
*/
public boolean canHandle(Credentials credentials) {
//this is custom method that you will write in auth handler
return StringUtils.isNotBlank(authHandler.getUserId(credentials));
}

/**
* Returns an authentication plugin which validates the authentication data
* contained as an attribute in the credentials object. The
* <code>authenticate</code> method returns <code>true</code> only if
* authentication data is contained in the credentials (expected because this
* method should only be called if {@link #canHandle(Credentials)} returns
* <code>true</code>) and the authentication data is valid.
*/
public AuthenticationPlugin getAuthentication(final Principal principal, final Credentials creds) {
return new AuthenticationPlugin() {
public boolean authenticate(Credentials credentials) throws RepositoryException {
//You will be implementing this in your auth handler
return StringUtils.isNotBlank(authHandler.getUserId(credentials));
}

};

}

/**
* Returns <code>null</code> to have the <code>DefaultLoginModule</code>
* provide a principal based on an existing user defined in the repository.
*/
public Principal getPrincipal(final Credentials credentials) {
return null;
}

/**
* This implementation does nothing.
*/
@SuppressWarnings("unchecked")
public void addPrincipals(final Set principals) {
}

/**
* Returns <code>LoginModulePlugin.IMPERSONATION_DEFAULT</code> to indicate
* that this plugin does not itself handle impersonation requests.
*/
public int impersonate(final Principal principal, final Credentials credentials) {
return LoginModulePlugin.IMPERSONATION_DEFAULT;
}

}


Step2 : Plug it in your custom auth handler

//Import
import org.osgi.framework.ServiceRegistration;
import org.apache.sling.auth.core.spi.AuthenticationHandler;
import org.apache.sling.auth.core.spi.DefaultAuthenticationFeedbackHandler;

//Your Custom Auth handler class
public class CustomAuthHandler extends DefaultAuthenticationFeedbackHandler implements AuthenticationHandler {
//All Your Logic
private ServiceRegistration loginModule;
//All Your Logic
@Activate
protected void activate(ComponentContext componentContext) {
context = componentContext;
this.loginModule = null;
try {
this.loginModule = CustomPluggableLoginModule.register(this, componentContext.getBundleContext());
} catch (Throwable t) {
//Error handling
}

}

@Deactivate
protected void deactivate(@SuppressWarnings("unused") ComponentContext componentContext) {
if (loginModule != null) {
loginModule.unregister();
loginModule = null;
}
}
}


Example: https://svn.apache.org/repos/asf/sling/trunk/bundles/auth/form/src/main/java/org/apache/sling/auth/form/impl/

Example of Open Source Extended Authentication Handler:
http://sling.apache.org/site/openid-authenticationhandler.html
http://sling.apache.org/site/form-based-authenticationhandler.html

CQ OOTB Extended authentication Handler

Day CRX Sling - Token Authenticationcom.day.crx.sling.crx-auth-token)Adobe Granite SSO Authentication Handlercom.adobe.granite.auth.sso)Day Communique 5 PIN Authentication Handlercom.day.cq.cq-pinauthhandler

There are a lot of things needed for creating your custom user registration process (You might / Might not) Need following,

1) Custom Login Module http://www.wemblog.com/2012/06/how-to-add-custom-login-module-in-cq55.html to sync users/group in CQ from the third party system
2) Custom Authentication handler as above
3) Reverse replication to sync user across (If user registration is in publish)


By aem4beginner

No comments:

Post a Comment

If you have any doubts or questions, please let us know.