The Official BLOG and Wiki for CustomMode.com
[ start | index | login ]
start > JAASSecurityRealm in Tomcat

JAASSecurityRealm in Tomcat

Created by dmitry. Last edited by dmitry, 3 years and 87 days ago. Viewed 992 times. #40
[diff] [history] [edit] [rdf]
labels
attachments

draft

Introduction

I just finished converting my projects from Jetty Jboss to tomcat and one of the most significant problems I had to overcome was the security. I used Jboss's realm mechanism and didn't really feel like writing everything from scratch. To accomplish the goal I had to make my chose in favor of JAASSecurityRealm in Tomcat. Surprisingly the web is blottered with different suggestions/solutions that don't work the way they should. This article describes step by step design and implementation of my solution wich is very simple, reliable and can be extended to any degree of complexity as needed.

JAAS stands for Java Authentication and Authorisation Service. This article only describes how to use JAAS for Authentication in web applications deployed to tomcat. The Authorisation part is done through default web app mechanism - describing protected resources in web.xml.

Components

server.xml

The server.xml is located in {CATALINA_HOME}/conf. There are two components that need to be declared in server.xml.

<Realm> node that is a child of <Engene> component:

<Realm className="org.apache.catalina.realm.JAASRealm"
  appName="CastorLogin"
  userClassNames="com.forum.jaas.SimpleUserPrincipal"
  roleClassNames="com.forum.jaas.SimpleGroupPrincipal"
  digest="MD5"
  debug="99" />
  • appName is the name that matches the name of the module in jaas.config.
  • userClassName and roleClassName specify classes that need to be implemented (there does not seem to be the default implementation).
  • digest specifies different password digest algoritms for password hashing, expreiments with this made no difference, the password passed to the module from the client form was always clear text and i had to encrypt it before comparing it to the password in the database wich was already encrypted.

jaas.config

CastorLogin {
  com.forum.jaas.CastorLoginModule REQUIRED  
  dataSource="jdbc/MySQLAppDS";
};

This file contains jass login modules definitions. If the application will use jass security it has to be started with -D option specifying where this file can be found by the jass framework. In our case the application this application is tomcat so we have to pass this parameter to tomcat. Modify {CATALINA_HOME}/bin/catalina.sh , as the first line add:

JAVA_OPTS=-Djava.security.auth.login.config==/home/dev/proj/forum/webapp/WEB-IN
F/jaas.config

CastorLoginModule.java

package ...;

import … … import ...;

public class CastorLoginModule implements LoginModule {

private Collection oAuthenticatedRoles = new Vector();

private Principal oAuthenticatedUser = null;

private CallbackHandler oCallbackHandler;

private String oDataSource;

private PasswordCallback oPasswordCallback;

private Subject oSubject;

private NameCallback oUsernameCallback;

public boolean abort() throws LoginException { oSubject.getPrincipals().clear(); oSubject.getPrivateCredentials().clear(); oSubject.getPublicCredentials().clear(); return false; }

private boolean authenticate(String aUser, String aPassword) { … try { … oAuthenticatedRoles.add( new SimpleGroupPrincipal(mResultSet.getString("rolename"))); oAuthenticatedUser = new SimpleUserPrincipal(aUser); authenticated = true; … oAuthenticatedRoles.add( new SimpleGroupPrincipal(roleName)); } catch (Exception e) { e.printStackTrace(); } return authenticated; }

public boolean commit() throws LoginException { boolean commit = false; if (oAuthenticatedUser == null) { return false; } oSubject.getPrincipals().add(oAuthenticatedUser); if (oAuthenticatedRoles.size() > 0) { for (Iterator it = oAuthenticatedRoles.iterator(); it.hasNext();) { Object o = it.next(); o.getClass().getName()); Principal role = (Principal) o; oSubject.getPrincipals().add(role); } } return true; }

