This post describes a way to authenticate and authorize users in EAR application. This is nothing special but there could be some problems with respect to the requirements in place. So the tasks that shall be solved are the following:

 

  • User can access system via WUI by logging in.
  • Other applications can access system via WebService. Account used for accessing must be in specific role.
  • User credentials are in database.
  • Seam (version 2) is used.
  • Custom security domain is defined.
  • Authentication and authorization is covered by JAAS

 


Custom security domain definition

To define new security domain, there is a dedicated configuration file in JBoss, located in $JBOSS_HOME/server/$SERVER_PROFILE/conf/login-config.xml. It is possible to define new security domain there or in separate configuration file that gets then shipped together with EAR. We use here the second alternative. So the configuration file, named e.g. custom-login-config-beans.xml is then packed in EAR's META-INF folder.

 

<?xml version="1.0" encoding="UTF-8"?>
<deployment xmlns="urn:jboss:bean-deployer:2.0">
    <application-policy xmlns="urn:jboss:security-beans:1.0" name="CustomSecurityDomain">
         <authentication>
            <login-module code="com.example.CustomAuthModule"
                flag="required">
                <module-option name="entityManagerFactoryJndiName">java:/EntityManagerFactory</module-option>
                <module-option name="unauthenticatedIdentity">anonymous</module-option>
                <module-option name="hashAlgorithm">md5</module-option>
            </login-module>
        </authentication>
    </application-policy>
</deployment>

 

This configuration file defines new security domain - CustomSecurityDomain. It declares the class that provides facilities for this security domain - com.example.CustomAuthModule. It declares JNDI name of the EntityManagerFactory that provides persistence facilty used to get credentials. It declares identity of unauthenticated user (some resources might be accessible also for unauthenticated users). And it declares hash algorithm used to disable access to plain passwords.

 

The com.example.CustomAuthModule itself

To implement custom authentication module that could access user credentials in dozens of exotic places, we need a class that inherits from org.jboss.security.auth.spi.AbstractServerLoginModule. In this particular case, as we have credentials in database, we will inherit from org.jboss.security.auth.spi.UsernamePasswordLoginModule which is more appropriate. The code of CustomAuthModule is the following:

 

 

package com.example;

import java.security.Principal;
import java.security.acl.Group;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;

import com.example.UserAccount;
import com.example.UserRole;

import org.jboss.security.SimpleGroup;
import org.jboss.security.auth.spi.UsernamePasswordLoginModule;

public class AuthModule extends UsernamePasswordLoginModule { 
    private EntityManager entityManager;
    private static final String EMF_JNDI_CONFIG_KEY = "entityManagerFactoryJndiName";

    /**
     * Initialize this LoginModule.
     */
    public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
        super.initialize(subject, callbackHandler, sharedState, options);
        InitialContext ctx;
        try {
            ctx = new InitialContext();
            // Get the name of EMF JNDI
            String jndiEntityManagerFactory = options.get(EMF_JNDI_CONFIG_KEY).toString();
            EntityManagerFactory factory = null;
            factory = (EntityManagerFactory) ctx.lookup(jndiEntityManagerFactory);
            entityManager = factory.createEntityManager();
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected String getUsersPassword() throws LoginException {
        String username = getIdentity().getName();
        UserAccount user = (UserAccount) entityManager.createQuery("from UserAccount where username = :username").setParameter("username", username)
                .getSingleResult();
        return user.getPasswordHash();
    }

    @Override
    protected Group[] getRoleSets() throws LoginException {
        HashMap<String, Group> groupMap = new HashMap<String, Group>();
 
        String username = getIdentity().getName();
        UserAccount user = (UserAccount) entityManager.createQuery("from UserAccount where username = :username").setParameter("username", username)
                .getSingleResult();
 
        String defaultGroupName = "Roles";
        Group defaultGroup = new SimpleGroup(defaultGroupName);
        setsMap.put(defaultGroupName, defaultGroup);

        List<UserRole> roles = user.getRoles();
        for (UserRole userRole : roles) {
            Principal p;
            try {
                p = createIdentity(userRole.getRolename());
                defaultGroup.addMember(p);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
 
        Group[] roleSets = new Group[setsMap.size()];
        groupMap.values().toArray(roleSets);
        return roleSets;
    }
}

 

We need to override three methods

  • initialize - which initiates the authentication module to the JAAS infrastructure and populates reference to the entity manager via entity manager factory declared in configuration. Note, that to make use of entity manager you have to provide appropriate configuration that is not part of this post.
  • getUsersPassword - retrieves user password from database using name that is available via getIdentity method.
  • getRoleSets - gets roles of the user. This example considers only one level of roles (meaning that role cannot contain group of other roles).

 

The classes UserAccount and UserRole are simple entity classes that consist of rolename in case of UserRole and username, passwordHash and list of type UserRole in case of UserAccount.

Enable new authentication module in Seam

To allow Seam to make use of above defined custom authentication module, we need to make certain configuration in components.xml that is shippen in WEB module of the EAR. The necessary configuration is the following.

 

 

<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://jboss.com/products/seam/components"
    xmlns:security="http://jboss.com/products/seam/security"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.2.xsd http://jboss.com/products/seam/security http://jboss.com/products/seam/security-2.2.xsd">
    <!-- other configuration -->
    <security:identity jaas-config-name="CustomSecurityDomain" />
</components>

 

This informs Seam to use JAAS as an authentication mechanism and names security domain that shall be used for this purpose.

Login page

The login page is very simple and uses embedded Seam Identity. The code is self-explanatory.

 

<h:form id="loginForm">  
    <div>
        <h:outputLabel for="username">Username</h:outputLabel>
        <h:inputText id="username" value="#{credentials.username}"/>
    </div>
    <div>
        <h:outputLabel for="password">Password</h:outputLabel>
        <h:inputSecret id="password" value="#{credentials.password}"/>
    </div>
    <div>
        <h:commandButton id="submit" value="Login" action="#{identity.login}"/>
    </div>
</h:form>

 

To refresh memory. #{credentials.username} is EL reference to Seam component named credentials and its property username. (The same applies to #{credentials.password}). #{identity.login} is EL execution of method login on Seam built-in component identity.

 

WebService

Finally, we wanted to have a WebService that will share the same authentication mechanism as WUI. With help of annotations, this is quite straightforward and no extensive configuration is needed. Here is example WebService that will make use of CustomSecurityDomain and will allow to execute methods only users from group external. The code again does not require any closer explanation as the annotations are more than self-descriptive.

 

 

package com.example;

import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;

import org.jboss.ejb3.annotation.SecurityDomain;
import org.jboss.wsf.spi.annotation.WebContext;

@WebContext(contextRoot = "/webservices", urlPattern = "/TestService", secureWSDLAccess = false)
@SecurityDomain(value = "CustomSecurityDomain")
@RolesAllowed("external")
@SOAPBinding(style = SOAPBinding.Style.DOCUMENT, use = SOAPBinding.Use.LITERAL, parameterStyle = SOAPBinding.ParameterStyle.WRAPPED)
@WebService(name = "TestService", targetNamespace = "urn:com:example", endpointInterface = "com.example.TestService")
@Stateless
public class TestServiceSession implements TestService {
    // WebMethods from TestService interface that gets generated from WSDL
}

 

By this you are able to reuse authentication and authorization facility in both web user interface and also in backend webservices. By providing different implementation of security domain, you can change the source of your user credentials.