Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

The aim of this tutorial is to build a simple servlet for authenticating users with profiles in the demo application. You can use the code in this tutorial as the basis for your web applications.

Servlet web.xml Configuration

Before we begin it might be convenient to put a few of the Guardian LDAP configuration parameters into the web.xml of the web application. This way these parameters can be tweaked for different deployments without code changes.

The following parameters are good candidates so let's add em:

Parameter Name

Value

realm

EXAMPLE.COM

connectionUrl

ldap://localhost:10389/dc=example,dc=com

applicationPrincipalDn

appName=demo,ou=Applications,dc=example,dc=com

applicationCredentials

secret

Here's the web.xml file. Notice that our servlet class is LoginServlet and it's mapped to everything under /demo.

No Format
titleweb.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
  <servlet>
    <servlet-name>LoginServlet</servlet-name>
    <servlet-class>org.safehaus.triplesec.demo.LoginServlet</servlet-class>
    <init-param>
    	<param-name>realm</param-name>
    	<param-value>EXAMPLE.COM</param-value>
    </init-param>
    <init-param>
    	<param-name>connectionUrl</param-name>
    	<param-value>ldap://localhost:10389/dc=example,dc=com</param-value>
    </init-param>
    <init-param>
    	<param-name>applicationPrincipalDn</param-name>
    	<param-value>appName=demo,ou=Applications,dc=example,dc=com</param-value>
    </init-param>
    <init-param>
    	<param-name>applicationCredentials</param-name>
    	<param-value>secret</param-value>
    </init-param>
    <load-on-startup/>
  </servlet>

  <servlet-mapping>
    <servlet-name>LoginServlet</servlet-name>
    <url-pattern>/demo/*</url-pattern>
  </servlet-mapping>
</web-app>

Servlet Init Method

A servlet or any web application for that matter which has it's policy managed in Triplesec should initialize the ApplicationPolicy at startup. This way the ApplicationPolicy which is not user or profile specific but application specific can be put within the application scope. Here's the init() method which extracts init parameters from the web.xml and initializes an ApplicationPolicy:

Code Block
titleServlet init() Method
// Servlet members for use later
private String realm;
private ApplicationPolicy policy;


public void init( ServletConfig config )
{
    // -------------------------------------------------------------------
    // get the realm the guardian connection URL and the application DN
    // -------------------------------------------------------------------

    realm = config.getInitParameter( "realm" );
    String connectionUrl = config.getInitParameter( "connectionUrl" );
    String applicationPrincipalDn = config.getInitParameter( "applicationPrincipalDn" );
    String applicationCredentials = config.getInitParameter( "applicationCredentials" );

    // -------------------------------------------------------------------
    // setup connection parameters and initialize the application policy
    // -------------------------------------------------------------------

    Properties props = new Properties();
    props.setProperty( "applicationPrincipalDn", applicationPrincipalDn );
    props.setProperty( "applicationCredentials", applicationCredentials );
    try
    {
        Class.forName( "org.safehaus.triplesec.guardian.ldap.LdapConnectionDriver" );
        policy = ApplicationPolicyFactory.newInstance( connectionUrl, props );
    }
    catch ( Exception e )
    {
        e.printStackTrace();
    }
}

Isolating Login Code in a Command

For each user authentication we're going to have to instantiate a SafehausLoginModule instance, initialize it, call login() and then call commit(). While doing so we need to supply a CallbackHandler with the proper parameters so it can feed them to the LoginModule. Afterwords the authenticated Principal needs to be extracted from the Subject supplied to the LoginModule.

Logically this whole process can be abstracted using the command pattern so it seems like one atomic operation. Furthermore virtually every application that directly uses the JAAS LoginModule will do this so it might be nice to stuff this code into a handy reusable class. Here's what such a command would look like:

Code Block
titleLoginCommand Class
/**
 * Simple login command used by the demo application.
 *
 * @author <a href="mailto:akarasulu@safehaus.org">Alex Karasulu</a>
 * @version $Rev$
 */
