Adding custom user stores to security realms.

Within JBoss AS7 for communication with the management interfaces and for other services exposed using Remoting where username / password authentication is used the use of Digest authentication is preferred over the use of HTTP Basic or SASL Plain so that we can avoid the sending of password in the clear over the network.  For validation of the digests to work on the server we either need to be able to retrieve a users plain text password or we need to be able to obtain a ready prepared hash of their password along with the username and realm.

 

Previously to allow the addition of custom user stores we have added an option to the realms to call out to a JAAS domain to validate a users username and password, the problem with this approach is that to call JAAS we need the remote user to send in their plain text username and password so that a JAAS LoginModule can perform the validation, this forces us down to use either the HTTP Basic authentication mechanism or the SASL Plain mechanism depending on the transport used which is undesirable as we can not longer use Digest.

 

To overcome this we now support plugging in custom user stores to support loading a users password, hash and roles from a custom store to allow different stores to be implemented without forcing the authentication back to plain text variant, this article describes the requirements for a plug in and shows a simple example plug-in for use with AS7.

 

Implementing a Plug In

 

When implementing a plug in there are two steps to the authentication process, the first step is to load the users identity and credential from the relevent store - this is then used to verify the user attempting to connect is valid.  After the remote user is validated we then load the users roles in a second step.  For this reason the support for plug-ins is split into the two stages, when providing a plug-in either of these two steps can be implemented but there is no requirement to implement the other side.

 

When implementing a plug-in the following interfaces are the bare minimum that need to be implemented so depending on if a plug-in to load a users identity or a plug-in to load a users roles is being implemented you will be implementing one of these interfaces.

 

Note - All classes and interfaces of the SPI to be implemented are in the 'org.jboss.as.domain.management.plugin' package which is a part of the 'org.jboss.as.domain-management' module but for simplicity for the rest of this article only the short names will be shown.

 

AuthenticationPlugIn

To implement an AuthenticationPlugIn the following interface needs to be implemened: -

 

public interface AuthenticationPlugIn<T extends Credential> {
    Identity<T> loadIdentity(final String userName, final String realm) throws IOException; 
}

 

During the authenication process this method will be called with the user name supplied by the remote user and the name of the realm they are authenticating against, this method call represents that an authentication attempt is occurring but it is the Identity instance that is returned that will be used for the actual authentication to verify the remote user.

 

The Identity interface is also an interface you will implement: -

 

public interface Identity<T extends Credential> { 
    String getUserName(); 
    T getCredential();
}

 

Additional information can be contained within the Identity implementation although it will not currently be used, the key piece of information here is the Credential that will be returned - this needs to be one of the following: -

 

PasswordCredential

public final class PasswordCredential implements Credential { 
    public PasswordCredential(final char[] password);
    public char[] getPassword();
    void clear();
}

 

 

 

The PasswordCredential is already implemented so use this class if you have the plain text password of the remote user, by using this the secured interfaces will be able to continue using the Digest mechanism for authentication.

 

DigestCredential

public final class DigestCredential implements Credential {
    public DigestCredential(final String hash);
    public String getHash();
}

This class is also already implemented and should be returned if instead of the plain text password you already have a pre-prepared hash of the username, realm and password.

 

ValidatePasswordCredential

public interface ValidatePasswordCredential extends Credential {
    boolean validatePassword(final char[] password);
}

This is a special Credential type to use when it is not possible to obtain either a plain text representation of the password or a pre-prepared hash - this is an interface as you will need to provide an implementation to verify a supplied password.  The down side of using this type of Credential is that the authentication mechanism used at the transport level will need to drop down from Digest to either HTTP Basic or SASL Plain which will now mean that the remote client is sending their credential across the network in the clear.

 

If you use this type of credential be sure to force the mechanism choice to Plain as described in the configuration section below.

 

AuthorizationPlugIn

If you are implementing a custom mechanism to load a users roles you need to implement the AuthorizationPlugIn

 

public interface AuthorizationPlugIn {
    String[] loadRoles(final String userName, final String realm) throws IOException;
}

 

