Software Configuration: CQ5.5 service pack 2.1 (cq-service-pack-5.5.2.20121012.zip) Windows XP machine
I am trying to integrate our internal SSO API to enable SSO with LDAP on CQ5.5 service pack 2.1.
SSO and LDAP is configured as per the below Adobe CQ5.5 links.
SSO - http://dev.day.com/docs/en/cq/5-5/deploying/configuring_cq.html#Single %20Sign%20On
LDAP - http://dev.day.com/docs/en/cq/5-5/deploying/configuring_ldap.html
As part of the SSO implementation I am passing a validated userId to Day CQ SSO Authentication Handler inside a Sling Filter by setting a HTTP Header with the help of decorator pattern or Fake Http request.
Please find below repository.xml, ldap_login.conf, start.bat and bestssoheaderfilter-1.0-SNAPSHOT.jar bundle source files with this discussion.
BestSlingHeaderFilter.java, FakeHeadersRequest.java
I can see in the error.log file that the request Header name / value has been passed by the Sling Filter chain correctly but still Day CQ SSO Authentication Handler is unable to extract credentials and failed back to login as annonymous
I am using Decorator pattern to create fake http request in order to add Http Header to the original request. It looks like the Sling Filter inside OSGI bundle is able to add the Http Header with the username value but Day CQ SSO Authentication Handler is still complaining and failing back to anonymous user.
repository.xml
ldap_login.conf
start.bat
Day CQ SSO Authentication Handler Configuration
CQ Authenicator
bestssoheaderfilter-1.0-SNAPSHOT.jar - source code files
BestSlingHeaderFilter.java as Sling Filter
package com.sso.header.filter.osgi;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.sling.SlingFilter;
import org.apache.felix.scr.annotations.sling.SlingFilterScope;
import org.apache.sling.api.SlingHttpServletResponse;
import org.slf4j.Logger; import org.slf4j.LoggerFactory;
import org.apache.sling.api.SlingHttpServletRequest;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Enumeration;
@SlingFilter( label = "Header Sling Filter",
description = "Implementation of a Sling Filter for setting HTTP Header",
metatype = true,
generateComponent = true, // True if you want to leverage activate/deactivate
generateService = true,
order = -2000,
scope = SlingFilterScope.REQUEST) // REQUEST, INCLUDE, FORWARD, ERROR, COMPONENT (REQUEST, INCLUDE, COMPONENT)
@Property(name = "_usernameHeader", value = "SM_USER")
public class BestSlingHeaderFilter implements javax.servlet.Filter {
private static final Logger logger = LoggerFactory.getLogger(BestSlingHeaderFilter.class);
private static String _usernameHeader;
public void destroy() { }
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if (servletRequest instanceof SlingHttpServletRequest && servletResponse instanceof SlingHttpServletResponse) {
//cast the object
SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) servletRequest;
SlingHttpServletResponse slingResponse = (SlingHttpServletResponse) servletResponse;
FakeHeadersRequest localFakeHeadersRequest = new FakeHeadersRequest(slingRequest);
localFakeHeadersRequest.setHeader("SM_USER", "abc");
localFakeHeadersRequest.setHeader("SM_AUTHENTIC", "YES");
localFakeHeadersRequest.setHeader("SM_AUTHORIZED", "YES");
logger.debug("sso request username " + localFakeHeadersRequest.getHeader("SM_USER"));
Enumeration headerNames = localFakeHeadersRequest.getHeaderNames();
while(headerNames.hasMoreElements()) {
String hn = headerNames.nextElement();
String value = localFakeHeadersRequest.getHeader(hn);
logger.debug("header names and values " + hn + " " + value);
}
//otherwise, continue on in the chain with the ServletRequest and ServletResponse objects
filterChain.doFilter(localFakeHeadersRequest, slingResponse);
}
}
public void init(FilterConfig paramFilterConfig) throws ServletException {
_usernameHeader = paramFilterConfig.getInitParameter("usernameHeader");
if ((_usernameHeader == null) || ("".equals(_usernameHeader)))
_usernameHeader = "SM_USER";
}
}
FakeHeadersRequest.java
package com.sso.header.filter.osgi;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.wrappers.SlingHttpServletRequestWrapper;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.*;
public class FakeHeadersRequest extends SlingHttpServletRequestWrapper {
private final Map _fakeHeaders;
/** * @param request SlingHttpServletRequest. */
public FakeHeadersRequest(SlingHttpServletRequest request) {
super(request);
_fakeHeaders = request.getSession().getAttribute("_fakeHeaders")==null ? new HashMap() : (Map) request.getSession().getAttribute("_fakeHeaders");
request.getSession().setAttribute("_fakeHeaders", _fakeHeaders);
}
@Override
public String getHeader(String name) {
//get the request object and cast it
final SlingHttpServletRequest request = (SlingHttpServletRequest)getRequest();
return _fakeHeaders.containsKey(name) ? _fakeHeaders.get(name) : request.getHeader(name);
}
public void setHeader(String name, String value){
_fakeHeaders.put(name, value);
}
@Override
public Enumeration getHeaderNames() {
final SlingHttpServletRequest request = (SlingHttpServletRequest)getRequest();
final Set list = new HashSet(); //loop over request headers from wrapped request object
final Enumeration e = request.getHeaderNames();
while(e.hasMoreElements()) {
list.add(e.nextElement());
}
list.addAll(_fakeHeaders.keySet());
//create an enumeration from the list and return
final Enumeration en = Collections.enumeration(list);
return en;
}
}
How SSO process works:
Step 1) HTTP request with kerberos authentication is validated with internal system and a valid userId is set as the request principal name. The internal kerberos validation is configured as a Sling filter and uploaded as OSGI bundle on CQ5.5
Step 2) A Fake HTTP request (based on Decorator pattern) is created to set existing HTTP request with HTTP Header name as SM_USER and value as the above validated userId. This is achieved by configuring Sling filter and uploaded as OSGI bundle on CQ5.5
Step 3) CQ5.5 SSO Authnetication Handler bundle is configured to extract valid userId from the Http request Header Name as SM_USER.
Issue: SSO Authentication Handler is unable to extract userId from the HTTP request header name SM_USER due to which SSO is failing and CQ5.5 redirects to login page. The CQ5.5 login page flashes and I can see below message in the CQ5.5
error.log file:
25.03.2013 06:19:45.647 *DEBUG* [127.0.0.1 [1364206785647] GET /favicon.ico HTTP/1.1] com.day.cq.auth.impl.SsoAuthenticationHandler forceAuthentication: Not forcing authentication because request parameter sling:authRequestLogin is not set 25.03.2013 06:19:45.647
*DEBUG* [127.0.0.1 [1364206785647] GET /favicon.ico HTTP/1.1] org.apache.sling.auth.core.impl.HttpBasicAuthenticationHandler forceAuthentication: Not forcing authentication because request parameter sling:authRequestLogin is not set 25.03.2013 06:19:45.647
*DEBUG* [127.0.0.1 [1364206785647] GET /favicon.ico HTTP/1.1] org.apache.sling.auth.core.impl.SlingAuthenticator getAuthenticationInfo: no handler could extract credentials; assuming anonymous 25.03.2013 06:19:45.647
*DEBUG* [127.0.0.1 [1364206785647] GET /favicon.ico HTTP/1.1] org.apache.sling.auth.core.impl.SlingAuthenticator doHandleSecurity: No credentials in the request, anonymous 25.03.2013 06:19:45.647
*INFO* [127.0.0.1 [1364206785647] GET /favicon.ico HTTP/1.1] org.apache.sling.auth.core.impl.SlingAuthenticator getAnonymousResolver: Anonymous access not allowed by configuration - requesting credentials 25.03.2013 06:19:45.647
*DEBUG* [127.0.0.1 [1364206785647] GET /favicon.ico HTTP/1.1] org.apache.sling.auth.core.impl.SlingAuthenticator login: requesting authentication using handler: com.day.cq.auth.impl.LoginSelectorHandler@1ebe57
How to reproduce
Step 1) Create a OSGI bundle from the source code (BestSlingHeaderFilter.java, FakeHeadersRequest.java) and deploy as OSGI bundle to CQ5.5 service pack 2.1. The source file has been configured with Http request Header Name SM_USER with hard coded value as abc.
Step 2) Configure CQ5.5 SSO Authentication Handler bundle to extract userId by setting HTTP Header Name as SM_USER.
Step 3) Create a user name with "abc" on CQ5.5 to skip LDAP configuration
Step 4) Try to login to CQ5.5 by hitting http://localhost:7402.
Step 5) UserId "abc" should see CQ5.5 Welcome screen and should not be prompted with login screen to input userId manually.
Step 6) Chec the error.log and search for "SM_USER" or SSO Authentication Handler and you should not see any message as "No credentials in the request, anonymous".