Orion Server: Creating custom user authentication

This is a Java/J2EE tutorial for Orion Application Server.

This short article aims to answer the following question:

I would like to restrict access to my web-application using FORM or BASIC authentication, but I do not store all my usernames in the principals.xml file. How can I authenticate using a custom user database?

Well, Orion has support for pluggable UserManagers. The idea is that you create a class that implements the com.evermind.security.UserManager interface and tell Orion to use that for your application. The UserManager is responsible for locating, creating, editing, removing and managing User and Group object.

Once a UserManager has been plugged in, Orion will call it when it needs to authenticate a user. This can happen in situations such as user’s accessing restricted pages of a web-app or a client trying to bind to the JNDI context. While the UserManager is very rich and flexible, this article focuses purely on solving the above question and many aspects of the UserManager shall be ignored.

Four steps are required:

  1. Write the custom UserManager.
  2. Plug the UserManager into your application.
  3. Define your groups.
  4. Create security constraints in your web-app.

Step 1 : Writing the custom UserManager

To make life easier, an abstract UserManager implementation has been created called com.orionsupport.security.SimpleUserManager.

Download simpleusermanager.jar

In order to create your own UserManager, you simply extend this class and implement three methods to your own needs:

protected boolean userExists(String username);
protected boolean checkPassword(String username, String password);
protected boolean inGroup(String username, String groupname);

Optionally, if your UserManager needs to perform a task upon startup, you can overide this method:

public void init(Properties properties) throws InstantiationException;

These methods will perform code to talk to your custom user-database. Here is an example, that implements a custom user-database in memory using HashMaps. Three standard users are added (with passwords) and 1 admin is added. The standard users are in the ‘users’ group and the admin is in all groups.

package test;

import java.util.*;
import com.orionsupport.security.SimpleUserManager;

public class MyUserManager extends SimpleUserManager {

  // map contains username/password pairs
  private Map users  = new HashMap();
  private Map admins = new HashMap();

  public void init( Properties properties ) {
    users.put( "joe", "schmoe" );
    users.put( "someone", "mypassword" );
    users.put( "billgates", "banana" );
    admins.put( "admin", "secret" );
  }

  protected boolean userExists( String username ) {
    return users.containsKey( username ) || admins.containsKey( username );
  }

  protected boolean checkPassword( String username, String password ) {
    if ( admins.containsKey( username ) ) {
      return password.equals( admins.get( username ) );
    }
    else {
      return password.equals( users.get( username ) );
    }
  }

  protected boolean inGroup( String username, String groupname ) {
    if ( admins.containsKey( username ) ) {
      // admins are in all groups
      return true;
    }
    else if ( users.containsKey( username ) ) {
      // users are only in group 'users'
      return groupname.equals( "users" );
    }
    else {
      // unknown user
      return false;
    }
  }

}

The above example is not going to be very useful in the real world, but reading through it should illustrate how to create a UserManager that connects to your custom user-database. This could be a relational database, in memory store, XML file, flat-file, LDAP server, NT domain or proprietary solution. It’s your job to create the custom hooks - SimpleUserManager just makes it very quick and easy.

Step 2 : Plugging the UserManager into your application

Once you have written the UserManager you need to let Orion know about it. To do this, edit the appropriate orion-application.xml in your orion/application-deployments directory for your application and add the following:

<user-manager class="test.MyUserManager" />

Tip: If you need to specify properties to be passed to your UserManager, you can insert <property name="..." value="..." /> tags inside the <user-manager> tag. These are then passed to the public void init( Properties properties ) method of the UserManager.

You also need to ensure that SimpleUserManager and your own implementation (MyUserManager) are in the class-path for your application. To do this, add appropriate <library path="...;" /> tags to orion-application.xml.

Once you’ve done this, check the log for Orion (if it’s not running - start it). If you don’t have any errors, well done. If you do, it’s almost always a class-path problem - check all the classes are available to Orion.

Step 3 : Defining your groups

Edit the file principals.xml in the same directory as orion-application.xml and add entries for each group you require in your application. For every group add an appropriate <group name="...;" /> tag. Example:

