JBoss.orgCommunity Documentation
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.
The ConversationState can be stored
In a static local thread variable, or
As a key-value pair in the ConversationRegistry component.
One or the other, or both methods can be used to set/retrieve the state at runtime. The most important thing is that they should be complementary, i.e. make sure that the conversation state is set before you try to use it.
Local Thread Variable: Storing the ConversationState in a static local thread variable makes it possible to represent it as a context (current user's state).
ConversationState.setCurrent(conversationState); .... ConversationState.getCurrent();
Key-Value way
If you store the ConversationState inside the ConversationRegistry component as a set of key-value pairs, the session key is an arbitrary String (user name, ticket id, httpSessionId etc).
conversationRegistry.register("key", conversationState); ... conversationRegistry.getState("key");
ConversationRegistry The ConversationRegistry is a mandatory component deployed into eXo Container as following:
<component> <type>org.exoplatform.services.security.ConversationRegistry</type> </component>
An Authenticator is responsible for Identity creating, it contains two methods:
validateUser() accepts an array of credentials and returns the userId (which can be something different from the username).
createIdentity() accepts the userId and returns a 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 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):
LoginModule.login() creates a list of credentials using standard JAAS Callbacks features, obtains an Authenticator instance, and creates an 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 the identity using userId as a key.
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).
LoginModule.logout() can be called by JAASConversationStateListener, it extends ConversationStateListener.
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."); }
There are several JAAS Login modules included in eXo Platform sources:
org.exoplatform.services.security.jaas.DefaultLoginModule which provides both authentication (using eXo Authenticator based mechanism) and authorization, filling Conversation Registry as it described in previous section. There are also several per-Application Server extensions of this login module which can be found in org.exoplatform.services.security.jaas package, which can be used in appropriate AS. In particular, we have dedicated Login modules for Tomcat, JBoss, Jonas and WebSphere.
Besides that, for the case when third party authentication mechanism required, we have org.exoplatform.services.security.jaas.IdentitySetLoginModule, which catches a login identity from third party "authenticating" login module and preforms eXo specific authorization job. In this case third party login module has to put login (user) name to the shared state map under "javax.security.auth.login.name" key and third party LM has to be configured before IdentitySetLoginModule like:
exo { com.third.party.LoginModuleImpl required; org.exoplatform.services.security.jaas.IdentitySetLoginModule required; };
As you know, when a user in JAAS is authenticated, a Subject is created as a result. This Subject represents the authenticated user. 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 the specific J2EE container implementation. We extended the DefaultLoginModule by overloading its commit() method with a dedicated logic, presently available for Tomcat, JBOSS and JONAS application servers.
Furthermore, you can use the optional RolesExtractor which is responsible for mapping primary Subject's principals (userId and a 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 a particular set of Roles.