As with the AuthenticationPlugIn this has a single method that takes a users userName and realm - the return type is an array of Strings with each entry representing a role the user is a member of.

 

PlugInConfigurationSupport

In addition to the specific interfaces above there is an additional interface that a plug-in can implement to recieve configuration information before the plug-in is used and also to recieve a Map instance that can be used to share state between the plug-in instance used for the authentication step of the call and the plug-in instance used for the authorization step.

 

public interface PlugInConfigurationSupport {
    void init(final Map<String, String> configuration, final Map<String, Object> sharedState) throws IOException;
}

 

Installing and Configuring a Plug-In

The next step of this article describes the steps to implement a plug-in provider and how to make it available within AS7 and how to configure it.  A simple Eclipse / Ant based project is attach containing the example used to illustrate this.

 

The following is an example security realm definition which will be used to illustrate this: -

 

<security-realm name="PlugInRealm">
   <plug-ins>
      <plug-in module="org.jboss.as.sample.plugin"/>
   </plug-ins>
   <authentication>
      <plug-in name="Sample">
         <properties>
            <property name="darranl.password" value="dpd"/>
            <property name="darranl.roles" value="Admin,Banker,User"/>
         </properties>
      </plug-in>
   </authentication>
   <authorization>
      <plug-in name="Delegate" />
   </authorization>
</security-realm>

 

Before looking closely at the packaging and configuration there is one more interface to implement and that is the PlugInProvider interface, that interface is responsible for making PlugIn instances available at runtime to handle the requests.

 

PlugInProvider

public interface PlugInProvider {
    AuthenticationPlugIn<Credential> loadAuthenticationPlugIn(final String name);
    AuthorizationPlugIn loadAuthorizationPlugIn(final String name);
}

 

These methods are called with the name that is supplied in the plug-in elements that are contained within the authentication and authorization elements of the configuration, based on the sample configuration above the loadAuthenticationPlugIn method will be called with a parameter of 'Sample' and the loadAuthorizationPlugIn method will be called with a parameter of 'Delegate'.

 

Multiple plug-in providers may be available to the application server so if a PlugInProvider implementation does not recognise a name then it should just return null and the server will continue searching the other providers.  If a PlugInProvider does recognise a name but fails to instantiate the PlugIn then a RuntimeException can be thrown to indicate the failure.

 

As a server could have many providers registered it is recommended that a naming convention including some form of hierarchy is used e.g. use package style names to avoid conflicts.

 

For the sample project the implementation is as follows: -

 

public class SamplePluginProvider implements PlugInProvider {

    public AuthenticationPlugIn<Credential> loadAuthenticationPlugIn(String name) {
        if ("Sample".equals(name)) {
            return new SampleAuthenticationPlugIn();
        }
        return null;
    }

    public AuthorizationPlugIn loadAuthorizationPlugIn(String name) {
        if ("Sample".equals(name)) {
            return new SampleAuthenticationPlugIn();
        } else if ("Delegate".equals(name)) {
            return new DelegateAuthorizationPlugIn();
        }
        return null;
    }
}

 

The load methods are called for each authentication attempt but it will be an implementation detail of the provider if it decides to return a new instance of the provider each time - in this scenario as we also use configuration and shared state then new instances of the implementations make sense.

 

To load the provider use a ServiceLoader so within the META-INF/services folder of the jar this project adds a file called 'org.jboss.as.domain.management.plugin.PlugInProvider' - this contains a single entry which is the fully qualified class name of the PlugInProvider implementation class.

 

org.jboss.as.sample.SamplePluginProvider

 

Package as a Module

To make the PlugInProvider available to the application it is bundled as a module and added to the modules already shipped with AS7.

 

To add as a module we first need a module.xml: -

 

<?xml version="1.0" encoding="UTF-8"?>

<module xmlns="urn:jboss:module:1.1" name="org.jboss.as.sample.plugin">
    <properties>
    </properties>

    <resources>
        <resource-root path="SamplePlugIn.jar"/>
    </resources>

    <dependencies>
        <module name="org.jboss.as.domain-management" />
    </dependencies>
