Jump to: navigation, search

Jetty/Feature/JAAS



Introduction


JAAS implements a Java version of the standard Pluggable Authentication Module (PAM) framework.

JAAS can be used for two purposes:

  • for authentication of users, to reliably and securely determine who is currently executing Java code, regardless of whether the code is running as an application, an applet, a bean, or a servlet; and
  • for authorization of users to ensure they have the access control rights (permissions) required to do the actions performed.

JAAS authentication is performed in a pluggable fashion. This permits applications to remain independent from underlying authentication technologies. New or updated authentication technologies can be plugged under an application without requiring modifications to the application itself. Applications enable the authentication process by instantiating a LoginContext object, which in turn references a Configuration to determine the authentication technology(ies), or LoginModule(s), to be used in performing the authentication. Typical LoginModules may prompt for and verify a username and password. Others may read and verify a voice or fingerprint sample.

Feature

Many application servers support JAAS as a means of bringing greater flexibility to the declarative security models of the J2EE( now known as the Java EE) specification. Jetty support for JAAS provides greater alternatives for servlet security, and increases the portability of web applications.

The JAAS support aims to dictate as little as possible whilst providing a sufficiently flexible infrastructure to allow users to drop in their own custom LoginModules.

Configuration

Using JAAS with jetty is very simply a matter of declaring a org.eclipse.jetty.plus.jaas.JAASLoginService, creating a jaas login module configuration file and specifying it on the jetty run line. Let's look at an example.

Step 1

Configure a jetty org.eclipse.jetty.plus.jaas.JAASLoginService to match the <realm-name> in your web.xml file. For example, if the web.xml contains a realm called "xyz" like so:

<login-config>
  <auth-method>FORM</auth-method>
  <realm-name>xyz</realm-name>
  <form-login-config>
    <form-login-page>/login/login</form-login-page>
    <form-error-page>/login/error</form-error-page>
  </form-login-config>
</login-config>

Then the following JAASLoginService would be declared in a jetty configuration file:

    <Call name="addBean">
      <Arg>
          <New class="org.eclipse.jetty.plus.jaas.JAASLoginService">
           <Set name="Name">Test JAAS Realm</Set>
           <Set name="LoginModuleName">xyz</Set>
          </New>
      </Arg>
    </Call>
Important.png
Important
The name of the realm-name that you declare in web.xml must match exactly the Name element in your jetty config file.


For your convenience, the Hightide distribution of jetty contains an example configuration file in etc/jetty-jaas.xml.

Step 2

Set up your LoginModule in a configuration file, following the syntax rules:

xyz {
       com.acme.SomeLoginModule required debug=true;
    };
Important.png
Important
It is imperative that the application name on the first line is exactly the same as the LoginModuleName from your jetty config file.


Step 3

You now need to invoke jetty with support for jaas. This involves 3 elements:

  • adding extra jars to jetty's classpath
  • adding the jetty config file with the JAASLoginService declaration to the startup sequence
  • adding the jaas system property java.security.auth.login.config which specifies the location of your login module config file

If you're running the Hightide distribution of jetty, the extra jars will already be on the classpath, so your run line becomes:

java -Djava.security.auth.login.config=mylogin.conf -jar start.jar [myjaas.xml]

Where myjaas.xml is the jetty config file containing the JAASLoginService declaration from step 1. If you modified the provided $JETTY_HOME/etc/jetty-jaas.xml file then this will already be part of the startup sequence, so there is no need to specify it on the run line.

If you're running the standard distribution of jetty, you will need to provide all 3 elements, so the run line becomes:

java -Djava.security.auth.login.config=mylogin.conf -jar start.jar OPTIONS=plus myjaas.xml

Where myjaas.xml is the jetty config file you created in step 1. You might like to edit the start.ini file to add the extra OPTION and the myjaas.xml file to jetty's startup sequence to simplify the command line.


A Closer Look at JAASLoginService

To allow the greatest degree of flexibility in using JAAS with web applications, the JAASLoginService supports a couple of configuration options. Note that you don't ordinarily need to set these explicitly, as jetty has defaults which will work in 99% of cases. However, should you need to, you can configure:

  • a policy for role-based authorization (Default: org.eclipse.jetty.plus.jaas.StrictRoleCheckPolicy)
  • a CallbackHandler (Default: org.eclipse.jetty.plus.jaas.callback.DefaultCallbackHandler)
  • a list of classnames for the Principal implementation that equate to a user role (Default: org.eclipse.jetty.plus.jaas.JAASRole)

