HTTP header based auto login in Liferay

17.09.2012.

Liferay includes many extension points which enable vendors to extend built-in functionality and integrate it into the existing infrastructure. One of those extension points is the auto login hook mechanism. Auto login hooks enable Liferay to integrate with front end security appliances that perform SSO actions (such as authentication, authorization, security policy management etc.) across the multiple web services in the enterprise.

Web security appliances act as reverse proxies that protect the backend services and perform transparent authentication and authorization workflows without involving the backend web application implementation. They typically support insertion of custom HTTP headers that relate the information about the user profiles. Such information can be easily used by web applications without a need to use additional APIs or libraries for the integration. This is very simple, yet powerful, mechanism that can be used for information exchange between reverse proxy and the backend service.

The simplest of way of authentication is that the front-end security appliance inserts authentication header (such as login name) that will be available on every HTTP request. To integrate Liferay with SSO, a simple auto login hook needs to be implemented to support HTTP header based authentication. The inserted value act as a unique key to find all needed information in the user identity store.

HTTPHeaderAutoLogin defines four custom properties:

  • http.header.auth.enabled – enables or disables HTTP header auto login hook
  • http.header.auth.login.header – HTTP header to use as user login
  • http.header.auth.no.such.user.redirect.url – user will be redirected to this URL if HTTP header is not set or no such user exists
  • http.header.auth.import.from.ldap – determines if authenticated user is going to be imported from LDAP.
public class HTTPHeaderAutoLogin implements AutoLogin {
...
public String[] login(HttpServletRequest request, HttpServletResponse response) {
HttpSession session = request.getSession();
 
String[] credentials = null;
 
try {
long companyId = PortalUtil.getCompanyId(request);

First, we test if HTTP header auto login is enabled:

if (!PrefsPropsUtil.getBoolean(
companyId, HTTP_HEADER_AUTH_ENABLED,
HTTP_HEADER_AUTH_ENABLED_VALUE)) {
 
return credentials;
}

Extract login name from request headers, using user defined header name. If there is no login header, redirect user to the URL defined in portal properties:

String login = (String) request.getHeader(PrefsPropsUtil.getString(companyId,
HTTP_HEADER_AUTH_LOGIN_HEADER, HTTP_HEADER_AUTH_LOGIN_HEADER_VALUE));
 
String redirectHTTPURL = PrefsPropsUtil.getString(
companyId, HTTP_HEADER_AUTH_NO_SUCH_USER_REDIRECT_URL,
HTTP_HEADER_AUTH_NO_SUCH_USER_REDIRECT_URL_VALUE);
 
if (Validator.isNull(login)) {
request.setAttribute(AutoLogin.AUTO_LOGIN_REDIRECT, redirectHTTPURL);
return credentials;
}

Import user from LDAP, if LDAP import is enabled in portal properties:

User user = null;
 
if (PrefsPropsUtil.getBoolean(
companyId, HTTP_HEADER_AUTH_IMPORT_FROM_LDAP,
HTTP_HEADER_AUTH_IMPORT_FROM_LDAP_VALUE)) {
_log.debug("Trying to import user from ldap:" + login);
//assume users will be authenticated by screenname
try {
user = PortalLDAPImporterUtil.importLDAPUserByScreenName(companyId, login);
_log.debug("User imported/updated from ldap:" + login);
} catch (Exception e) {
_log.error("Failed to import user from ldap:" + login);
} 
}else{
_log.debug("Http import ldap disabled");
}

If user does not exist in LDAP, try to get it from Liferay user repository:

if (user == null){
try{
_log.debug("User does not exist in ldap, fetching from Liferay:" + login);  
user = UserLocalServiceUtil.getUserByScreenName(companyId, login);
}catch (NoSuchUserException e) {
_log.debug("No user found from Liferay db:" + login);  
request.setAttribute(AutoLogin.AUTO_LOGIN_REDIRECT, redirectHTTPURL);
return credentials;
}catch (Exception e){
_log.debug("Failed to import user from Liferay:" + login);  
return credentials;
}
}
 
String redirect = ParamUtil.getString(request, "redirect");
 
if (Validator.isNotNull(redirect)) {
request.setAttribute(AutoLogin.AUTO_LOGIN_REDIRECT, redirect);
}

At the end, prepare credentials information which will be returned:

credentials = new String[3];
 
credentials[0] = String.valueOf(user.getUserId());
credentials[1] = user.getPassword();
credentials[2] = Boolean.TRUE.toString();
 
return credentials;
}
catch (Exception e) {
_log.error(e, e);
}
 
return credentials;
}
}

To enable HTTP header auto login hook, it’s classname should be added to Liferay’s “auto.login.hooks” property.

When implementing HTTP header based authentication, there are important security considerations that administrator needs to address. FIrst, direct access to the backend web application should be disabled, because it would enable an attacker to insert its own header and impersonate any user on the backend system (for example, using Modify Headers plugin). Only trusted IP addresses (such as security appliances) should be allowed to access the backend service. The can be also implemented in the hook, but it is better to use local firewall (such as iptables) to implement such constraint. In some cases, auto.login.ignore.hosts property can also be used to block the autologin from certain locations.

A second precondition is to configure the front-end security appliance to block sensitive header insertion attacks, so that the general user cannot try the same trick when connecting through the security appliance.