</module>

 

The interfaces being implemented are in the 'org.jboss.as.domain-management' module so a dependency on that module is defined, this module.xml is then placed in the '{jboss.home}/modules/org/jboss/as/sample/plugin/main'.

 

The compiled classed and META-INF/services as described above are assmbled into a jar called SamplePlugIn.jar and also placed into this folder.

 

Looking back at the sample configuration at the top of the realm definition the following element was added: -

 

   <plug-ins>
      <plug-in module="org.jboss.as.sample.plugin"/>
   </plug-ins>

 

This element is used to list the modules that should be searched for plug-ins.  As plug-ins are loaded during the server start up this search is a lazy search so don't expect a definition to a non existant module or to a module that does not contain a plug-in to report an error.

 

The AuthenticationPlugIn

The AuthenticationPlugIn is implemented as: -

 

public class SampleAuthenticationPlugIn extends AbstractPlugIn {

    private static final String PASSWORD_SUFFIX = ".password";
    private static final String ROLES_SUFFIX = ".roles"; 
    private Map<String, String> configuration;

    public void init(Map<String, String> configuration, Map<String, Object> sharedState) throws IOException {
        this.configuration = configuration;
        // This will allow an AuthorizationPlugIn to delegate back to this instance.
        sharedState.put(AuthorizationPlugIn.class.getName(), this);
    }

    public Identity loadIdentity(String userName, String realm) throws IOException {
        String passwordKey = userName + PASSWORD_SUFFIX;
        if (configuration.containsKey(passwordKey)) {
            return new SampleIdentity(userName, configuration.get(passwordKey));
        }
        throw new IOException("Identity not found.");
    }

    public String[] loadRoles(String userName, String realm) throws IOException {
        String rolesKey = userName + ROLES_SUFFIX;
        if (configuration.containsKey(rolesKey)) {
            String roles = configuration.get(rolesKey);
            return roles.split(",");
        } else {
            return new String[0];
        }
    }

    private static class SampleIdentity implements Identity {
        private final String userName;
        private final Credential credential;

        private SampleIdentity(final String userName, final String password) {
            this.userName = userName;
            this.credential = new PasswordCredential(password.toCharArray());
        }

        public String getUserName() {
            return userName;
        }

        public Credential getCredential() {
            return credential;
        }
    }
}

 

As you can see from this implementation there is also an additional class being extended AbstractPlugIn - that is simply an abstract class that implements the AuthenticationPlugIn, AuthorizationPlugIn, and PlugInConfigurationSupport interfaces already.  The properties that were defined in the confuguration are passed in as a Map and importantly for this sample the plug-in adds itself to the shared state map.

 

The AuthorizationPlugIn

The implementation of the authentication plug in is as follows: -

public class DelegateAuthorizationPlugIn extends AbstractPlugIn {

    private AuthorizationPlugIn authorizationPlugIn;

    public void init(Map<String, String> configuration, Map<String, Object> sharedState) throws IOException {
        authorizationPlugIn = (AuthorizationPlugIn) sharedState.get(AuthorizationPlugIn.class.getName());
    }

    public String[] loadRoles(String userName, String realm) throws IOException {
        return authorizationPlugIn.loadRoles(userName, realm);
    } 

}

 

This plug-in illustrates how two plug-ins can work together, by the AuthenticationPlugIn placing itself in the shared state map it is possible for the authorization plug-in to make use of it for the loadRoles implementation.

 

Another option to consider to achieve similar behaviour could be to provide an Identity implementation that also contains the roles and place this in the shared state map - the AuthorizationPlugIn can retrieve this and return the roles.

 

Forcing Plain Text Authentication

As mentioned earlier in this article if the ValidatePasswordCredential is going to be used then the authentication used at the transport level needs to be forced from Digest authentication to plain text authentication, this can be achieved by adding a mechanism attribute to the plug-in definition within the authentication element i.e.

 

  <authentication>
    <plug-in name="Sample" mechanism="PLAIN">