public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map aOptions) { this.oSubject = subject; this.oCallbackHandler = callbackHandler; oUsernameCallback = new NameCallback("Username"); oPasswordCallback = new PasswordCallback("Password", false); oDataSource = (String)aOptions.get("dataSource"); }

public boolean login() throws LoginException { String username = null; String password = null;

boolean success = false; try { oCallbackHandler.handle(new Callback[] { oUsernameCallback, oPasswordCallback });

username = oUsernameCallback.getName(); password = new String(oPasswordCallback.getPassword()); success = authenticate(username, password); } catch (Exception e) { e.printStackTrace(); } // System. // out.println("authenticated: " + success); return success; }

public boolean logout() throws LoginException { oSubject.getPrincipals().clear(); oSubject.getPrivateCredentials().clear(); oSubject.getPublicCredentials().clear(); return true; }

}

Main points:
  • in my project I'm using Castor for accessing database (that's where the name CastorLoginModule comes from). The straight JDBC could be used as well. I'm not providing the data access logic in this example, I'm leaving it up to the reader to implement the real logic.
  • initialize method reads dataSource parameter specified in jaas.config for CastorLogin module.
  • login method is a callback that gets executed by the JAAS framework when user tries to login, it calls private authenticate method that does the real job of verifing if the supplied name/password match the values stored in the database record.
  • CastorLoginModule, SimpleUserPrincipal, and SimpleGroupPrincipal must be added to Tomcat's classpath.
package com.forum.jaas;

import java.security.Principal;

public class SimpleUserPrincipal implements Principal {

final private String name;

public SimpleUserPrincipal(String name) { this.name = name; }

public String getName() { return name; } }

  • SimpleGroupPrincipal
package com.forum.jaas;

import java.security.Principal;

public class SimpleGroupPrincipal implements Principal {

final private String name;

public SimpleGroupPrincipal(String name) { this.name = name; }

public final String getName() { return name; } }

web.xml

<filter>
    	<filter-name>UserSessionBinderFilter</filter-name>
    	<filter-class>
    		com.forum.common.UserSessionBinderFilter
    	</filter-class>
    </filter>
    <filter-mapping>
    	<filter-name>UserSessionBinderFilter</filter-name>
    	<url-pattern>*.do</url-pattern>
    </filter-mapping>
This section defines a filter that will be executed for every struts request. See the UserSessionBinderFilter details below.