Here's an example of setting each of these (to their default values):

<New class="org.eclipse.jetty.plus.jaas.JAASLoginService">
  <Set name="Name">Test JAAS Realm</Set>
  <Set name="LoginModuleName">xyz</Set>
  <Set name="RoleCheckPolicy">
    <New class="org.eclipse.jetty.plus.jaas.StrictRoleCheckPolicy"/>
  </Set>
  <Set name="CallbackHandlerClass">
       org.eclipse.jetty.plus.jaas.callback.DefaultCallbackHandler
  </Set>
  <Set name="roleClassNames">
    <Array type="java.lang.String">
      <Item>org.eclipse.jetty.plus.jaas.JAASRole</Item>
    </Array>
  </Set>
</New>

RoleCheckPolicy

The RoleCheckPolicy must be an implementation of the org.eclipse.jetty.plus.jaas.RoleCheckPolicy interface and its purpose is to help answer the question "is User X in Role Y" for role-based authorization requests. The default implementation distributed with jetty is the org.eclipse.jetty.plus.jaas.StrictRoleCheckPolicy, which will assess a user as having a particular role iff that role is at the top of the stack of roles that have been temporarily pushed onto the user or if the user has no temporarily assigned roles, the role is amongst those configured for the user.

Roles can be temporarily assigned to a user programmatically by using the pushRole(String rolename) method of the org.eclipse.jetty.plus.jaas.JAASUserPrincipal class.

For the majority of webapps, the default StrictRoleCheckPolicy will be quite adequate, however you may provide your own implementation and set it on your JAASUserRealm instance.

CallbackHandler

A CallbackHandler is responsible for interfacing with the user to obtain usernames and credentials to be authenticated.

Jetty ships with the org.eclipse.jetty.plus.jaas.DefaultCallbackHandler which interfaces the information contained in the request to the Callbacks that are requested by LoginModules. You can replace this default with your own implementation if you have specific requirements not covered by the default.

Role Principal Implementation Class

When LoginModules authenticate a user, they usually also gather all of the roles that a user has and place them inside the JAAS Subject. As LoginModules are free to use their own implementation of the JAAS Principal to put into the Subject, jetty needs to know which Principals represent the user and which represent his/her roles when performing authorization checks on <security-constraint>s. The example LoginModules that ship with jetty all use the org.eclipse.jetty.plus.jaas.JAASRole class. However, if you have plugged in some other LoginModules, you must configure the classnames of their role Principal implementations.

Sample LoginModules

More on these modules follows, but first, a word about securing passwords in jetty config files:

Passwords/Credentials

Passwords can be stored in clear text, obfuscated or checksummed. The class org.eclipse.jetty.http.security.Password should be used to generate all varieties of passwords,the output from which can be cut and pasted into property files or entered into database tables.

Read more on Securing Passwords.


JDBCLoginModule

The JDBCLoginModule stores user passwords and roles in a database that are accessed via JDBC calls. You can configure the JDBC connection information, as well as the names of the table and columns storing the username and credential, and the name of the table and columns storing the roles.

Here is an example login module configuration file entry for it using an HSQLDB driver:

jdbc {
      org.eclipse.jetty.plus.jaas.spi.JDBCLoginModule required
      debug="true"
      dbUrl="jdbc:hsqldb:."
      dbUserName="sa"
      dbDriver="org.hsqldb.jdbcDriver"
      userTable="myusers"
      userField="myuser"
      credentialField="mypassword"
      userRoleTable="myuserroles"
      userRoleUserField="myuser"
      userRoleRoleField="myrole";
      };


There is no particular schema required for the database tables storing the authentication and role information. The properties userTable, userField, credentialField, userRoleTable, userRoleUserField, userRoleRoleField configure the names of the tables and the columns within them that are used to format the following queries:

select <credentialField> from <userTable> where <userField> =? select <userRoleRoleField> from <userRoleTable> where <userRoleUserField> =?

Credential and role information is lazily read from the database when a previously unauthenticated user requests authentication. Note that this information is only cached for the length of the authenticated session. When the user logs out or the session expires, the information is flushed from memory.

Note that passwords can be stored in the database in plain text or encoded formats - see "Passwords/Credentials" above.


DataSourceLoginModule

Similar to the JDBCLoginModule, but this LoginModule uses a DataSource to connect to the database instead of a jdbc driver. The DataSource is obtained by doing a jndi lookup on java:comp/env/${dnJNDIName}

Here is a sample login module configuration for it:

