JBoss.orgCommunity Documentation

Chapter 62. Security Service

62.1. 1 Overview
62.2. 1 Framework
62.2.1. 1.1 ConversationState and ConversationRegistry
62.2.2. 1.1 Authenticator
62.3. Usage
62.3.1. JAAS login module
62.3.2. 1.1 Predefinded JAAS login modules
62.3.3. 1.1 J2EE container authentication

The purpose is to make a simple, unified way for the authentication and the storing/propagation of user sessions through all the eXo components and J2EE containers. JAAS is supposed to be the primary login mechanism but the Security Service framework should not prevent other (custom or standard) mechanisms from being used. You can learn more about JAAS in the Java Tutorial

The central point of this framework is the ConversationState object which stores all information about the state of the current user (very similar to the Session concept). The same ConversationState also stores acquired attributes of an Identity which is a set of principals to identify a user.

The ConversationState has definite lifetime. This object should be created when the user's identity becomes known by eXo (login procedure) and destroyed when the user leaves an eXo based application (logout procedure). Using JAAS it should happen in LoginModule's login() and logout() methods respectively.

An Authenticator is responsible for Identity creating, it contains two methods:

public interface Authenticator {
  /**
   * Authenticate user and return userId which can be different to username. 
   * @param credentials - list of users credentials (such as name/password, X509 certificate etc)
   * @return userId
   * @throws LoginException
   * @throws Exception
   */
  String validateUser(Credential[] credentials) throws LoginException, Exception;

  /**
   * @param credentials - userId.
   * @return Identity
   * @throws Exception
   */
  Identity createIdentity(String userId) throws Exception;  
}

It is up to the application developer (and deployer) whether to use the Authenticator component(s) and how many implementations of this components should be deployed in eXo container. The developer is free to create an Identity object using a different way, but the Authenticator component is the highly recommended way from architectural considerations.

Typical functionality of the validateUser(Credential\[] credentials) method is the comparison of incoming credentials (username/password, digest etc) with those credentials that are stored in an implementation specific database. Then, validateUser(Credential\[] credentials) returns back the userId or throws a LoginException in a case of wrong credentials.

Default Authenticator implementation is org.exoplatform.services.organization.auth.OrganizationAuthenticatorImpl which compares incoming username/password credentials with the ones stored in OrganizationService. Configuration example:

<component>
  <key>org.exoplatform.services.security.Authenticator</key> 
  <type>org.exoplatform.services.organization.auth.OrganizationAuthenticatorImpl</type>
</component>

The framework described is not coupled with any authentication mechanism but the most logical and implemented by default is the JAAS Login module. The typical sequence looks as follows (see org.exoplatform.services.security.jaas.DefaultLoginModule):

Authenticator authenticator = (Authenticator) container()
          .getComponentInstanceOfType(Authenticator.class); 
// RolesExtractor can be null     
RolesExtractor rolesExtractor = (RolesExtractor) container().
getComponentInstanceOfType(RolesExtractor.class);


Credential[] credentials = new Credential[] {new UsernameCredential(username), new PasswordCredential(password) };
String userId = authenticator.validateUser(credentials);
identity = authenticator.createIdentity(userId);

When initializing the login module, you can set the option parameter "singleLogin". With this option you can disallow the same Identity to login for a second time.

By default singleLogin is disabled, so the same identity can be registered more than one time. Parameter can be passed in this form singleLogin=yes or singleLogin=true.

IdentityRegistry identityRegistry = (IdentityRegistry) getContainer().getComponentInstanceOfType(IdentityRegistry.class);
      
if (singleLogin && identityRegistry.getIdentity(identity.getUserId()) != null) 
  throw new LoginException("User " + identity.getUserId() + " already logined.");

identity.setSubject(subject);
identityRegistry.register(identity);

In the case of using several LoginModules, JAAS allows to place the login() and commit() methods in different REQUIRED modules.

After that, the web application must use SetCurrentIdentityFilter. This filter obtains the ConversationRegistry object and tries to get the ConversationState by sessionId (HttpSession). If there is no ConversationState, then SetCurrentIdentityFilter will create a new one, register it and set it as current one using ConversationState.setCurrent(state).

This listener must be configured in web.xml. The method sessionDestroyed(HttpSessionEvent) is called by the ServletContainer. This method removes the ConversationState from the ConversationRegistry ConversationRegistry.unregister(sesionId) and calls the method LoginModule.logout().

ConversationRegistry conversationRegistry = (ConversationRegistry) getContainer().getComponentInstanceOfType(ConversationRegistry.class);

ConversationState conversationState = conversationRegistry.unregister(sesionId);

if (conversationState != null) {
  log.info("Remove conversation state " + sesionId);
  if (conversationState.getAttribute(ConversationState.SUBJECT) != null) {
    Subject subject = (Subject) conversationState.getAttribute(ConversationState.SUBJECT); 
    LoginContext ctx = new LoginContext("exo-domain",  subject);
    ctx.logout();
} else {
  log.warn("Subject was not found in ConversationState attributes.");
}