External password authentication

From OpenCms Wiki
Revision as of 10:08, 9 March 2010 by 147.251.3.64 (Talk)
Jump to: navigation, search

OpenCms stores user accounts including their passwords in its database. However, sometimes you need to use password from some external source, like LDAP server or Kerberos.

I have not found any documentation how to do it, so here are my findings. This is for OpenCMS 7.5.2.

The LDAP modules listed on this wiki are not usable. None of them provides source code. The Langhua one is for OpenCMS 7.0 and is available only in binary form.

However, I have investigated how the login process goes. A user logs in o n the /system/login/index.html JSP page, which has the following source:

<%@ page import="org.opencms.workplace.*" %>
<% CmsLogin wp = new CmsLogin(pageContext, request, response); %>
<%= wp.displayDialog() %>

Then the chain of calls is (a stacktrace in reverse order):

       at org.opencms.db.CmsDriverManager.loginUser(CmsDriverManager.java:4762)
       at org.opencms.db.CmsSecurityManager.loginUser(CmsSecurityManager.java:2883)
       at org.opencms.file.CmsObject.loginUser(CmsObject.java:2263)
       at org.opencms.jsp.CmsJspLoginBean.login(CmsJspLoginBean.java:189)
       at org.opencms.jsp.CmsJspLoginBean.login(CmsJspLoginBean.java:169)
       at org.opencms.workplace.CmsLogin.displayDialog(CmsLogin.java:293)
       at org.apache.jsp.WEB_002dINF.jsp.online.system.login.index_html_jsp._jspService(index_html_jsp.java:59)

Unfortunately, none of this places provides any hook or configuration. The CmsDriverManager and CmsSecurityManager classes are even final, so you cannot extend them.

However, the CmsDriverManager.loginUser()' method calls method readUser() on an implementation of CmsUserDriver, and the implementation is configured in the opencms.properties file dependent on database, so in the default MySQL case it is:

# from opencms.properties
db.user.driver=org.opencms.db.mysql.CmsUserDriver

Here is the only place (that I have found) where you can put your code.

The solution

The org.opencms.db.mysql.CmsUserDriver extends the class org.opencms.db.generic.CmsUserDriver and adds nothing to it. So my solution is to creta a new implementation of of db.user.driver which extends org.opencms.db.generic.CmsUserDriver and overrides its readUser() method.

Here is an example:

package cz.cesnet.meta.opencms;
 
import org.apache.commons.logging.Log;
import org.opencms.configuration.CmsConfigurationManager;
import org.opencms.db.CmsDbContext;
import org.opencms.db.CmsDbEntryNotFoundException;
import org.opencms.db.CmsDriverManager;
import org.opencms.db.Messages;
import org.opencms.db.generic.CmsUserDriver;
import org.opencms.file.CmsDataAccessException;
import org.opencms.file.CmsUser;
import org.opencms.i18n.CmsMessageContainer;
import org.opencms.main.CmsLog;
import org.opencms.security.CmsPasswordEncryptionException;
public class ExternalAuthenticationCmsUserDriver extends CmsUserDriver {
 
    private static final Log LOG = CmsLog.getLog(ExternalAuthenticationCmsUserDriver.class);
 
    UserPasswordCheck userPasswordCheck;
 
    @SuppressWarnings({"unchecked"})
    @Override
    public void init(CmsDbContext dbc, CmsConfigurationManager configurationManager, List successiveDrivers, CmsDriverManager driverManager) {
        super.init(dbc, configurationManager, successiveDrivers, driverManager);
        Map config = configurationManager.getConfiguration();
 
        String classname = (String) config.get("db.user.passwordcheck");
        if (classname == null) classname = NoPasswordCheck.class.getName();
        try {
            Class aClass = Class.forName(classname);
            userPasswordCheck = (UserPasswordCheck) aClass.newInstance();
            userPasswordCheck.init(config);
        } catch (Exception e) {
            LOG.error("cannot load " + classname, e);
        }
        LOG.info("ExternalAuthenticationCmsUserDriver initialized");
    }
 
    @Override
    public CmsUser readUser(CmsDbContext dbc, String userFqn, String password, String remoteAddress) 
                                       throws CmsDataAccessException, CmsPasswordEncryptionException {
        if (LOG.isInfoEnabled()) LOG.debug("readUser(" + userFqn + ")");
        CmsUser user = super.readUser(dbc, userFqn);
        if (user != null) {
            try {
                userPasswordCheck.check(userFqn, password);
            } catch (Exception ex) {
                CmsMessageContainer message = Messages.get().container(Messages.ERR_UNKNOWN_USER_1,userFqn);
                throw new CmsDbEntryNotFoundException(message);
            }
        }
        return user;
    }
}

The opencms.properties file has properties setting the new user driver, the implementation of my UserPasswordCheck interface and some properties for config information for the implementation:

db.user.driver=cz.cesnet.meta.opencms.ExternalAuthenticationCmsUserDriver
db.user.passwordcheck=cz.cesnet.meta.opencms.LdapPasswordCheck
db.user.ldap.url=ldap://some.ldap.machine/dc=foo,dc=bar
Personal tools
Namespaces
Variants
Actions
Navigation
Toolbox