<!--************* logged in user ****************************************** -->
<security-constraint>
    <web-resource-collection>
	<web-resource-name>protected content</web-resource-name>
	<url-pattern>/profile/*</url-pattern>
	<url-pattern>/forum/forms/*</url-pattern>
	<url-pattern>/fees/registration/*</url-pattern>
	<url-pattern>/login/index.do</url-pattern>
	<!-- If you list http methods, only those methods are protected -->
	<!--http-method>DELETE</http-method>
	     <http-method>GET</http-method>
	     <http-method>POST</http-method>
	     <http-method>PUT</http-method-->
    </web-resource-collection>

<auth-constraint> <role-name>*</role-name> </auth-constraint> <user-data-constraint> <transport-guarantee>NONE</transport-guarantee> </user-data-constraint> </security-constraint>

<!--************* administrator ******************************************* --> <security-constraint> <web-resource-collection> <web-resource-name>administrators content</web-resource-name> <!-- Define the context-relative URL(s) to be protected --> <url-pattern>/admin/*</url-pattern> <!--url-pattern>/client/*</url-pattern--> <!-- If you list http methods, only those methods are protected --> <!-- <http-method>DELETE</http-method> --> <!-- <http-method>GET</http-method> --> <!-- <http-method>POST</http-method> --> <!-- <http-method>PUT</http-method> --> </web-resource-collection>

<auth-constraint> <!-- Anyone with one of the listed roles may access this area --> <role-name>manager</role-name> </auth-constraint> <user-data-constraint> <transport-guarantee>NONE</transport-guarantee> </user-data-constraint> </security-constraint>

The <security-constraint> element defines what resources are protected and what roles are allowed to access these resources. This is a standard j2ee web application authorization mechanism. It relies on the standard HttpServletRequest API. There are two methods in HttpServletRequest that are invoked by the security framework to determine if the resource can be accessed by the user: getRemoteUser and isUserInRole. If the user is not logged in these methods will return null by default. If the user is not loggedin and is trying to access resource that matches one of <url-pattern> masks, the framework will automatically redirect to login page (see login page definition in the web.xml file below). The <url-pattern> for different roles must be nonambigues. For the logged in users the framework automatically executes isUserInRole mothod on the HttpServletRequest object and if it returns false it will return
HTTP Status 403 - Access to the requested resource has been denied

When ever the developr correctly implements and configures the JAASSecutoryRealm, for the logged in users getRemoteUser will always return non null value if the user is authenticated and isUserInRole will return true or false based on the LoginModule implementaion details.

<!-- Default login configuration uses form-based authentication -->
<login-config>
    <auth-method>FORM</auth-method>
    <realm-name>CastorLogin</realm-name>
    <form-login-config>
        <form-login-page>/login/login.do</form-login-page>
        <form-error-page>/login/error.do</form-error-page>
    </form-login-config>
</login-config>
This section defines a form based login.

<security-role>
    <role-name>customer</role-name>
</security-role>
<security-role>
    <role-name>manager</role-name>
</security-role>
Two roles are defined.

UserSessionBinderFilter.java

This filter is invoked for every request. When ever getRemoteUser on the HttpServletRequest object return non null value, it means the user was authenticated, and the filter instantiates the User object from data base and sticks it in to the HTTPSession. After this the User object (more full blown version of the object then just getRemoteUser string) will be available to every jsp/servelet/struts action. If getRemoteUser returns null, the filter will remove the User object from the HTTPSession.
package com.forum.common;

import java.io.IOException; import java.util.Collection; import java.util.Vector;

import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;

import com.forum.common.session.SessionListener; import com.forum.common.session.SessionManager; import com.forum.jdo.forum.User; import com.forum.taglib.castor.JDOHelper;

/** * Date:$Date: 2004/09/18 01:16:56 $ * * @author $Author: dmitry $ * @version $Revision: 1.1 $ */

public final class UserSessionBinderFilter implements Filter { static org.apache.log4j.Logger log = org.apache.log4j.Logger .getLogger(UserSessionBinderFilter.class);

public void init(FilterConfig aFilterConfig) throws ServletException { oFilterConfig = aFilterConfig; }

private FilterConfig oFilterConfig = null;

public void destroy() { oFilterConfig = null; }

public void doFilter(ServletRequest aServletRequest, ServletResponse aServletResponse, FilterChain aFilterChain) throws IOException, ServletException {

HttpServletRequest mHttpServletRequest = (HttpServletRequest) aServletRequest; HttpServletResponse mHttpServletResponse = (HttpServletResponse) aServletResponse;

String mUsername = mHttpServletRequest.getRemoteUser();

if (mUsername == null) { mHttpServletRequest.getSession().removeAttribute("SessionListener.SESSION_MANAGER"); } else {

JDOHelper helper = new JDOHelper();

helper.transactionBegin(); Collection params = new Vector(); params.add(mUsername);

User user = (User) helper .execute( "SELECT a FROM com.forum.jdo.forum.User a where userName=$1 and active = 'Y' ", params);//getUser(username, password);

helper.transactionEnd(); try { ((SessionManager) (mHttpServletRequest.getSession() .getAttribute(SessionListener.SESSION_MANAGER))).setUser(user); } catch(Exception e) { e.printStackTrace(); } } aFilterChain.doFilter(aServletRequest, aServletResponse); } }

References

authorization

no comments | post comment
custommode.com | ©2000-2005
webmaster at custommode dot com