public class LoginCommand
{
    /** the user id of the principal minus realm info */
    private final String userId;
    /** the realm the user is authenticating into */
    private final String realm;
    /** the value of the hotp */
    private final String passcode;
    /** the static password for the user */
    private final String password;
    /** the triplesec guardian policy for this application */
    private final ApplicationPolicy policy;

    /** the safehaus principal resulting from authentication */
    private SafehausPrincipal principal;


    /**
     * Creates a single use login command that can later be executed.
     *
     * @param userId the user id of the principal minus realm info
     * @param realm the realm the user is authenticating into
     * @param passcode the value of the hotp
     */
    public LoginCommand( String userId, String password, String realm,
                         String passcode, ApplicationPolicy policy )
    {
        this.userId = userId;
        this.realm = realm;
        this.passcode = passcode;
        this.password = password;
        this.policy = policy;
    }


    /**
     * Logs the user into the system.  Exceptions will contain optional
     * information used to determine if a resync is in effect or if the account
     * is locked out/disabled.
     *
     * @return true if we can authenticate the user, false otherwise
     */
    public boolean execute() throws LoginException
    {
        LoginModule module = new SafehausLoginModule();
        Subject subject = new Subject();
        Map options = new HashMap();
        options.put( SafehausLoginModule.ALLOW_ADMIN, "true" );
        module.initialize( subject, new LoginHandler(), new HashMap(), options );
        boolean result = module.login();
        result &= module.commit();
        Object[] principals = subject.getPrincipals().toArray();
        if ( principals.length > 0 )
        {
            principal = ( SafehausPrincipal ) principals[0];
        }
        return result;
    }


    public SafehausPrincipal getSafehausPrincipal()
    {
        return principal;
    }


    /**
     * Simple handler implementation for this Demo.
     */
    class LoginHandler implements CallbackHandler
    {
        public void handle( Callback[] callbacks )
           throws IOException, UnsupportedCallbackException
        {
            for ( int ii = 0; ii < callbacks.length; ii++ )
            {
                if ( callbacks[ii] instanceof NameCallback )
                {
                    NameCallback ncb = ( NameCallback ) callbacks[ii];
                    ncb.setName( userId );
                }
                else if ( callbacks[ii] instanceof PasswordCallback )
                {
                    PasswordCallback pcb = ( PasswordCallback ) callbacks[ii];
                    pcb.setPassword( password.toCharArray() );
                }
                else if ( callbacks[ii] instanceof RealmCallback )
                {
                    RealmCallback rcb = ( RealmCallback ) callbacks[ii];
                    rcb.setRealm( realm );
                }
                else if ( callbacks[ii] instanceof PolicyCallback )
                {
                    PolicyCallback pcb = ( PolicyCallback ) callbacks[ii];
                    pcb.setPolicy( policy );
                }
                else if ( callbacks[ii] instanceof PasscodeCallback )
                {
                    PasscodeCallback pcb = ( PasscodeCallback ) callbacks[ii];
                    pcb.setPasscode( passcode );
                }
            }
        }
    }
}

If you've already looked at the other tutorials there is nothing all that interesting in here except for the code which extracts and sets the SafehausPrincipal.

The commmand's anatomy and usage is simple. Create it supplying all the parameters passed by the handler to the SafehausLoginModule. All parameters are final since the command instance is used only once and thrown away. After creating it call the execute() method to login. If there are exceptional failures these bubble up with throws. If not and authentication fails false is returned and the there is no principal to extract. If authentication succeeds then true is returned and the Subject's principal is extracted and the principal member is set.

Now let's look at the request handling code in the servlet which uses this command to authenticate users and access their authorization profile.

Servlet Request Handling

Since our servlet will handle both GET and POST operations the same way we create a doAll() and just call that for both the doGet() and doPost() methods like so:

Code Block
titledoGet() and doPost()
protected void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException,
    IOException
{
    doAll( request, response );
}


protected void doPost( HttpServletRequest request, HttpServletResponse response ) throws ServletException,
    IOException
{
    doAll( request, response );
}

