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 wayConversationState.setCurrent(conversationState); .... ConversationState.getCurrent();
conversationRegistry.register("key", state); ... conversationRegistry.getState("key");
<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; }
<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.
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);- LoginModule.logout() can be called by JAASConversationStateListener, it is extends of ConversationStateListener.
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); }
on 08/08/2008 at 10:40