Creating custom user authentication
Written by Hani Suleiman
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:
To make life easier, an abstract UserManager implementation has been created
called com.orionsupport.security.SimpleUserManager. You can download the jar
from this document in the attachments on the right.
In order to create your own UserManager, you simply extend this class and implement 3 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( java.util.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. 3 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.
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.
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.
And finally (wasn't that hard was it?) 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.
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>
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 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 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:
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.
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>
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 3 methods that is called on it, the user will be shown the restricted page.
Copyright © 2007 IronFlare AB