External password authentication

From OpenCms Wiki
(Difference between revisions)
Jump to: navigation, search
(The solution)
 
(3 intermediate revisions by one user not shown)
Line 5: Line 5:
 
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.  
 
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:
+
However, I have investigated how the login process goes. A user logs in on the '''/system/login/index.html''' JSP page, which has the following source:
  
 
  <%@ page import="org.opencms.workplace.*" %>
 
  <%@ page import="org.opencms.workplace.*" %>
Line 24: Line 24:
 
so you cannot extend them.
 
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:
+
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
 
  # from opencms.properties
Line 33: Line 33:
 
=== The solution ===
 
=== 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.
+
The org.opencms.db.mysql.CmsUserDriver extends the class org.opencms.db.generic.CmsUserDriver and adds nothing to it. So my solution is to create a new implementation of CmsUserDriver which extends org.opencms.db.generic.CmsUserDriver and overrides its readUser() method.
  
Here is an example:
+
Here is the code :
  
<source lang="java">
+
<source lang="java">
 
package cz.cesnet.meta.opencms;
 
package cz.cesnet.meta.opencms;
  
Line 79: Line 79:
 
     public CmsUser readUser(CmsDbContext dbc, String userFqn, String password, String remoteAddress)  
 
     public CmsUser readUser(CmsDbContext dbc, String userFqn, String password, String remoteAddress)  
 
                                       throws CmsDataAccessException, CmsPasswordEncryptionException {
 
                                       throws CmsDataAccessException, CmsPasswordEncryptionException {
         if (LOG.isInfoEnabled()) LOG.debug("readUser(" + userFqn + ")");
+
         if (LOG.isDebugEnabled()) LOG.debug("readUser(" + userFqn + ")");
 
         CmsUser user = super.readUser(dbc, userFqn);
 
         CmsUser user = super.readUser(dbc, userFqn);
 
         if (user != null) {
 
         if (user != null) {
 
             try {
 
             try {
                 userPasswordCheck.check(userFqn, password);
+
                 userPasswordCheck.check(user, password);
 
             } catch (Exception ex) {
 
             } catch (Exception ex) {
 
                 CmsMessageContainer message = Messages.get().container(Messages.ERR_UNKNOWN_USER_1,userFqn);
 
                 CmsMessageContainer message = Messages.get().container(Messages.ERR_UNKNOWN_USER_1,userFqn);
Line 100: Line 100:
 
  db.user.passwordcheck=cz.cesnet.meta.opencms.LdapPasswordCheck
 
  db.user.passwordcheck=cz.cesnet.meta.opencms.LdapPasswordCheck
 
  db.user.ldap.url=ldap://some.ldap.machine/dc=foo,dc=bar
 
  db.user.ldap.url=ldap://some.ldap.machine/dc=foo,dc=bar
 +
 +
The UserPasswordCheck interface is here:
 +
<source lang="java">
 +
package cz.cesnet.meta.opencms;
 +
 +
import java.util.Map;
 +
 +
/**
 +
* Interface for external password check.
 +
*
 +
* @author Martin Kuba makub@ics.muni.cz
 +
*/
 +
public interface UserPasswordCheck {
 +
 +
    /**
 +
    * Initializes from values in opencms.properties.
 +
    * @param config map of values from opencms.properties
 +
    */
 +
    void init(Map<String,Object> config);
 +
 +
    /**
 +
    * Checks password. Throws an exception if unsuccessful.
 +
    *
 +
    * @param user    cms user object
 +
    * @param password password
 +
    * @throws Exception when unsucessful
 +
    */
 +
    void check(CmsUser user, String password) throws Exception;
 +
 +
}
 +
</source>
 +
 +
You can implement anything inside the check() method, like authentication against a LDAP server, or calling a Kerberos KDC center etc.

Latest revision as of 15:28, 9 March 2010

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 on 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 create a new implementation of CmsUserDriver which extends org.opencms.db.generic.CmsUserDriver and overrides its readUser() method.

Here is the code :

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.isDebugEnabled()) LOG.debug("readUser(" + userFqn + ")");
        CmsUser user = super.readUser(dbc, userFqn);
        if (user != null) {
            try {
                userPasswordCheck.check(user, 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

The UserPasswordCheck interface is here:

package cz.cesnet.meta.opencms;
 
import java.util.Map;
 
/**
 * Interface for external password check.
 *
 * @author Martin Kuba makub@ics.muni.cz
 */
public interface UserPasswordCheck {
 
    /**
     * Initializes from values in opencms.properties.
     * @param config map of values from opencms.properties
     */
    void init(Map<String,Object> config);
 
    /**
     * Checks password. Throws an exception if unsuccessful.
     *
     * @param user     cms user object
     * @param password password
     * @throws Exception when unsucessful
     */
    void check(CmsUser user, String password) throws Exception;
 
}

You can implement anything inside the check() method, like authentication against a LDAP server, or calling a Kerberos KDC center etc.

Personal tools
Namespaces
Variants
Actions
Navigation
Toolbox