Quantcast
Channel: Imifos' Lucubratory » Glassfish
Viewing all articles
Browse latest Browse all 3

Custom JAAS Realm for Glassfish 3

$
0
0

Building a custom JAAS realm for Glassfish 3 is actually quite straight forward, as long as you know how to do it :) The documentation available around is not very clear and it takes some time to collect the required info on the web and to get it working. So here is a short sum-up of the process.

The first thing to do is to build a realm specific JAR file that is deployed in the Glassfish’s domain directory. This JAR file contains a Realm handler, describing the realm and handling the realm groups, and the ‘LoginModule’ responsible to perform the authentication.

In case of Glassfish 3, we don’t need to develop an entire JAAS solution. We can easily base our solution on some helper classes, as shown hereafter.

Let’s go for the JAR file…

First, the Maven POM (which is generally a sub-module of a project POM, as in this example):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

   <modelVersion>4.0.0</modelVersion>

   <parent>
      <artifactId>myrealm-project</artifactId>
      <groupId>eu.lucubratory.myrealm</groupId>
      <version>1.0-SNAPSHOT</version>
   </parent>

   <artifactId>myrealm-jaas</artifactId>
   <packaging>jar</packaging>

   <name>MyRealm JAAS Module</name>

   <dependencies>

      <!-- 'org.glassfish.security' has a dependency to 'javax.persistence'. However, it is
      currently broken in the java.net and JBoss repositories. To avoid the compilation to
      stop, we get the dependency directly from Eclipse. Later, this explicit dependency might
      be removed. -->
      <dependency>
         <groupId>org.eclipse.persistence</groupId>
         <artifactId>javax.persistence</artifactId>
         <version>2.0.3</version>
         <scope>compile</scope>
      </dependency>

      <dependency>
         <groupId>org.glassfish.security</groupId>
         <artifactId>security</artifactId>
         <version>3.1.1</version>
         <scope>provided</scope>
      </dependency>

   </dependencies>

   <repositories>

       <!-- See above -->
       <repository>
           <id>EclipseLink</id>
           <url>http://download.eclipse.org/rt/eclipselink/maven.repo</url>
       </repository>

       <repository>
           <id>glassfish-repository</id>
           <name>Java.net Repository for Glassfish</name>
           <url>http://download.java.net/maven/glassfish</url>
       </repository>

       <!-- Alternative to the java.net repository -->
       <!--repository>
           <id>glassfish</id>
           <name>Glassfish</name>
           <url>https://repository.jboss.org/nexus/content/repositories/glassfish</url>
       </repository-->

   </repositories>

   <build>
       <plugins>
           <plugin>
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-compiler-plugin</artifactId>
               <version>2.3.2</version>
               <configuration>
                  <source>1.7</source>
                  <target>1.7</target>
               </configuration>
           </plugin>
       </plugins>
   </build>

</project>

Now the Realm handler class:

package eu.lucubratory.myrealm;

import com.sun.appserv.security.AppservRealm;
import com.sun.enterprise.security.auth.realm.BadRealmException;
import com.sun.enterprise.security.auth.realm.InvalidOperationException;
import com.sun.enterprise.security.auth.realm.NoSuchRealmException;
import com.sun.enterprise.security.auth.realm.NoSuchUserException;

import java.util.Enumeration;
import java.util.Properties;
import java.util.Vector;

/**
 * Extends the default implementation provided by "AppservRealm" as requested by
 * com.sun.enterprise.security.auth.realm.Realm in order to make the realm
 * declaration complete.
 */
public class MyRealm extends AppservRealm {

   private static final String JAAS_CONTEXT="jaas-context";

   public MyRealm() {
   }

   /**
    * Initialize a realm with some properties. This can be used
    * when instantiating realms from their descriptions. This
    * method may only be called a single time.
    *
    * @param properties - Key-value pairs defined in the Console's Realm declaration.
    *
    * @exception BadRealmException if the configuration parameters identify a corrupt realm
    * @exception NoSuchRealmException if the configuration parameters specify a realm which doesn't exist
    */
   @Override
   public void init(Properties properties) throws BadRealmException, NoSuchRealmException {

       System.out.println("Init MyRealm");

       // Pass the properties declared in the console to the system
       String propJaasContext=properties.getProperty(JAAS_CONTEXT);
       if (propJaasContext!=null) {
          setProperty(JAAS_CONTEXT, propJaasContext);
       }
   }

   /**
    * Returns a short (preferably less than fifteen characters) description
    * of the kind of authentication which is supported by this realm.
    */
   @Override
   public String getAuthType() {
      return "CustomHardcoded";
   }

   /**
    * Returns the name of all the groups that this user belongs to.
    *
    * @param username name of the user in this realm whose group listing is needed.
    *
    * @return enumeration of group names (strings).
    *
    * @exception InvalidOperationException thrown if the realm does not
    * support this operation - e.g. Certificate realm does not support this
    * operation
    */
   @Override
   public Enumeration getGroupNames(String user) throws InvalidOperationException, NoSuchUserException {

      System.out.println("getGroupNames("+user+")");

      Vector<String> vector = new Vector<>();

      // Get the groups from your DB
      vector.add("group");

      return vector.elements();
   }
}

Having a look in the source code of the Realm class reveals much more things we could do. Most of the tasks are taken charge of by Glassfish’s AppservRealm helper class.

Next task, the ‘LoginModule‘:

package eu.lucubratory.myrealm;

import com.sun.appserv.security.AppservPasswordLoginModule;

import com.sun.enterprise.security.auth.realm.InvalidOperationException;
import com.sun.enterprise.security.auth.realm.NoSuchUserException;

