Use Case:
Integrate it with Custom Pluggable Login Module (AEM 6)
Step1 : create pluggable login Module
Step2 : Plug it in your custom auth handler
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:
- You want to use custom Authentication handler instead of OOTB one for authentication.
- Custom User Registration
- 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:
1) Create custom class extending Sling Authentication Handler and override available methods
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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:
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 third party system
2) Custom Authentication handler as above
3) Reverse replication to sync user across (If user registration is in publish)
Note: Above code is just Pseudo code. Please test, You might have to add your custom logic for this to work.
Let me know if you have any question or comment.
Hi Yogesh,
ReplyDeleteI really like this article and this blog, so thank you for putting this information together and taking the time to maintain and update this blog. I just wanted to call out one point I noticed:
"And more ..... If you can decompile these bundle, You should be able to see example of how it is implemented."
If a client decompiles an OOTB proprietary jar, then technically they're in violation of their license agreement and could lose support and/or license. As such, I would really not suggest decompiling the jars, even if they are good examples.
That being said, again I really do like this blog and appreciate the effort.
Doug,
DeleteThank you very much for your comment. When I said decompiled, I meant Open source (Sling) authentication handler (Including form based). But I can see confusion here. Thus deleted that portion.
Again thanks for review.
Yogesh
Hi Yogesh,
ReplyDeleteThis post is what I was looking for. I am bit confused about some of the parameters and methods you have defined in the code though. Constant ATTR_HOST_NAME_FROM_REQUEST is not defined. Is it same to assume it as "hostname"?
Regards,
Anup
Anup,
DeleteYou do not need that variable. This is just an example of how you can set custom attribute in credential object
Yogesh
Can we have a sample package to get into the details?
ReplyDeleteHello,
DeleteSample Package is difficult in this case because each custom authentication requirement is different. Let me know if you have any specific question.
Yogesh
Hello Yogesh,
ReplyDeleteGreat article. If I were to create a Custom authentication module, which uses a simple CSV file for User name and Password, can I still use the OOTB login page ? I dont want users to Register and I prefer the CSV approach as it is just the list of users who I want to control access for a very short period of time.
Silican,
DeleteYou do not need custom authentication handler to create user and group. You can use Java API to parse CSV and then Jackrabbit API to create user. Here is example of creating user and group in CQ http://wemcode.wemblog.com/user-group-management. That site also have example of how you can create Role.
Yogesh
Hi Yogesh,
ReplyDeleteThank you for the above tips. I have implemented something similar in order to do some additional checks on the user who is authenticating (e.g. check their account has not expired).
Do you know how I might then call the default AuthenticationHandler from my extractCredentials? In other words, revert to the normal "j_security_check" ? I am trying to call the default handler's extractCredentials() and return the AuthenticationInfo object if my own handler is satisfied with it's additional checks. Any ideas?
John,
DeleteYou can just return null from custom authentication handler and then next authentication handler will pick up.
Yogesh
Thanks Yogesh, I guess what I am asking is after I have authenticated in my custom AuthenticationHandler and return AuthenticationInfo, how do I then get CQ to create the logged in session cookie? When I return the AuthenticationInfo , it fires the authenticationSucceeded, but there is no session cookie created.
DeleteI ended up using com.day.crx.security.token.TokenUtil.createCredentials() in my authenticationSucceeded() to create the token, just in case anybody is trying to do something similar.
DeleteThank you John Sharing this. This will work if Order of Token Auth Handler comes after your custom Auth handler.
DeleteYogesh
Thanks Yogesh,
ReplyDeleteif I return null from my custom handler, wouldn't this mean that my custom conditions are not included? I want to include my conditions PLUS the default conditions handled by j_security_ check..
I have question related to same custom login approach that you have suggested. Say I want to authenticate user against DataBase, Say I do that in authenticationSucceeded and it returns true for the first time. So the Question is how do you maintain a session then? like how would you ensure access to your secure area is being authenticated against logged in user each time.
ReplyDeleteApologies but I am little unaware of the login token purpose. Also I dont want to maintain users in CQ Repository what to do then, how their access to repository pages will be handled?
I have question related to Auto login in CQ5.6. I have created registration form for registring new user in my site. I am using a custom post servlet for creating users.
ReplyDeleteI want my users to get logged in once they have completed registration process.
How can we make user logged in automatically once registration process is completed?
Ankur,
DeleteTry something like
TokenUtil.createCredentials(request, response, slingRepository, userID, true) in your servlet service method.
Maven dependency for TokenUtil:
com.day.crx.sling
crx-auth-token
2.4.23
provided
Also this could be useful for your use case:
http://www.cqcon.eu/content/dam/cqcon/Pr%C3%A4sentation_Antonio_Sanso.pdf
Please confrim that this works for you.
I tried to follow the above steps to implement the Custom Authentication handler but was stuck at some point.
ReplyDelete1. When I implemented using the component properies "@Property(name = AuthenticationHandler.PATH_PROPERTY, value = "/")," I was not able to reach to the Custom Authentication Handler. But when I used "@Property(name = AuthenticationHandler.PATH_PROPERTY, value = "/content")" . What is the correct way to do so?
2. Even if I reach the authenticationSucceeded() function I am obtaining a 403 Forbidden from the "j_mycustom_security_check" handler. Has anyone faced similar problem?
3. What does the value "static final String AUTH_TYPE" symbolizes.
Thanks for helping in advance.
Just to add I am using CQ5.6.1 and version of jar for org.apache.sling.auth.core is 1.1.6
Delete@HK, did you get any resolution to 403 error? I am facing the same issue
DeleteHi Yogesh,
ReplyDeleteI am trying to authenticate from open am , In request cookie value is available once user get authenticate from open am , We provide the filter in dispatcher like /content/mysite*. How we can aunthenticate in cq5. When we access from openam the dispatcher url it show us a prompt "Your request could not be completed becauseyou have signed out".Please let me know how to go around
Hello Raj,
DeleteUsually you get that error when there is some exception in login process. Can you check your error log and make sure that nothing is wrong ?
Yogesh
Hi Yogesh,
ReplyDeleteI am working out with LDAP Authentication, I created a login component which sends a request to your_path/j_security_check. with username and password and the user is authenticated against the LdapLoginModule i believe which is good.
Now, if the user is authenticated i want to check whether they are authorized to use the system or not. There is a property on user profile node with which we check that.
The problem is once i make a request to path/j_security_check it automatically logs in the user to the system. I wan to avoid that before i know if the user is authorized to use the system or not.
Can you please let me know how can i just override the authorization mechanism.
Just in case somebody is interested, i have got this working using form Authentication handler http://svn.apache.org/repos/asf/sling/trunk/bundles/auth/form/src/main/java/org/apache/sling/auth/form/impl/FormAuthenticationHandler.java
DeleteI almost ended up using most parts of this handler as i wanted to avoid writing any security code because it might just have flaws.
Since i kept my handler service ranking to be 0 and Token Authentication handler rank was higher, i ended up creating com.day.crx.security.token.TokenUtil.createCredentials() in the authenticationSucceeded method. If you happen to have custom Authentications before you login the user you can do it in authenticationSucceeded method as well.
Note in case your form has got a resource for redirection in form of an input hidden field i saw a weird behavior where even with valid creds i was seeing INVALID_CREDNETIALS error but as soon i put a call to TokenAuth the login redirection started working.
Sam,
DeleteThank a lot of sharing your problem and solution in Blog. Really appreciated.
Yogesh
Hi Yogesh,
ReplyDeleteI am working with LDAP Authentication, Siteminder authenticates the user with credentials from LDAP and sends user data to response.
So I want to store that user data in session bean so that I can add more details specific to that user in that bean.
For it I created a user bean in my top header component jsp which is in every page.
And I am trying to make the page "session=true" it sends "500: Internal Server error".
Can you please help
You can simply use request.getRemoteuser() right ? Not sure why you need to store this in session. Note that session is not scalable in case you are planning to use caching in future.
DeleteHi Yogesh,
ReplyDeleteI built my authentication handler by following your instructions and the sample code, https://github.com/davidjgonzalez/com.activecq.samples/blob/master/core/src/main/java/com/activecq/samples/slingauthenticationhandler/impl/TokenSlingAuthenticationHandler.java.
in the method "authenticationSucceeded", I call "return false" to continue the framework. however, I see an error is reported in the error.log: org.apache.sling.auth.core.impl.SlingAuthenticator handleSecurity: AuthenticationHandler did not block request; access denied
did I miss any configurations?
could you please help?
Hi, I have imported org.apache.sling.auth.core.AuthUtil in the java class. But this is showing an error.
ReplyDeleteKindly help.
This comment has been removed by the author.
DeleteIn addition to the above comment
ReplyDeletehttp://sling.apache.org/apidocs/sling5/org/apache/sling/jcr/resource/JcrResourceConstants.html package dos't contains "JcrResourceConstants.AUTHENTICATION_INFO_CREDENTIALS"
Hello Renju,
DeleteThanks for your Feedback. I updated document, also added how you can create your pluggable login module.
Yogesh
is it possible to call the j_security_check from my own servlet? instead of from action
ReplyDeleteHello Fabian,
DeleteWhat is the use case for this ? You might be able to achieve your use case using some other method.
Yogesh
Hi Yogesh,
ReplyDeleteI tried to follow the above steps to implement the Custom Authentication handler in CQ5.5 but was stuck at some point. I did configuration mentioned in above but Custom Authentication handler is not getting invoke. I got below warning:
“org.apache.sling.auth.core.impl.SlingAuthenticator handleSecurity: AuthenticationHandler did not block request; access denied”.
Can u help, will it require any other configuration and how to resolve it.
Same here (AEM 5.6.1)!
DeleteHi Yogesh,
ReplyDeleteGreat article. I have one question here - Do we not need to create a login-tokrn cookie in the authenticationSucceeded method before sending the response as true? Otherwise how would the further requests be authenticated?
Hello Karttik,
DeleteNot in this case. You can have such mechanism to avoid call to server (Chose any cookie you want). In this case all request will go through authentication handler.
Yogesh