<principals>

  <groups>
    <group name="users" />
    <group name="admins" />
  </groups>

  <users>
  </users>

</principals>

Notice that only groups are specified here - users are not as your UserManager is responsible for retrieving them.

Step 4 : Creating security constraints on your web-application

And finally we need to tell our web-application to restrict certain pages to users who are in a specified group.

Open WEB-INF/web.xml of your web-app, 3 additions are about to be made.

1. Add security roles

For every group that you want to use, you need to add a <security-role> tag. The syntax is:

<security-role>
  <role-name>myGroup</role-name>
</security-role>

2. Add login configuration

This specifies how the user shall be prompted to enter their username and password. There are a few types, although typically you will use BASIC or FORM.

BASIC

BASIC is the easiest to use - it involves using HTTP authorization features built into the browser to prompt for username and password. When a user attempts to access a page using BASIC authentication, the browser will pop-up a window asking for login details. This username/password is stored by the browser and automatically sent for any page that is in the specified realm - this allows multiple username/passwords to be stored by the browser for different web-apps. Typically a username/password is remembered by the browser until it is closed.

<login-config>
  <auth-method>BASIC</auth-method>
  <realm-name>myRealm</realm-name>
</login-config>

FORM

FORM is a more practical solution - when a restriced page is requested, an HTML page shall be returned instead containing a form to enter the username/password into. The advantage of this is that the page can be customized to have the same look and feel as the rest of your site and contain other information (such as help or links to retrieve your lost password or create new account).

In order to use the FORM method, you must first create an HTML page containing the actual form. Create a file called login.jsp in your web-app directory:

<form method="post" action="j_security_check">
  Username: <input name="j_username" type="text"><br>
  Password: <input name="j_password" type="password"><br>
  <input type="submit">
</form>

Of course, the form can look however you want it to, so long as it: * has the action attribute set to j_security_check * has the method attribute set to post (actually this isn’t really required but you are recommended to do it) * contains 2 fields: j_username and j_password

When your form has been created, you need to let Orion know about it:

<login-config>
  <auth-method>FORM</auth-method>
  <form-login-config>
    <form-login-page>/login.jsp</form-login-page>
    <form-error-page>/login.jsp</form-error-page>
  </form-login-config>
</login-config>

If you would like another page to handle invalid login attempts, you can change the <form-error-page> tag.

3. Protect resources

The last thing you have to do is tell Orion, which files or directories in your web-app are restricted to which users:

<security-constraint>
  <web-resource-collection>
    <url-pattern>/path/</url-pattern>
  </web-resource-collection>
  <auth-constraint>
    <role-name>myGroup</role-name>
  </auth-constraint>
</security-constraint>

Example

The following web.xml file sets up 2 restrictions (using form-based authentication). The /users/ directory is restricted to people in the users group only, and the /admins/ directory is restricted to, well you can guess.

<web-app>

  <security-role>
    <role-name>users</role-name>
  </security-role>

  <security-role>
    <role-name>admins</role-name>
  </security-role>

  <login-config>
    <auth-method>FORM</auth-method>
    <form-login-config>
      <form-login-page>/login.jsp</form-login-page>
      <form-error-page>/login.jsp</form-error-page>
    </form-login-config>
  </login-config>

  <security-constraint>
    <web-resource-collection>
      <url-pattern>/users/</url-pattern>
    </web-resource-collection>
    <auth-constraint>
      <role-name>users</role-name>
    </auth-constraint>
  </security-constraint>

  <security-constraint>
    <web-resource-collection>
      <url-pattern>/admins/</url-pattern>
    </web-resource-collection>
    <auth-constraint>
      <role-name>admins</role-name>
    </auth-constraint>
  </security-constraint>

  ... rest of web.xml here ...

</web-app>

Et voila! Trying to access http://mysite/myapp/users/ will prompt with the a form for logging in. If your UserManager responds with true for all three methods that is called on it, the user will be shown the restricted page.

Remember to download simpleusermanager.jar.