ds {
     org.eclipse.jetty.plus.jaas.spi.DataSourceLoginModule required
     debug="true"
     dbJNDIName="ds"
     userTable="myusers"
     userField="myuser"
     credentialField="mypassword"
     userRoleTable="myuserroles"
     userRoleUserField="myuser"
     userRoleRoleField="myrole";
    };


PropertyFileLoginModule

With this login module implementation, the authentication and role information is read from a property file.

props {
        org.eclipse.jetty.plus.jaas.spi.PropertyFileLoginModule required
        debug="true"
        file="/somewhere/somefile.props";
      };

The file parameter is the location of a properties file of the same format as the etc/realm.properties example file. The format is:

<username>: <password>[,<rolename> ...]

Here's an example:

fred: OBF:1xmk1w261u9r1w1c1xmq,user,admin
harry: changeme,user,developer
tom: MD5:164c88b302622e17050af52c89945d44,user
dick: CRYPT:adpexzg3FUZAk,admin

The contents of the file are fully read in and cached in memory the first time a user requests authentication.

LdapLoginModule

This requires JDK1.5 or above. Here's an example:

ldaploginmodule {
   org.eclipse.jetty.plus.jaas.spi.LdapLoginModule required
   debug="true"
   contextFactory="com.sun.jndi.ldap.LdapCtxFactory"
   hostname="ldap.example.com"
   port="389"
   bindDn="cn=Directory Manager"
   bindPassword="directory"
   authenticationMethod="simple"
   forceBindingLogin="false"
   userBaseDn="ou=people,dc=alcatel"
   userRdnAttribute="uid"
   userIdAttribute="uid"
   userPasswordAttribute="userPassword"
   userObjectClass="inetOrgPerson"
   roleBaseDn="ou=groups,dc=example,dc=com"
   roleNameAttribute="cn"
   roleMemberAttribute="uniqueMember"
   roleObjectClass="groupOfUniqueNames";
   };

Writing your Own LoginModule

If you want to implement your own custom LoginModule, there are two classes to be familiar with org.eclipse.jetty.plus.jaas.spi.AbstractLoginModule and org.eclipse.jetty.plus.jaas.spi.UserInfo.

The org.eclipse.jetty.plus.jaas.spi.AbstractLoginModule implements all of the javax.security.auth.spi.LoginModule methods. All you need to do is to implement the getUserInfo method to return a org.eclipse.jetty.plus.jaas.UserInfo instance which encapsulates the username, password and role names (note: as java.lang.Strings) for a user.

The AbstractLoginModule does not support any caching, so if you want to cache UserInfo (eg as does the org.eclipse.jetty.plus.jaas.spi.PropertyFileLoginModule) then you must provide this yourself.

Other Goodies

RequestParameterCallback

As all servlet containers intercept and process a form submission with action j_security_check, it is usually not possible to insert any extra input fields onto a login form with which to perform authentication: you may only pass j_username and j_password. For those rare occasions when this is not good enough, and you require more information from the user in order to authenticate them, you can use the JAAS callback handler org.eclipse.jetty.plus.jaas.callback.RequestParameterCallback. This callback handler gives you access to all parameters that were passed in the form submission. To use it, in the login() method of your custom login module, add the RequestParameterCallback to the list of callback handlers the login module uses, tell it which params you are interested in, and then get the value of the parameter back. Here's an example:

public class FooLoginModule extends AbstractLoginModule
{
        .
        .
        .
 
     public boolean login()
        throws LoginException
     {
        .
        .
        .
        Callback[] callbacks = new Callback[3];
        callbacks[0] = new NameCallback();
        callbacks[1] = new ObjectCallback();
 
        //as an example, look for a param named "extrainfo" in the request
        //use one RequestParameterCallback() instance for each param you want to access
        callbacks[2] = new RequestParameterCallback ();
        ((RequestParameterCallback)callbacks[2]).setParameterName ("extrainfo");
        .
        .
        .
        callbackHandler.handle(callbacks);
        String userName = ((NameCallback)callbacks[0]).getName();
        Object pwd = ((ObjectCallback)callbacks[1]).getObject();
        List paramValues = ((RequestParameterCallback)callbacks[2]).getParameterValues();
 
        //use the userName, pwd and the value(s) of the parameter named "extrainfo" to
        //authenticate the user
        .
        .
        .
     }


Example JAAS WebApp

The Hightide distribution of jetty contains an example webapp that uses JAAS.

Additional Resources

See Java Authentication and Authorization Service (JAAS) Reference Guide for more information about JAAS.