How Servlet containers all implement identity stores differently
The authentication mechanism is responsible for interacting with the caller and the environment. E.g. it causes a UI to be rendered that asks for details such as a username and password, and after a postback retrieves these from the request. As such it's roughly equivalent to a controller in the MVC architecture.
Java EE has standardised 4 authentication mechanisms for a Servlet container, as well as a JASPIC API profile to provide a custom authentication mechanism for Servlet (and one for SOAP, but let's ignore that for now). Unfortunately standard custom mechanisms are only required to be supported by a full Java EE server, which means the popular web profile and standalone servlet containers are left in the dark. [update: since EE 8 Web Profile supports JASPIC too]
Servlet vendors can adopt the standard API if they want and the Servlet spec even encourages this, but in practice few do so developers can't depend on this. (Spec text is typically quite black and white. *Must support* means it's there, anything else like *should*, *is encouraged*, *may*, etc simply means it's not there) [update: since Tomcat 8.5, Tomcat supports JASPIC too, as does Jetty, so now all current servers do support JASPIC]
The following table enumerates the standard options:
- Basic
- Digest (encouraged to be supported, not required)
- Client-cert
- Form
- Custom/JASPIC (encouraged for standalone/web profile Servlet containers, required for full profile Servlet containers)
The identity store on its turn is responsible for providing access to a storage system where caller data and credentials are stored. E.g. when being given a valid caller name and password as input it returns a (possibly different) caller name and zero or more groups associated with the caller. As such it's roughly equivalent to a model in the MVC architecture; the identity store knows nothing about its environment and does not interact with the caller. It only performs the {credentials in, caller data out} function.
Identity stores are somewhat shrouded in mystery, and not without reason. Java EE has not standardised any identity store, nor has it really standardised any API or interface for them. There is a bridge profile for JAAS LoginModules, which are arguably the closest thing to a standard interface, but JAAS LoginModules can not be used in a portable way in Java EE since essential elements of them are not standardised. Furthermore, this bridge profile can only be used for custom authentication mechanisms (using JASPIC), which is itself only guaranteed to be available for Servlet containers that reside within a full Java EE server as mentioned above.
What happens now is that every Servlet container provides a proprietary interface and lookup method for identity stores. Nearly all of them ship with a couple of default implementations for common storage systems that the developer can choose to use. The most common ones are listed below:
- In-memory (properties file/xml file based)
- Database (JDBC/DataSource based)
- LDAP
As a direct result of not being standardised, not only do Servlet containers provide their own implementations, they also each came up with their own names. Up till now no less than 16(!) terms were discovered for essentially the same thing:
- authenticator
- authentication provider
- authentication repository
- authentication realm
- authentication store
- identity manager
- identity provider
- identity store
- login module
- login service
- realm
- relying party
- security policy domain
- security domain
- service provider
- user registry
Following a vote in the EG for the new Java EE security JSR, it was decided to use the term "identity store" going forward. This is therefor also the term used in this article.
To give an impression of how a variety of servlet containers have each implemented the identity store concept we analysed a couple of them. For each one we list the main interface one has to implement for a custom identity store, and if possible an overview of how the container actually uses this interface in an authentication mechanism.
The servlet containers and application servers containing such containers that we've looked at are given in the following list. Each one is described in greater detail below.
Tomcat
Tomcat calls its identity store "Realm". It's represented by the interface shown below:
public interface Realm { Principal authenticate(String username); Principal authenticate(String username, String credentials); Principal authenticate(String username, String digest, String nonce, String nc, String cnonce, String qop, String realm, String md5a2); Principal authenticate(GSSContext gssContext, boolean storeCreds); Principal authenticate(X509Certificate certs[]); void backgroundProcess(); SecurityConstraint [] findSecurityConstraints(Request request, Context context); boolean hasResourcePermission(Request request, Response response, SecurityConstraint[] constraint, Context context) throws IOException; boolean hasRole(Wrapper wrapper, Principal principal, String role); boolean hasUserDataPermission(Request request, Response response, SecurityConstraint[] constraint) throws IOException; void addPropertyChangeListener(PropertyChangeListener listener); void removePropertyChangeListener(PropertyChangeListener listener); Container getContainer(); void setContainer(Container container); CredentialHandler getCredentialHandler(); void setCredentialHandler(CredentialHandler credentialHandler); }
According to the documentation, "A Realm [identity store] is a "database" of usernames and passwords that identify valid users of a web application (or set of web applications), plus an enumeration of the list of roles associated with each valid user."
Tomcat's bare identity store interface is rather big as can be seen. In practice though implementations inherit from RealmBase, which is a base class (as its name implies). Somewhat confusingly its JavaDoc says that it's a realm "that reads an XML file to configure the valid users, passwords, and roles".
The only methods that most of Tomcat's identity stores implement are authenticate(String username, String credentials) for the actual authentication, String getName() to return the identity store's name (this would perhaps have been an annotation if this was designed today), and startInternal() to do initialisation (would likely be done via an @PostConstruct annotation today).
Example of usage
The code below shows an example of how Tomcat actually uses its identity store. The following shortened fragment is taken from the implementation of the Servlet FORM authentication mechanism in Tomcat.
// Obtain reference to identity store Realm realm = context.getRealm(); if (characterEncoding != null) { request.setCharacterEncoding(characterEncoding); } String username = request.getParameter(FORM_USERNAME); String password = request.getParameter(FORM_PASSWORD); // Delegating of authentication mechanism to identity store principal = realm.authenticate(username, password); if (principal == null) { forwardToErrorPage(request, response, config); return false; } if (session == null) { session = request.getSessionInternal(false); } // Save the authenticated Principal in our session session.setNote(FORM_PRINCIPAL_NOTE, principal);
What sets Tomcat aside from most other systems is that the authenticate() call in most cases directly goes to the custom identity store implementation instead of through many levels of wrappers, bridges, delegators and what have you. This is even true when the provided base class RealmBase is used.
Jetty
Jetty calls its identity store LoginService. It's represented by the interface shown below:
public interface LoginService { String getName(); UserIdentity login(String username, Object credentials, ServletRequest request); boolean validate(UserIdentity user); IdentityService getIdentityService(); void setIdentityService(IdentityService service); void logout(UserIdentity user); }
According to its JavaDoc, a "Login service [identity store] provides an abstract mechanism for an [authentication mechanism] to check credentials and to create a UserIdentity using the set [injected] IdentityService".
There are a few things to remark here. The getName() method names the identity store. This would likely be done via an annotation had this interface been designed today.
The essential method of the Jetty identity store is login(). It's username/credentials based, where the credentials are an opaque Object. The ServletRequest is not often used, but a JAAS bridge uses it to provide a RequestParameterCallback to Jetty specific JAAS LoginModules.
validate() is essentially a kind of shortcut method for login() != null, albeit without using the credentials.
A distinguishing aspect of Jetty is that its identity stores get injected with an IdentityService, which the store has to use to create user identities (users) based on a Subject, (caller) Principal and a set of roles. It's not 100% clear what this was intended to accomplish, since the only implementation of this service just returns new DefaultUserIdentity(subject, userPrincipal, roles), where DefaultUserIdentity is mostly just a simple POJO that encapsulates those three data items.
Another remarkable method is logout(). This is remarkable since the identity store typically just returns authentication data and doesn't hold state per user. It's the authentication mechanism that knows about the environment in which this authentication data is used (e.g. knows about the HTTP request and session). Indeed, almost no identity stores make use of this. The only one that does is the special identity store that bridges to JAAS LoginModules. This one isn't stateful, but provides an operation on the passed in user identity. As it appears, the principal returned by this bridge identity store encapsulates the JAAS LoginContext, on which the logout() method is called at this point.
Example of usage
The code below shows an example of how Jetty uses its identity store. The following shortened and 'unfolded' fragment is taken from the implementation of the Servlet FORM authentication mechanism in Jetty.
if (isJSecurityCheck(uri)) { String username = request.getParameter(__J_USERNAME); String password = request.getParameter(__J_PASSWORD); // Delegating of authentication mechanism to identity store UserIdentity user = _loginService.login(username, password, request); if (user != null) { renewSession(request, (request instanceof Request? ((Request)request).getResponse() : null)); HttpSession session = request.getSession(true); session.setAttribute(__J_AUTHENTICATED, new SessionAuthentication(getAuthMethod(), user, password)); // ... base_response.sendRedirect(redirectCode, response.encodeRedirectURL(nuri)); return form_auth; } // ... }
In Jetty a call to the identity store's login() method will in most cases directly call the installed identity store, and will not go through many layers of delegation, bridges, etc. There is a convenience base class that identity store implementations can use, but this is not required.
If the base class is used, two abstract methods have to be implemented; UserIdentity loadUser(String username) and void loadUsers(), where typically only the former really does something. When this base class is indeed used, the above call to login() goes to the implementation in the base class. This first checks a cache, and if the user is not there calls the sub class via the mentioned loadUser() class.
public UserIdentity login(String username, Object credentials, ServletRequest request) { UserIdentity user = _users.get(username); if (user == null) user = loadUser(username); if (user != null) { UserPrincipal principal = (UserPrincipal) user.getUserPrincipal(); if (principal.authenticate(credentials)) return user; } return null; }
The user returned from the sub class has a feature that's a little different from most other servers; it contains a Jetty specific principal that knows how to process the opaque credentials. It delegates this however to a Credential implementation as shown below:
public boolean authenticate(Object credentials) { return credential != null && credential.check(credentials); }
The credential used here is put into the user instance and represents the -expected- credential and can be of a multitude of types e.g. Crypt, MD5 or Password. MD5 means the expected password is MD5 hashed, while just Password means the expected password is plain text. The check for the latter looks as follows:
public boolean check(Object credentials) { if (this == credentials) return true; if (credentials instanceof Password) return credentials.equals(_pw); if (credentials instanceof String) return credentials.equals(_pw); if (credentials instanceof char[]) return Arrays.equals(_pw.toCharArray(), (char[]) credentials); if (credentials instanceof Credential) return ((Credential) credentials).check(_pw); return false; }
Undertow
Undertow is one of the newest Servlet containers. It's created by Red Hat to replace Tomcat (JBossWeb) in JBoss EAP, and can already be used in WildFly 8/9/10 which are the unsupported precursors for JBoss EAP 7. Undertow can also be used standalone.
The native identity store interface of Undertow is the IdentityManager, which is shown below:
public interface IdentityManager { Account verify(Credential credential); Account verify(String id, Credential credential); Account verify(Account account); }Peculiar enough there are no direct implementations for actual identity stores shipped with Undertow.
Example of usage
The code below shows an example of how Undertow actually uses its identity store. The following shortened fragment is taken from the implementation of the Servlet FORM authentication mechanism in Undertow.
FormData data = parser.parseBlocking(); FormData.FormValue jUsername = data.getFirst("j_username"); FormData.FormValue jPassword = data.getFirst("j_password"); if (jUsername == null || jPassword == null) { return NOT_AUTHENTICATED; } String userName = jUsername.getValue(); String password = jPassword.getValue(); AuthenticationMechanismOutcome outcome = null; PasswordCredential credential = new PasswordCredential(password.toCharArray()); // Obtain reference to identity store IdentityManager identityManager = securityContext.getIdentityManager(); // Delegating of authentication mechanism to identity store Account account = identityManager.verify(userName, credential); if (account != null) { securityContext.authenticationComplete(account, name, true); outcome = AUTHENTICATED; } else { securityContext.authenticationFailed(MESSAGES.authenticationFailed(userName), name); } if (outcome == AUTHENTICATED) { handleRedirectBack(exchange); exchange.endExchange(); } return outcome != null ? outcome : NOT_AUTHENTICATED;
JBoss EAP/WildFly
JBoss identity stores are based on the JAAS LoginModule, which is shown below:
public interface LoginModule { void initialize(Subject subject, CallbackHandler callbackHandler, Map<String,?> sharedState, Map<String,?> options); boolean login() throws LoginException; boolean commit() throws LoginException; boolean abort() throws LoginException; boolean logout() throws LoginException; }As with most application servers, the JAAS LoginModule interface is used in a highly application server specific way.
It's a big question why this interface is used at all, since you can't just implement that interface. Instead you have to inherit from a credential specific base class. Therefor the LoginModule interface is practically an internal implementation detail here, not something the user actually uses. Despite that, it's not uncommon for users to think "plain" JAAS is being used and that JAAS login modules are universal and portable, but they are anything but.
For the username/password credential the base class to inherit from is UsernamePasswordLoginModule. As per the JavaDoc of this class, there are two methods that need to be implemented: getUsersPassword() and getRoleSets().
getUsersPassword() has to return the actual password for the provided username, so the base code can compare it against the provided password. If those passwords match getRoleSets() is called to retrieve the roles associated with the username. Note that JBoss typically does not map groups to roles, so it returns roles here which are then later on passed into APIs that normally would expect groups. In both methods the username is available via a call to getUsername().
The "real" contract as *hypothetical* interface could be thought of to look as follows:
public interface JBossIdentityStore { String getUsersPassword(String username); Group[] getRoleSets(String username) throws LoginException; }
Example of usage
There's no direct usage of the LoginModule in JBoss. JBoss EAP 7/WildFly 8-9-10 directly uses Undertow as its Servlet container, which means the authentication mechanisms shipped with that uses the IdentityManager interface exactly as shown above in the Undertow section.
For usage in JBoss there's a bridge implementation of the IdentityManager to the JBoss specific JAAS LoginModule available.
The "identityManager.verify(userName, credential)" call shown above ends up at JAASIdentityManagerImpl#verify. This first wraps the username, but extracts the password from PasswordCredential. Abbreviated it looks as follows:
public Account verify(String id, Credential credential) { if (credential instanceof DigestCredential) { // .. } else if(credential instanceof PasswordCredential) { return verifyCredential( new AccountImpl(id), copyOf(((PasswordCredential) credential).getPassword()) ); } return verifyCredential(new AccountImpl(id), credential); }The next method called in the "password chain" is somewhat troublesome, as it doesn't just return the account details, but as an unavoidable side-effect also puts the result of authentication in TLS. It takes a credential as an Object and delegates further to an isValid() method. This one uses a Subject as an output parameter (meaning it doesn't return the authentication data but puts it inside the Subject that's passed in). The calling method then extracts this authentication data from the subject and puts it into its own type instead.
Abbreviated again this looks as follows:
private Account verifyCredential(AccountImpl account, Object credential) Subject subject = new Subject(); boolean isValid = securityDomainContext .getAuthenticationManager() .isValid(account.getOriginalPrincipal(), credential, subject); if (isValid) { // Stores details in TLS getSecurityContext() .getUtil() .createSubjectInfo(account.getOriginalPrincipal(), credential, subject); return new AccountImpl( getPrincipal(subject), getRoles(subject), credential, account.getOriginalPrincipal() ); } return null; }The next method being called is isValid() on a type called AuthenticationManager. Via two intermediate methods this ends up calling proceedWithJaasLogin.
This method obtains a LoginContext, which wraps a Subject, which wraps the Principal and roles shown above (yes, there's a lot of wrapping going on). Abbreviated the method looks as follows:
private boolean proceedWithJaasLogin(Principal principal, Object credential, Subject theSubject) { try { copySubject(defaultLogin(principal, credential).getSubject(), theSubject); return true; } catch (LoginException e) { return false; } }
The defaultLogin() method finally just calls plain Java SE JAAS code, although just before doing that it uses reflection to call a setSecurityInfo() method on the CallbackHandler. It's remarkable that even though this method seems to be required and known in advance, there's no interface used for this. The handler being used here is often of the type JBossCallbackHandler.
Brought back to its essence the method looks like this:
private LoginContext defaultLogin(Principal principal, Object credential) throws LoginException { CallbackHandler theHandler = (CallbackHandler) handler.getClass().newInstance(); setSecurityInfo.invoke(theHandler, new Object[] {principal, credential}); LoginContext lc = new LoginContext(securityDomain, subject, handler); lc.login(); return lc; }
Via some reflective magic the JAAS code shown here will locate, instantiate and at long last will call our custom LoginModule's initialize(), login() and commit() methods, which on their turn will call the two methods that we needed to implement in our subclass.
Resin
Resin calls its identity store "Authenticator". It's represented by a single interface shown below:
public interface Authenticator { String getAlgorithm(Principal uid); Principal authenticate(Principal user, Credentials credentials, Object details); boolean isUserInRole(Principal user, String role); void logout(Principal user); }There are a few things to remark here. The logout() method doesn't seem to make much sense, since it's the authentication mechanism that keeps track of the login state in the overarching server. Indeed, the method does not seem to be called by Resin, and there are no identity stores implementing it except for the AbstractAuthenticator that does nothing there.
isUserInRole() is somewhat remarkable as well. This method is not intended to check for the roles of any given user, such as you could for instance use in an admin UI. Instead, it's intended to be used by the HttpServletRequest#isUserInRole call, and therefor only for the *current* user. This is indeed how it's used by Resin. This is remarkable, since most other systems keep the roles in memory. Retrieving it from the identity store every time can be rather heavyweight. To combat this, Resin uses a CachingPrincipal, but an identity store implementation has to opt-in to actually use this.
Example of usage
The code below shows an example of how Resin actually uses its identity store. The following shortened fragment is taken from the implementation of the Servlet FORM authentication mechanism in Resin.
// Obtain reference to identity store Authenticator auth = getAuthenticator(); // .. String userName = request.getParameter("j_username"); String passwordString = request.getParameter("j_password"); if (userName == null || passwordString == null) return null; char[] password = passwordString.toCharArray(); BasicPrincipal basicUser = new BasicPrincipal(userName); Credentials credentials = new PasswordCredentials(password); // Delegating of authentication mechanism to identity store user = auth.authenticate(basicUser, credentials, request); return user;
A nice touch here is that Resin obtains the identity store via CDI injection. A somewhat unknown fact is that Resin has its own CDI implementation, CanDI and uses it internally for a lot of things. Unlike some other servers, the call to authenticate() here goes straight to the identity store. There are no layers of lookup or bridge code in between.
That said, Resin does encourage (but not require) the usage of an abstract base class it provides: AbstractAuthenticator. IFF this base class is indeed used (again, this is not required), then there are a few levels of indirection the flow goes through before reaching one's own code. In that case, the authenticate() call shown above will start with delegating to one of three methods for known credential types. This is shown below:
public Principal authenticate(Principal user, Credentials credentials, Object details) { if (credentials instanceof PasswordCredentials) return authenticate(user, (PasswordCredentials) credentials, details); if (credentials instanceof HttpDigestCredentials) return authenticate(user, (HttpDigestCredentials) credentials, details); if (credentials instanceof DigestCredentials) return authenticate(user, (DigestCredentials) credentials, details); return null; }
Following the password trail, the next level will merely extract the password string:
protected Principal authenticate(Principal principal, PasswordCredentials cred, Object details) { return authenticate(principal, cred.getPassword()); }
The next authenticate method will call into a more specialized method that only obtains a User instance from the store. This instance has the expected password embedded, which is then verified against the provided password. Abbreviated it looks as follows:
protected Principal authenticate(Principal principal, char[] password) { PasswordUser user = getPasswordUser(principal); if (user == null || user.isDisabled() || (!isMatch(principal, password, user.getPassword()) && !user.isAnonymous())) return null; return user.getPrincipal(); }
The getPasswordUser() method goes through one more level of convenience, where it extracts the caller name that was wrapped by the Principal:
protected PasswordUser getPasswordUser(Principal principal) { return getPasswordUser(principal.getName()); }
This last call to getPasswordUser(String) is what typically ends up in our own custom identity store.
Finally, it's interesting to see what data PasswordUser contains. Abbreviated again this is shown below:
public class PasswordUser { Principal principal; char[] password; boolean disabled; boolean anonymous; String[] roles; }
Glassfish
GlassFish identity stores are based on the JAAS LoginModule, which is shown below:
public interface LoginModule { void initialize(Subject subject, CallbackHandler callbackHandler, Map<String,?> sharedState, Map<String,?> options); boolean login() throws LoginException; boolean commit() throws LoginException; boolean abort() throws LoginException; boolean logout() throws LoginException; }
Just as we saw with JBoss above, the LoginModule interface is again used in a very application server specific way. In practice, you don't just implement a LoginModule but inherit from com.sun.enterprise.security.BasePasswordLoginModule or it's empty subclass com.sun.appserv.security.AppservPasswordLoginModule for password based logins, or com.sun.appserv.security.AppservCertificateLoginModule/com.sun.enterprise.security.BaseCertificateLoginModule for certificate ones.
As per the JavaDoc of those classes, the only method that needs to be implemented is authenticateUser(). Inside that method the username is available via the protected variable(!) "_username", while the password can be obtained via getPasswordChar(). When a custom identity store is done with its work commitUserAuthentication() has to be called with an array of groups when authentication succeeded and a LoginException thrown when it failed. So essentially that's the "real" contract for a custom login module. The fact that the other functionality is in the same class is more a case of using inheritance where aggregation might have made more sense. As we saw with JBoss, the LoginModule interface itself seems more like an implementation detail instead of something a client can really take advantage of.
The "real" contract as *hypothetical* interface looks as follows:
public interface GlassFishIdentityStore { String[] authenticateUser(String username, char[] password) throws LoginException; }
Even though a LoginModule is specific for a type of identity store (e.g. File, JDBC/database, LDAP, etc), LoginModules in GlassFish are mandated to be paired with another construct called a Realm. While having the same name as the Tomcat equivalent and even a nearly identical description, the type is completely different. In GlassFish it's actually a kind of DAO, albeit one with a rather heavyweight contract.
Most of the methods of this DAO are not actually called by the runtime for authentication, nor are they used by application themselves. They're likely intended to be used by the GlassFish admin console, so a GlassFish administrator can add and delete users. However, very few actual realms support this and with good reason. It just doesn't make much sense for many realms really. E.g. LDAP and Solaris have their own management UI already, and JDBC/database is typically intended to be application specific so there the application already has its own DAOs and services to manage users, and exposes its own UI as well.
A custom LoginModule is not forced to use this Realm, but the base class code will try to instantiate one and grab its name, so one must still be paired to the LoginModule.
The following lists the public and protected methods of this Realm class. Note that the body is left out for the non-abstract methods.
public abstract class Realm implements Comparable { public static synchronized Realm getDefaultInstance(); public static synchronized String getDefaultRealm(); public static synchronized Enumeration getRealmNames(); public static synchronized void getRealmStatsProvier(); public static synchronized Realm getInstance(String); public static synchronized Realm instantiate(String, File); public static synchronized Realm instantiate(String, String, Properties); public static synchronized void setDefaultRealm(String); public static synchronized void unloadInstance(String); public static boolean isValidRealm(String); protected static synchronized void updateInstance(Realm, String); public abstract void addUser(String, String, String[]); public abstract User getUser(String); public abstract void updateUser(String, String, String, String[]); public abstract void removeUser(String); public abstract Enumeration getUserNames(); public abstract Enumeration getGroupNames(); public abstract Enumeration getGroupNames(String); public abstract void persist(); public abstract void refresh(); public abstract AuthenticationHandler getAuthenticationHandler(); public abstract boolean supportsUserManagement(); public abstract String getAuthType(); public int compareTo(Object); public String FinalgetName(); public synchronized String getJAASContext(); public synchronized String getProperty(String); public synchronized void setProperty(String, String); protected void init(Properties); protected ArrayList<String> getMappedGroupNames(String); protected String[] addAssignGroups(String[]); protected final void setName(String); protected synchronized Properties getProperties(); }
Example of usage
To make matters a bit more complicated, there's no direct usage of the LoginModule in GlassFish either. GlassFish' Servlet container is internally based on Tomcat, and therefor the implementation of the FORM authentication mechanism is a Tomcat class (which strongly resembles the class in Tomcat itself, but has small differences here and there). Confusingly, this uses a class named Realm again, but it's a totally different Realm than the one shown above. This is shown below:
// Obtain reference to identity store Realm realm = context.getRealm(); String username = hreq.getParameter(FORM_USERNAME); String pwd = hreq.getParameter(FORM_PASSWORD); char[] password = ((pwd != null)? pwd.toCharArray() : null); // Delegating of authentication mechanism to identity store principal = realm.authenticate(username, password); if (principal == null) { forwardToErrorPage(request, response, config); return (false); } if (session == null) session = getSession(request, true); session.setNote(FORM_PRINCIPAL_NOTE, principal);
This code is largely identical to the Tomcat version shown above. The Tomcat Realm in this case is not the identity store directly, but an adapter called RealmAdapter. It first calls the following slightly abbreviated method for the password credential:
public Principal authenticate(String username, char[] password) { if (authenticate(username, password, null)) { return new WebPrincipal(username, password, SecurityContext.getCurrent()); } return null; }
protected boolean authenticate(String username, char[] password, X509Certificate[] certs) { try { if (certs != null) { // ... create subject LoginContextDriver.doX500Login(subject, moduleID); } else { LoginContextDriver.login(username, password, _realmName); } return true; } catch (Exception le) {} return false; }
public static void login(String username, char[] password, String realmName){ Subject subject = new Subject(); subject.getPrivateCredentials().add(new PasswordCredential(username, password, realmName)); LoginContextDriver.login(subject, PasswordCredential.class); }
This new login method checks for several credential types, which abbreviated looks as follows:
public static void login(Subject subject, Class cls) throws LoginException { if (cls.equals(PasswordCredential.class)) doPasswordLogin(subject); else if (cls.equals(X509CertificateCredential.class)) doCertificateLogin(subject); else if (cls.equals(AnonCredential.class)) { doAnonLogin(); else if (cls.equals(GSSUPName.class)) { doGSSUPLogin(subject); else if (cls.equals(X500Name.class)) { doX500Login(subject, null); else throw new LoginException("Unknown credential type, cannot login."); }
As we're following the password trail, we're going to look at the doPasswordLogin() method here, which strongly abbreviated looks as follows:
private static void doPasswordLogin(Subject subject) throws LoginException try { new LoginContext( Realm.getInstance( getPrivateCredentials(subject, PasswordCredential.class).getRealm() ).getJAASContext(), subject, dummyCallback ).login(); } catch (Exception e) { throw new LoginException("Login failed: " + e.getMessage()).initCause(e); } }
We're now 5 levels deep, and we're about to see our custom login module being called.
At this point it's down to plain Java SE JAAS code. First the name of the realm that was stuffed into a PasswordCredential which was stuffed into a Subject is used to obtain a Realm instance of the type that was shown way above; the GlassFish DAO like type. Via this instance the realm name is mapped to another name; the "JAAS context". This JAAS context name is the name under which our LoginModule has to be registered. The LoginContext does some magic to obtain this LoginModule from a configuration file and initializes it with the Subject among others. The login(), commit() and logout() methods can then make use of this Subject later on.
At long last, the login() method call (via 2 further private helper methods, not shown here) will at 7 levels deep cause the login() method of our LoginModule to be called. This happens via reflective code which looks as follows:
// methodName == "login" here // find the requested method in the LoginModule for (mIndex = 0; mIndex < methods.length; mIndex++) { if (methods[mIndex].getName().equals(methodName)) break; } // set up the arguments to be passed to the LoginModule method Object[] args = { }; // invoke the LoginModule method boolean status = ((Boolean) methods[mIndex].invoke(moduleStack[i].module, args)).booleanValue();
final public boolean login() throws LoginException { // Extract the username, password and realm name from the Subject extractCredentials(); // Delegate the actual authentication to subclass (finally!) authenticateUser(); return true; }
Liberty
Liberty calls its identity stores "user registry". It's shown below:
public interface UserRegistry { void initialize(Properties props) throws CustomRegistryException, RemoteException; String checkPassword(String userSecurityName, String password) throws PasswordCheckFailedException, CustomRegistryException, RemoteException; String mapCertificate(X509Certificate[] certs) throws CertificateMapNotSupportedException, CertificateMapFailedException, CustomRegistryException, RemoteException; String getRealm() throws CustomRegistryException, RemoteException; Result getUsers(String pattern, int limit) throws CustomRegistryException, RemoteException; String getUserDisplayName(String userSecurityName) throws EntryNotFoundException, CustomRegistryException, RemoteException; String getUniqueUserId(String userSecurityName) throws EntryNotFoundException, CustomRegistryException, RemoteException; String getUserSecurityName(String uniqueUserId) throws EntryNotFoundException, CustomRegistryException, RemoteException; boolean isValidUser(String userSecurityName) throws CustomRegistryException, RemoteException; Result getGroups(String pattern, int limit) throws CustomRegistryException, RemoteException; String getGroupDisplayName(String groupSecurityName) throws EntryNotFoundException, CustomRegistryException, RemoteException; String getUniqueGroupId(String groupSecurityName) throws EntryNotFoundException, CustomRegistryException, RemoteException; ListgetUniqueGroupIds(String uniqueUserId) throws EntryNotFoundException, CustomRegistryException, RemoteException; String getGroupSecurityName(String uniqueGroupId) throws EntryNotFoundException, CustomRegistryException, RemoteException; boolean isValidGroup(String groupSecurityName) throws CustomRegistryException, RemoteException; List getGroupsForUser(String groupSecurityName) throws EntryNotFoundException, CustomRegistryException, RemoteException; WSCredential createCredential(String userSecurityName) throws NotImplementedException, EntryNotFoundException, CustomRegistryException, RemoteException; }
As can be seen it's clearly one of the most heavyweight interfaces for an identity store that we've seen till this far. As Liberty is closed source we can't exactly see what the server uses all these methods for.
As can be seen though it has methods to list all users and groups that the identity store manages (getUsers(), getGroups()) as well as methods to get what IBM calls a "display name", "unique ID" and "security name" which are apparently associated with both user and role names. According to the published JavaDoc display names are optional. It's perhaps worth it to ask the question if the richness that these name mappings potentially allow for are worth the extra complexity that's seen here.
createCredential() stands out as the JavaDoc mentions it's never been called for at least the 8.5.5 release of Liberty.
The main method that does the actual authentication is checkPassword(). It's clearly username/password based. Failure has to be indicated by trowing an exception, success returns the passed in username again (or optionally any other valid name, which is a bit unlike what most other systems do). There's support for certificates via a separate method, mapCertificate(), which seemingly has to be called first, and then the resulting username passed into checkPassword() again.
Example of usage
Since Liberty is closed source we can't actually see how the server uses its identity store. Some implementation examples are given by IBM and myself.
WebLogic
It's not entirely clear what an identity store in WebLogic is really called. There are many moving parts. The overall term seems to be "security provider", but these are subdivided in authentication providers, identity assertion providers, principal validation providers, authorization providers, adjudication providers and many more providers.
One of the entry points seems to be an "Authentication Provider V2", which is given below:
public interface AuthenticationProviderV2 extends SecurityProvider { AppConfigurationEntry getAssertionModuleConfiguration(); IdentityAsserterV2 getIdentityAsserter(); AppConfigurationEntry getLoginModuleConfiguration(); PrincipalValidator getPrincipalValidator(); }
Here it looks like the getLoginModuleConfiguration() has to return an AppConfigurationEntry that holds the fully qualified class name of a JAAS LoginModule, which is given below:
public interface LoginModule { void initialize(Subject subject, CallbackHandler callbackHandler, Map<String,?> sharedState, Map<String,?> options); boolean login() throws LoginException; boolean commit() throws LoginException; boolean abort() throws LoginException; boolean logout() throws LoginException; }It seems WebLogic's usage of the LoginModule is not as highly specific to the application server as we saw was the case for JBoss and GlassFish. The user can implement the interface directly, but has to put WebLogic specific principals in the Subject as these are not standardized.
Example of usage
Since WebLogic is closed source it's not possible to see how it actually uses the Authentication Provider V2 and its associated Login Module.
Conclusion
We took a look at how a number of different servlet containers implemented the identity store concept. The variety of ways to accomplish essentially the same thing is nearly endless. Some containers pass two strings for a username and password, others pass a String for the username, but a dedicated Credential type for the password, a char[] or even an opaque Object for the password. Two containers pass in a third parameter; the http servlet request.
The return type is varied as well. A (custom) Principal was used a couple of times, but several other representations of "caller data" were seen as well; like an "Account" and a "UserIdentity". In one case the container deemed it necessary to modify TLS to set the result.
The number of levels (call depth) needed to go through before reaching the identity store was different as well between containers. In some cases the identity store was called immediately with absolutely nothing in between, while in other cases up to 10 levels of bridging, adapting and delegating was done before the actual identity store was called.
Taking those intermediate levels into account revealed even more variety. We saw complete LoginContext instances being returned, we saw Subjects being used as output parameters, etc. Likewise, the mechanism to indicate success or failure ranged from an exception being thrown, via a boolean being returned, to a null being returned for groups.
One thing that all containers had in common though was that there's always an authentication mechanism that interacts with the caller and environment and delegates to the identity store. Then, no matter how different the identity store interfaces looked, every one of them had a method to perform the {credentials in, caller data out} function.
It's exactly this bare minimum of functionality that is arguably in most dire need of being standardised in Java EE. As it happens to be the case this is indeed what we're currently looking at in the security EG.
Arjan Tijms
Comments
Post a Comment