Part 1: Performing
authentication with plaintext/xml user-role mapping

This blog was written by our new committer Vladimir Ivanov who implemented a feature that users have been wanting for a long time. 

Introduction

Apache ESME currently supports two different authentication
schemes: when user credentials are stored in the database and via OpenID. 
Corporate users, however, might be interested in container-managed
authentication (CMA) — because this scheme supports integration with enterprise
services such as LDAP and Single Sign-On. In the first part of this blog,  I'll
explain how ESME-based applications can use CMA and how to configure two popular
web servers- Apache Tomcat and Jetty - with simple user-role mapping. In the
second part, I'll describe LDAP integration to perform CMA and how get
additional user attributes via the Lift LDAP API.

ContainerManagedAuthModule: The necessary code
changes

A new authentication module ContainerManagedAuthModule was
introduced to hook into the container-managed authentication process. First of
all, it was registered along with the other authentication modules:

Boot.scala

   
UserAuth.register(UserPwdAuthModule)

   
UserAuth.register(OpenIDAuthModule) 

   
UserAuth.register(ContainerManagedAuthModule)

UserAuth.scala

All authentication modules should extend the
AuthModule trait:

object
ContainerManagedAuthModule extends AuthModule

Currently, the list with security role (group) names is also
defined in the source code:

 
val rolesToCheck = List(

   
"esme-users"

 
)

It is also possible to get the list of roles from some
external source, for example, from a property file or a LDAP.

The method moduleName defines the name for the new
auth module. This value acts as a discriminator and will be stored in the
DB:

 
def moduleName: String = "cm"

After the container finishes the authentication and
authorization phases, it is  neccessary to hook into the normal user processing
to save the user data. This task is performed in the performInit method:

def
performInit(): Unit = {

CMA must be applied to a specific URL, for example
/cm/login, so it is necessary to append a partial function to
LiftRules.dispatch to perform the neccessary operations:

   
LiftRules.dispatch.append {

      case Req("cm" ::
"login" :: Nil, _, _) =>  {

        val from =
"/"

Note: The majority of necessary steps to
further utilize this new auth method have already been desribed in the Lift
Wiki
.

In short, it is neccessary to unwrap the
javax.servlet.http.HttpServletRequest object to get the username and role
names. If a user has one of the specified roles, the module should attempt to
find an existing user with the same nickname which previously has logged in via
this module. If such a user hasn't been found, a new User is created.  The last
step is to save the userId in the HTTP session via User.logUserIn method
call.

        S.request match
{

          case Full(req)
=> {

            val httpRequest:
HTTPRequest = req.request

            val hrs =
httpRequest.asInstanceOf[HTTPRequestServlet]

            val hsr:
HttpServletRequest = hrs.req

            val username :
String = hsr.getRemoteUser

           
if(username!=null){

              val
currentRoles = rolesToCheck.filter(hsr.isUserInRole(_))

             
if(currentRoles.size == 0) {

                info("No
roles have been found")

               
S.error(S.?("base_user_err_unknown_creds"))

              } else
{

               
currentRoles.map(cr => {

                (for
{

                    user
<- UserAuth.find(By(UserAuth.authKey, username),

                                         
By(UserAuth.authType, moduleName)).flatMap(_.user.obj) or

                   
User.find(By(User.nickname, username))

                  } yield
user) match {

                    case
Full(user) => {

                     
logInUser(user)

                   
}

                    case _
=> {

                      val usr
= User.createAndPopulate.nickname(username).saveMe

                      //find
and save additional attributes in LDAP if it's enabled

              ...

                     
}

                     
UserAuth.create.authType(moduleName).user(usr).authKey(username).save

                     
logInUser(usr)

                   
}

                 
}

                })

              }

   
}

Configuration

Now it's time to set-up the configuration for the CMA. All
configuration settings for a Java EE web application (ESME is based on the Lift
web framework, so it's packaged as a WAR file), including security settings, are
defined in the web.xml file:

web.xml

For this example, we will use form-based authentication:

     
FORM

     
ESMERealm

Next, the login and error pages are specified.

       

           

/cm_login.jsp

           

/cm_error.jsp

       

   

Then, the security-role name, which any authenticated user
must have for successful authorization, is defined:

   

   

      An
authenticated ESME user

     
esme-users

   

And finally it is necessary to configure the mapping between
the security role and the URL which is associated with the new authentication
module:

 

   

     

       
ForceLogin

       
Secured page for forcing the container to request
login

       
/cm/login

     

     

       
esme-users

    
 

   

The login page contains a form with specific action
attributes and two input fields:

cm_login.jsp

   

     
Login

   

   

       
method="POST" action="j_security_check">

          Username: type="text" name="j_username"/>

          Password: type="password" name="j_password"/>

          type="submit"/>

       

   

Let's move on to the web server configuration for the next
steps. We must define the users for our web application as well as mapping
between these users and the security role that is specified in the web.xml file.
I'll show how to configure simple user-role mapping for two popular web servers
— Jetty and Tomcat.

Jetty

The HashUserRealm implementation is used to specify
the user-role mapping in the properties file for Jetty. The
maven-jetty-plugin has been already included in the Maven project
file pom.xml for the ESME application, so it is possible to configure
Jetty in the plugin configuration section:

pom.xml

           

               
org.mortbay.jetty

               
maven-jetty-plugin

               

   
                /

                   
0

                   

                       
implementation="org.mortbay.jetty.security.HashUserRealm">

                  
         ESMERealm

                           
jetty-login.properties

                       

                   

               

           

The format for this property file has the following form:
username: password [,rolename
...]
.

An example is shown below:

jetty-login.properties

cmuser: cmuser,
esme-users

That's it. Now Jetty is configured for CMA. Execute mvn
clean jetty-run
command to start Jetty and type http://localhost:8080/cm/login URL in your browser. You should see the form containing the username and password fields. Now
try to log in with the user with the id cmuser.

Tomcat

The configuration of Tomcat web server is very similar to
that of Jetty, except that the user-role mapping is specified in a XML file. 
The MemoryUserDatabaseFactory implementation is used to define the
mapping file.  The corresponding realm UserDatabaseRealm is also
specified in the server.xml configuration file:

server.xml

 

   

   

             
type="org.apache.catalina.UserDatabase"

             
description="User database that can be updated and saved"

             
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"

             
pathname="conf/tomcat-users.xml" />

 

...

      className="org.apache.catalina.realm.UserDatabaseRealm"

  
          resourceName="UserDatabase"/>

Below is an example of the user-role mapping definition in
the tomcat-users.xml file which is usually located in the  tomcat/conf
directory.

tomcat-users.xml

 

 

Now it's neccessary to package the WAR file with the mvn
clean package
command and deploy it to Tomcat either via maven plugin or in
Tomcat's administrative console. Then proceed to the following URL:

http://localhost:8080/your_web_context/cm/login

The login form should be displayed.

Conclusion

In this part of the blog, I've covered the new authentication
module, application and server configuration and simple user-role mapping. In
the next part, I'll show how to configure Tomcat to use LDAP for CMA and get
additional attributes for the authenticated user.

Links

1. Lift Wiki -
How to use Container Managed Security : http://www.assembla.com/wiki/show/liftweb/How_to_use_Container_Managed_Security

2. Jetty
HashUserRealm: http://jetty.codehaus.org/jetty/jetty-6/apidocs/org/mortbay/jetty/security/HashUserRealm.html

3. Tomcat Realms:
http://tomcat.apache.org/tomcat-6.0-doc/realm-howto.html

4. Java EE 5
Tutorial – Securing Web Applications: http://download.oracle.com/javaee/5/tutorial/doc/bncas.html