import javax.security.auth.login.LoginException;

import java.util.Enumeration;
import java.util.List;
import java.util.ArrayList;

/**
 * Completes the abstract AppservPasswordLoginModule base class for password-based login modules.
 *
 * Most login modules receive a username and password from the client (possibly through HTTP
 * BASIC auth, or FORM, or other mechanism) and then make (or delegate) an authentication
 * decision based on this data. The AppservPasswordLoginModule class provides common methods
 * for such password-based login modules.
 *
 * This subclasses need to implement the authenticateUser() method and later call
 * commitUserAuthentication().
 */
public class LoginModule extends AppservPasswordLoginModule {

   public LoginModule() {
      System.out.println("MyRealm LoginModule - Construction");
   }

   /**
    * Overrides the authenticateUser() method in AppservPasswordLoginModule
    * Performs authentication of user
    * @throws javax.security.auth.login.LoginException
    */
   @Override
   protected void authenticateUser() throws LoginException {

      System.out.println("MyRealm LoginModule authenticateUser(), _username:"+
             _username+", _password:"+_password+", _currentrealm:"+_currentRealm);

      if (!(_currentRealm instanceof MyRealm)) {
          throw new LoginException("Realm not MyRealm. Check 'login.conf'.");
      }
      MyRealm myRealm=(MyRealm)_currentRealm;

      // Authenticate User
      if (!doAuthentication(_username,_password)) {
          //Login failed
          throw new LoginException("MyRealm LoginModule: Login Failed for user "+_username);
      }

      // Login succeeded
      System.out.println("MyRealm LoginModule: Login Succeded for user "+_username);

      // Get group names for the authenticated user from the Realm class
      Enumeration enumeration=null;
      try {
          enumeration=myRealm.getGroupNames(_username);
      }
      catch(InvalidOperationException e) {
          throw new LoginException("InvalidOperationException was thrown for getGroupNames() on MyRealm");
      }
      catch(NoSuchUserException e) {
          throw new LoginException("NoSuchUserException was thrown for getGroupNames() on MyRealm");
      }

      // Convert the Enumeration to String[]
      List<String> g=new ArrayList<>();
      while(enumeration!=null && enumeration.hasMoreElements())
          g.add((String)enumeration.nextElement());

      String[] authenticatedGroups=g.toArray(new String[g.size()]);

      // Call commitUserAuthentication with the group names the user belongs to.
      // Note that this method is called after the authentication has succeeded.
      // If authentication failed do not call this method. Global instance field
      // succeeded is set to true by this method.
      commitUserAuthentication(authenticatedGroups);
   }

   /**
    * Performs the authentication.
    */
   private boolean doAuthentication(String user, String password) {
      // Implement this.
      return false; // or true :)
   }
}

The result (in this case) is a JAR file named ‘myrealm-jaas-1.0-SNAPSHOT.jar’. This file has to be copied into the domain’s ‘lib‘ directory: ‘…glassfish-3.1.2.2\glassfish\domains\[my domain]\lib‘.

The next step is to register the new realm in Glassfish (for our domain) and to associated the ‘LoginModule’ to invoke for authentication within this realm. For this, we need to edit the file ‘..glassfish-3.1.2.2\glassfish\domains\[my domain]\config\login.conf‘ and add the following lines at the end:

myRealm {
   eu.lucubratory.myrealm.LoginModule required;
};

Now the server is ready to be started and we can install the new realm in the Console: ‘Configurations, server-config, Security, Realms‘ – ‘New Realm‘.

Note that all parameters are important. Set ‘Name’ to ‘my-realm’ and ‘Class name’ to  ‘eu.lucubratory.myrealm.MyRealm’. The name is actually what is referenced in ‘web.xml’ and the class name is the completely qualified identification of the realm description class.

Unfortunately, custom realms are not added by Glassfish to the drop-down list, so we need to use the edit box to specify the complete class name.

Next, we add the (additional) property ‘jaas-context’ with the value ‘myRealm’ as specified in ‘login.conf’. As soon as this gets confirmed by hitting the Save button, you get either an error message (hopefully not) or the console output that shows something like:

INFO: Admin Console: Initializing Session Attributes...
INFO: Init MyRealm (-> that's our out.println)
INFO: SEC1115: Realm [myreal-realm] of classtype [eu.lucubratory.myrealm.MyRealm] successfully created.

If the ‘jaas-context’ variable is wrong, Glassfish will throw the following exception when you try to login later: “com.sun.enterprise.security.auth.login.common.LoginException: Login failed: No LoginModules configured for [Value of jaas-context property]“. This indicates that the server cannot make the link between what is defined in the console and what is in the ‘login.conf’ file.

The last step is to configure our web application to use the new realm. To do so, we add the following in the ‘web.xml’ file.

<login-config>
   <auth-method>BASIC</auth-method>
   <realm-name>my-realm</realm-name>   (name as defined in the console!)
</login-config>

And then, somewhere in a login action (depending on the employed front-end technology)…

:
void login(HttpServletRequest request) {
   :
   request.login(username,password);
   :
}
:

The dependency chain to understand is:

  1. HttpServletRequest.login
  2. web.xml <login-config> <realm-name> pointing to
  3. domain setting (domains\[my domain]\config|domain.xml) realm name pointing to
  4. class describing the realm AND property ‘jaas-context’ parameter pointing TO ‘login.conf’
  5. ‘login.conf’ pointing to the ‘LoginModule’ class to use for authentication

Voila, that’s all… easy, isn’t it :)


Viewing all articles
Browse latest Browse all 3

Trending Articles