In the doAll() method below the values for 3 request parameters are extracted from the request argument:

  • username
  • password
  • passcode

These parameters can be posted from a simple login.html form and packaged into the webapp.

The LoginCommand class comes in handy. We just stuff it with these request parameters, the realm and policy members and call the execute() method. If the result is false an error message is printed out to the output stream.

If the result is true then authentication succeeded and the servlet prints out a success message then it prints out the contents of the principal's authorization profile. The permissions granted, and denied along with the roles (and the permission grants inherited from them) are printed out to the response stream.

Code Block
titledoAll()
protected void doAll( HttpServletRequest request, HttpServletResponse response ) throws ServletException,
    IOException
{
    // get the required parameters for authentication
    String username = ( String ) request.getParameter( "username" );
    String password = ( String ) request.getParameter( "password" );
    String passcode = ( String ) request.getParameter( "passcode" );

    // prepare and execute the login command that wraps the login module
    LoginCommand command = new LoginCommand( username, password, realm, passcode, policy );
    boolean result = false;
    try
    {
        result = command.execute();
    }
    catch ( LoginException e )
    {
        doErrorMessage( request, response, e.getMessage() );
        return;
    }

    if ( result == false )
    {
        doErrorMessage( request, response, "Failed authentication!" );
        return;
    }

    // get the authorization profile of the authenticated user and print it out
    SafehausPrincipal principal = command.getSafehausPrincipal();
    Profile profile = principal.getAuthorizationProfile();
    PrintWriter out = response.getWriter();
    out.println( "<html><body><p><font color=\"green\">Authentication Succeeded</font></p><br/><br/>" );
    out.println( "<h2>Authorization Profile " + profile.getProfileId()
        + " for User " + profile.getUserName() + "</h2>" );

    // print out the grants in the profile
    out.println( "<p>Profile Grants:</p><ul>" );
    Permissions grants = profile.getGrants();
    for ( Iterator ii = grants.iterator(); ii.hasNext(); /**/ )
    {
        out.println( "<li>" + ii.next() + "</li>" );
    }
    out.println( "</ul>" );

    // print out the denials in the profile
    out.println( "<p>Profile Denials:</p><ul>" );
    Permissions denials = profile.getDenials();
    for ( Iterator ii = denials.iterator(); ii.hasNext(); /**/ )
    {
        out.println( "<li>" + ii.next() + "</li>" );
    }
    out.println( "</ul>" );

    // print out the roles the profile puts the user in
    out.println( "<p>Profile Roles:</p><ul>" );
    Roles roles = profile.getRoles();
    for ( Iterator ii = roles.iterator(); ii.hasNext(); /**/ )
    {
        out.println( "<li>" + ii.next() + "</li>" );
    }
    out.println( "</ul>" );
    out.println( "</body></html>" );
}

This is far from a real world application but it demonstrates the mechanics of authenticating a user then accessing the authenticated user's authorization profile. This profile can be stuffed into the user's session for accesses later to permit or deny operations in the application or enable/disable access to resources.

Getting the Code and Running It

The code to this simple servlet demo can be found within the triplsec subversion repository here:

https://svn.safehaus.org/repos/triplesec/trunk/webapp-servlet-demo

You can check this code out and build it like so:

No Format
svn co https://svn.safehaus.org/repos/triplesec/trunk/webapp-servlet-demo
cd webapp-servlet-demo
mvn clean install

Once you've built the war you can take the war file in target/triplesec-servlet-demo.war and deploy it to tomcat or jetty or any other application server. However make sure you have installed Triplesec and the proper realm is used. If you're using a different realm you might want to change the init parameters to suite.

Another way to run the servlet is to launch an interactive unit test which fires up an embedded Triplesec instance along with Jetty. Note that this will startup Triplesec for the EXAMPLE.COM domain so your Kerberos Client Configuration should be set accordingly. To run this UI test issue the following command and try not to chuckle (smile):

No Format
titleRunning Interactive Demo Test
mvn -Dui test