The Goal

The purpose is to make simple, unified way for authentication and storing/propagation of user session through all the eXo components and J2EE containers. JAAS is supposed as primary login mechanism but the Sesurity Service framework should not prevent other (custom or standard) mechanism to be used.

Framework

The central point of framework are ConversationState which stores all information about current user's state (very close to Session concept) which stores acquired attributes of some Identity - set of Principals to identify some User.

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

ConversationState and ConversationRegistry

Those ConversationState can be stored inside ConversationRegistry component as key-value pairs, where session's key it is an arbitrary String (user name, ticket id, httpSessionId etc) as well as in a static thread local variable, which makes possible represent it as a context (current user's state). One or another, or both methods can be used to set/retrieve the state runtime, the most important thing is that they should be complimentary, i.e. make sure that something set the conversation state before you try to use it

thread-local way

ConversationState.setCurrent(conversationState);
  ....
  ConversationState.getCurrent();

AND/OR key-value way

conversationRegistry.register("key", state); 
  ...
  conversationRegistry.getState("key");

ConversationRegistry is a mandatory component deployed into eXo Container as following

<component>
    <type>org.exoplatform.services.security.ConversationRegistry</type>
  </component>

Authenticator

An Authenticator is responsible for Identity creating, it contains two methods, validateUser() which accepts an array of credentials and returns userId (it can be something different from username). Other method createIdentity() which acceptss userId and returns newly created Identity object.

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 application developer (deployer) whether to use the Authenticator component(s) and how many implementations of this components should be deployed in eXo container. Developer if free to make Identity using other way but that is highly recommended way from architectural point.

Typical functionality of validateUser(Credential[] credentials) method is comparison of incoming credentials (username/password, digest etc) with stored in implementation specific database and returning back the userId or throwing LoginException in a case of wrong credentials.

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

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

Using

JAAS login module

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

  • LoginModule.login() creates list of credentials using standard JAAS Callbacks features, obtains an Authenticator instance, and creates Identity object calling Authenticator.authenticate(..) method
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);

  • LoginModule.commit() obtains the IdentityRegistry object, and register created earlier identity using userId as a key.
Login module can get as option parameter "singleLogin" which can disable or enable one more registraition of Identity. By default singleLogin is disable, so Identity can be registered few times. Parameter can be pass in few 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 a case of using several LoginModules, as it possible by JAAS, the login() and commit() methods can be placed in different REQUIRED modules.

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

  • LoginModule.logout() can be called by JAASConversationStateListener, it is extends of ConversationStateListener.
This listener must be configured in web.xml. Method sessionDestroyed(HttpSessionEvent) is called by ServletContainer. This method remove ConversationState from ConversationRegistry ConversationRegistry.unregister(sesionId) and call 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.");
}

J2EE container authentication

It is important to know and follow the rules regarding Subject filling which are specific for each J2EE server, where eXo Platform is deployed.

To make it workable for the particular J2EE server it is necessary to add specific Principals/Credentials to the Subject to be propagated into specific J2EE container implementation. We extended the DefaultLoginModule overloading it commit() method with dedicated logic, for the time accessible for Tomcat, JBOSS and JONAS application servers.

Another optional thing for J2EE environment is RolesExtractor which is responsible for mapping primary Subject's principals (userId and set of groups) to J2EE Roles:

public interface RolesExtractor {
  Set <String> extractRoles(String userId, Set<MembershipEntry> memberships);
}

This component may be used by Authenticator to create the Identity with particular set of Roles.

 
Navigation

Creator: Gennady Azarenkov on 2008/03/02 10:32
Copyright (c) 2000-2009. Allright reserved - eXo platform SAS
1.6.13286