Url Link

JCMS 5: Developing specific authentications with AuthenticationManager

In brief...
This article explains how to set up a specific authentication (SSO, J2EE, NTLM, PKI, etc.) in JCMS 5.0 with the AuthenticationManager class.
Subject Jalios API
Designer How-To
Products JCMS 5.0
JCMS 5.5
JCMS 5.6
Published 1/19/05
Writer Olivier Jaquemet

1. Introduction

JCMS 5 is delivered with two authentication mechanisms:

  1. Authentication by the base of JCMS members,
  2. Authentication by a LDAP/LDAPS directory.

LDAP directories are becoming an integral part of enterprise information systems. JCMS 5 supports all the most popular LDAP directories, and LDAP authentication is sufficient for most enterprises. Nevertheless, some architectures necessitate special authentication mechanisms, for example:

  1. A user base managed in a database,
  2. A user base managed in several unsynchronized LDAP directories,
  3. Single Sign-On,
  4. Certificate-based authentication,
  5. Strong authentication (chipcard, biometric system, etc.)

The JCMS 5.0 API enables new authentication mechanisms adapted to any target architecture to be developed and integrated very easily.

2.The Authentication Manager

In JCMS 5.0, users are authenticated via the Authentication Manager; by default, JCMS uses the com.jalios.jcms.AuthenticationManager class. If the LDAP configuration is enabled, the custom.LdapAuthenticationManager class is used.

To develop a new authentication manager, the authentication mechanism must be implemented in a class derived from AuthenticationManager. Next, it is declared as the authentication manager in webapp.prop using the hook.auth-mgr.class property. This new manager will then be used, even if the LDAP configuration is enabled.

There are four ways of invoking the new manager; all or some of these should be implemented depending on the required authentication mechanism:

  • public Member login(HttpServletRequest request, HttpServletResponse response,
    String lang, String login, String password, boolean persistent)
    This method is called by doInitMember.jsp during the submission of a JCMS authentication form (login.jsp, privateLogin.jsp, doPortletLoginFullDisplay.jsp).
    If an external user base (LDAP for example) is used, it is in this method that member authentication and the synchronization with JCMS (cf. custom.LdapAuthenticationManager).

    In an SSO mechanism in which the authentication has been carried out previously (for example by the application server), this method must return a null value and the authentication will be performed using the following method.
  • public Member checkAuthentication(HttpServletRequest request, HttpServletResponse response)
    This method is called for each request by doInitMember.jsp. By default, it will recover, via a cookie, the authentication carried out previously in the login() method.

    It is typically in this method that is implemented a Single Sign-On (SSO) mechanism. Except in this case, this method should generally not be overridden.

    If this method must be overridden, if you do not call the super.checkAuthentication(...), method, you deny JCMS the possibility of authenticating a member via the parameters silentLogin and silentPassword, and via cookies. You can however use the methods checkAuthenticationFromSilentParams() and/or checkAuthenticationFromCookie() present in the AuthenticationManager class if you need to conserve certain functionalities.
  • public void logout(HttpServletRequest request, HttpServletResponse response)
    This method is called by logout.jsp and is therefore triggered only when a user logs off deliberately.
  • public boolean isCookieEnabled()
    This method enables JCMS to determine if the authentication (carried out via the login() method) should trigger the placing of a cookie, and whether or not the verification of the authentication (via the checkAuthentication() method) should use the cookies to authenticate a member.

3. Examples

3.1 SSO J2EE (example with Tomcat 4)

3.1.1 Creating the Application Server User Base

The first stage consists in declaring the user base (Realm) in Tomcat. In this example, we use a Memory Realm that enables a test situation to be set up rapidly by using a file for the user base. For more information about Tomcat Realms see Tomcat 4: Realm Configuration HOW-TO.
Add the following line in the Engine section of the Tomcat file server.xml:

<Realm className="org.apache.catalina.realm.MemoryRealm" pathname="conf/users.xml"/>

In the Tomcat conf/ directory create the file users.xml:

<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
<role rolename="jcms_role"/>
<user username="admin" password="welcome" roles="jcms_role"/>
</tomcat-users>

In this example, we specify only the "admin" user and his password "welcome". Given the implementation we are creating in the SSOAuthMgr class below, the specific username in this base must be identical to that of the JMCS member. In other implementations, it may be necessary to use a correspondence table.

3.1.2 Defining the Webapp Security Constraints

To indicate to the application server that the JCMS webapp requires an authentication, the following section must be added at the end of the webapp's deployment file web.xml:

<security-constraint>
<web-resource-collection>
<web-resource-name>JCMS</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>jcms_role</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>JCMS</realm-name>
</login-config>

This indicates that every access to JCMS (/*) necessitates an authentication, and more precisely membership of the jcms_role.

3.1.3Le gestionnaire d'authentification SSOAuthMgr

Add the following SSOAuthMgr.java file to your custom classes:

package custom;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.jalios.jcms.AuthenticationManager;
import com.jalios.jcms.Member;
/**
* Example of JCMS Authentication Manager using J2EE SSO
*/
public class SSOAuthMgr extends AuthenticationManager {

public Member login(HttpServletRequest request, HttpServletResponse response, String lang,
String login, String password, boolean persistent) throws IOException {
return null; // login is never done using the jcms forms
}

public Member checkAuthentication(HttpServletRequest request,
HttpServletResponse response) throws IOException {
String username = request.getRemoteUser(); // retrieve the J2EE username
return channel.getMemberFromLogin(username); // return the JCMS member with this username
}
public boolean isCookieEnabled() {
return false; // do not use JCMS cookie, authentication is always passed by the appserver
}
}

This class only handles the recovery of the user previously authenticated by the application server, and the authentication of the corresponding member in JCMS. Note that the authentication phase by submission of a JCMS form is no longer used. The login()method is therefore overridden to do nothing in this case. Since the member authentication is returned by the application server for each request, it is no longer necessary to activate the JCMS cookies.

Finally, this authentication manager must be declared in the file webapp.prop:

hook.auth-mgr.class: custom.SSOAuthMgr

3.2 Windows SSO (NTLM)

This example explains how to develop and implement an authentication manager that exploits the authentication of a Windows client workstation for JCMS.

3.2.1 Operating Principle

  1. The user opens his Windows session on a client workstation.
    His authentication is performed by making use of a centralized user base of NT domain type. This domain must be accessible by the machine hosting JCMS (as in the previous example, the user names entered in this base and in JCMS must be the same, unless a correspondence table is created in the authentication manager).
  2. The user logs on to JCMS with his browser which automatically sends the user's authentication.
    Under Internet Explorer this authentication is sent automatically to local intranet sites. This behavior can be modified by personalizing the security level(s) in the IE security tab (Tools/Internet Options).
    Under Firefox, the address of the web server hosting JCMS must be added to the list of servers automatically receiving the NTLM authentication; this is adjustable by typing about:config in the browser and modifying the network.automatic-ntlm-auth.trusted-uris property (this can also be carried out in the file pref.js located in the user's profile).
  3. JCMS receives the NTLM authentication in the request and authenticates the member. We use the JCIFS library which supplies a ServletFilter (NtlmHttpFilter) to process the NTLM authentication sent and position the authenticated user in the request.
  4. If the member does not exist in JCMS, he is created by using the LDAP connection (ActiveDirectory in general). The LDAP connection must be enabled and correctly configured in JCMS.
    Note that although in the example below the members will not be synchronized after their initial creation, and that no connection to LDAP will be carried out apart from during this creation.

3.2.2 JCIFS Configuration

In your deployment file web.xml add the filter HTTP NTLM from the JCIFS library. Configure it according to your architecture. For more information about configuring this module see http://jcifs.samba.org/src/docs/ntlmhttpauth.html

If JCMS is hosted on a Windows XP machine for a simple test, you can use the following configuration:

<filter>
<filter-name>NtlmHttpFilter</filter-name>
<filter-class>jcifs.http.NtlmHttpFilter</filter-class>
<init-param>
<param-name>jcifs.http.domainController</param-name>
<param-value>127.0.0.1</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>NtlmHttpFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

3.2.3 NTLMAuthMgr Authentication Manager

Add the following NTLMAuthMgr.java file to your custom classes:

package custom;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import jcifs.smb.NtlmPasswordAuthentication;
import com.jalios.jcms.AuthenticationManager;
import com.jalios.jcms.Member;
import com.jalios.ldap.LDAPConfiguration;
import com.jalios.ldap.LDAPMapper;
import com.jalios.util.Util;
import custom.LdapAuthenticationManager;
public class NTLMAuthMgr extends AuthenticationManager {
private static final Logger logger = Logger.getLogger(NTLMAuthMgr.class);

public Member checkAuthentication(HttpServletRequest request,
HttpServletResponse response) throws IOException {
NtlmPasswordAuthentication ntlm = (NtlmPasswordAuthentication) request.getSession().getAttribute( "NtlmHttpAuth" );
if (ntlm != null) {
if (logger.isDebugEnabled()) {
logger.debug("name: " + ntlm.getName());
logger.debug("username: " + ntlm.getUsername());
logger.debug("domain: " + ntlm.getDomain());
}
String login = ntlm.getUsername();
if (Util.notEmpty(login)) {
// Map common adminitrator name with JCMS admin
if ("Administrator".equals(login) || "Administrateur".equals(login)) {
return channel.getMemberFromLogin("admin");
}
// Search JCMS member with the given login
Member member = channel.getMemberFromLogin(login);
// If the member could not be found, try a synchronisation
// with the LDAP (probably ActiveDirectory), LDAP must be enabled in JCMS!
if (member == null) {
if (logger.isDebugEnabled()) {
logger.debug("Could not retrieve member \""+login+"\" in JCMS, trying a synchronisation with LDAP.");
}
member = createNTLMMemberFromLDAP(request, response, login);
}
if (member != null) {
return member;
}
logger.debug("Could not retrieve NTLM member in JCMS");
}
}
return super.checkAuthentication(request, response);
}

/**
* Try to connect to the ldap and create the member of the given login.
* This method does not check authentication as it is already done by JCIFS
* @param login the NTLM username of the user to create
* @return the newly create Member in JCMS
*/
public Member createNTLMMemberFromLDAP(HttpServletRequest request, HttpServletResponse response, String login) {
if (!channel.isLdapEnabled()) {
return null;
}

// Retrieve the last available configuration (updated by channel)
// and open a connection using the mapper
LDAPConfiguration ldapConf = channel.getLDAPConfiguration();
LDAPMapper mapper = new LDAPMapper(ldapConf);
if (!mapper.isConnected()) {
logger.warn("Could not connect to LDAP", mapper.getLastException());
return null;
}
// Search user and synchronize
Member member = LdapAuthenticationManager.synchronizeAccount(mapper,
login, /* login: retrieve from NTLM */
null, /* password: we don't have it*/
channel.getDefaultAdmin(), /* opAuthor */
false, /* checkConnect: no need to check connection as it was done by NTLM, and we don't have the password, so it wouldn't work */
true /* synchronize, that's the purpose! */);
if (member == null) {
logger.warn("Could not synchronize NTLM member \""+login+"\" with LDAP", mapper.getLastException());
}
mapper.disconnect();
return member;
}
}

 

 

This class handles only the recovery of the user previously authenticated by JCIFS, and creates him if he does not yet exist in JCMS; and it authenticates the corresponding member in JCMS.
Note that, unlike with the SSO module, we wish to conserve the authentication phase by submitting a JCMS form and placing a cookie. The login() and isCookieEnabled()methods are not therefore overridden and checkAuthentication()invokes super.checkAuthentication() if the NTLM authentication fails.

Finally, this authentication manager must be declared in the file webapp.prop:

hook.auth-mgr.class: custom.NTLMAuthMgr

3.3 Authentication using the User's Arrival Proxy

When an intranet is created within a group, an authentication database is not always available or it may be insufficiently complete to be able to filter access to content belonging to different entities. We must therefore find a way of defining the access "perimeter" of people logging on if they have not been authenticated.

3.3.1 Operating Principle

Generally it is possible to exploit the fact that within the network infrastructure the users are seen by the server though their addresses or proxy addresses.

It then becomes possible to associate ranges of addresses with JCMS groups as a means of controlling access to content. In the example shown below we wish to define two access perimeters, one for headquarters personnel, the other for subsidiaries.

3.2.2 HostNameAuthMgr Authentication Manager

1. Create two groups of users: for example headquarters and subsidiaries, identified in JCMS by two IDs, id_1 and id_2, for which we define read rights.

2. Define the necessary parameters in the file WEBINF/data/webapp.prop:

- list of proxies corresponding to perimeter 1;
-
ID of perimeter 1;
-
ID of perimeter 2 (the default perimeter).

For example:

hostname-authmgr.host-list: proxy1;proxy2;...; 
hostname-authmgr.host-mbr: id_1
hostname-authmgr.default-mbr: id_2

and we declare the HostNameAuthMgr class:

# Custom Authentication Manager class 
hook.auth-mgr.class: custom.HostNameAuthMgr

3. Implement this HostNameAuthMgr class extending the LdapAuthenticationManager class:.

package custom;
import java.io.IOException;
import java.net.InetAddress;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

import com.jalios.jcms.Member;
import com.jalios.util.Util;
public class HostNameAuthMgr extends LdapAuthenticationManager {
private static final Logger logger = Logger.getLogger(HostNameAuthMgr.class);
private static final String hostList = Util.getString(channel.getProperty("hostname-authmgr.host-list"), "");
private static final Member hostMbr = channel.getMember(channel.getProperty("hostname-authmgr.host-mbr"));
private static final Member defaultMbr = channel.getMember(channel.getProperty("hostname-authmgr.default-mbr"));
/**
* Perform authentication using HostName of user if not logged
*/
public Member checkAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
logger.debug("checkAuthentication()") ;
Member member = super.checkAuthentication(request, response);
if (member != null) {
return member;
}
String hostName = InetAddress.getByName(request.getRemoteAddr()).getHostName();
if (-1 != hostList.indexOf(hostName)) {
return hostMbr;
}
return defaultMbr;
}
}

Références :


Member Discussions

Login   Home   fr en
JALIOS SA - SIREN 440 126 035