Authentication module
See also notes from a Yale meeting on this subject.
What is
The "authentication" module contains interfaces and classes which solve the authentication concern of the system. Main interfaces are AuthenticationManager (main service component to authenticate "things". Delegates to pluggable AuthenticationHandler s), Principal (an authenticated "thing") and CredentialsToPrincipalResolver to handle resolving credentials to an actual principal. Again a UML class diagram of this module would be useful.
An idea
We collapse AuthenticationHandler and CredentialsToPrincipalResolver into a single AuthenticationHandler and then collapse the AuthenticationManager into that AuthenticationHandler to yield a single core interface of the Authentication package.
| On Credentials and marker interfaces Proposed elsewhere is the thought that the Credentials interface is not needed and we would be better served by using raw java.lang.Objects. It's a minor point. For purposes of this page I'll just assume we're going with the Credentials marker interface, which should be just fine. |
Proposed interfaces
/** * Implementations will examine particular Credentials for their representing * credentials that provide evidence of authenticity (e.g., a username and password) on the basis of which * the AuthenticationHandler creates an AuthenticationResult. AuthenticationResults either represent * AuthenticationSuccesses or AuthenticationFailures. */ public interface AuthenticationHandler { /** * Examine some Credentials for evidence that it authenticates a Principal. * Return an AuthenticationResult communicating the authenticated Principal and * possibly additional information about the nature of its authentication, or * a characterization of the failure to authenticate -- the Credentials were not understood or * supported, the user wasn't found, the password was wrong, the account has been locked, whatever -- * particular AuthenticationHandlers will return particular AuthenticationResult implementations appropriate * to their implementations. */ AuthenticationResult authenticate(Credentials credentials); }
The output of AuthenticationHandler is an AuthenticationResult, which may represent a success (an authenticated Principal and perhaps other information about the authentication), or may represent a failure (no authenticated principal and perhaps other information about the nature of the failure).
/** * The AuthenticationResult interface represents the commonality of AuthenticationSuccesses and * AuthenticationFailures as being different types of AuthenticationResult -- the two possible return values * from an AuthenticationHandler. * All AuthenticationResults must be either AuthenticationSuccesses or AuthenticationFailures. */ public interface AuthenticationResult { // this interface declares no methods. // It is a wrapper interface that allows AuthenticationHandlers to have a single return type which // will represent the result of authentication, whether that be success or failure. }
public interface AuthenticationSuccess { public Principal getPrincipal(); }
public interface AuthenticationFailure { // this is a marker interface. Actual implementations of this interface may communicate // the type of failure }
| supports() In place of a supports() method on AuthenticationHandlers, I would favor instead specifying that the authenticate() method return an UnsupportedCredentialsFailure in the case where the particular AuthenticationHandler doesn't understand the Credentials it was passed. UnsupportedCredentialsFailure /** * The AuthenticationFailure that represents the failure resulting from the * Credentials received by the AuthenticationHandler as being unknown, unrecognized, and therefore * unsupported. For instance, a handler expecting UsernamePasswordCredentials would return this AuthenticationFailure * in the case where it received KerberosCredentials. */ public class UnsupportedCredentialsFailure implements AuthenticationFailure { // this class provides no methods because it communicates everything it needs to communicate // by means of the identity of its runtime class. } |
Thoughts about managers
And right about now you should be saying, "Hey! Wait! Where's my AuthenticationManager? I needed that."
Under this view, AuthenticationManager becomes just a particular implementation of AuthenticationHandler – an AuthenticationHandler that knows how to delegate to other AuthenticationHandlers.
Why are these managers just implementations of AuthenticationHandler and not something more? Because fundamentally they are implementing the AuthenticationHandler interface. They take credentials, and they return AuthenticationResults. This makes them AuthenticationHandlers.
public class ManagingAuthenticationHandler implements AuthenticationHandler { // not listed here but assume setters and getters. private List childHandlers; /** * Implements this interface method by delegation. * * The return value is the following: * 1) If any delegate handler returned an AuthenticationSuccess, returns the AuthenticationSuccess * returned by the first delegate to succeed. * 2) If all delegate handlers returned CredentialsNotSupported, returns CredentialsNotSupported. * 3) If all delegates returned AuthenticationFailures and at least one delegate returned an * AuthenticationFailure that is not a CredentialsNotSupported, returns an AggregatedFailures representing * all accumulated AuthenticationFailures that were not CredentialsNotSupported. */ public AuthenticationResult authenticate(Credentials credentials) { AggregatedFailures aggregatedFailures = new AggregatedeFailures(); for (Iterator iter = this.childHandlers.iterator() { AuthenticationHandler handler = (AuthenticationHandler) iter.next(); AuthenticationResult result = handler.authenticate(credentials); if (result instanceof AuthenticationSuccess) { return result; } else if (result instanceof CredentialsNotSupported) { // do nothing } else { // we have an AuthenticationFailure that is not a CredentialsNotSupported aggregatedFailures.addFailure(result); } } if (this.aggregatedFailures.getFailures().isEmpty()) { return new UnsupportedCredentialsFailure(); } else { return aggregatedFailures; } } }
public class AggregatedFailures implements AuthenticationFailure { public void addFailure(AuthenticationFailure failure); /** * Get a List of AuthenticationFailures. */ public List getFailures(); }
Exceptions versus AuthenticationFailure as a type of AuthenticationResult
Instead of using AuthenticationFailures as a type of AuthenticationResult, we could instead go with encapsulating failure information in an Exception thrown by AuthenticationHandlers. Either way, the point is that AuthenticationHandlers may have information to return in the case of a failure that will have effects all the way back up in the view layer where a user is told "Your account has been locked" rather than given another opportunity to type her username and password.
More thoughts
See also notes from a Yale CAS meeting on this subject.

Comments (10)
Jan 08, 2005
William G. Thompson, Jr. says:
From a Domain Model perspective we've been thing about unifiying the outward loo...From a Domain Model perspective we've been thing about unifiying the outward looking CAS interface which would subsume both TicketManager and AuthenticationManager. CAS is not concerned with authN for authN sake...it wants to create a TicketGrantingTicket if if can authenticate a Prinicpal's Credentials. which leads us to a unified CAS interface with a method like +createTicketGrantingTicket(Credentials).
In the normal flow of events CAS.createTGT(Creds) would execute primary authN on the credentials, resolve the Principal, create and return a TGT. When things go wrong we can communicate to calling code by throwing an appropriate AuthenticationException. (e.g. InvalidCredentialsException, LockedPrinicipalException, ...)
Jan 13, 2005
Nathan Kopp says:
I like the AuthenticationHandler idea. This can lead to less code duplication an...I like the AuthenticationHandler idea. This can lead to less code duplication and higher performance (compared to the current model where AuthenticationHandler and CredentialsToPrincipalResolver are separate), because, for some authentication methods, the initial authentication can very easily produce the Principal as a side effect.
I personally don't think I'd like Mr. Thompson's suggestion about further collapsing the model. Sometimes I like things to be a bit more abstract, because that can sometimes make it easier to create new pluggable components.
Jan 13, 2005
Scott Battaglia says:
Actually, this would lead to possibly more code duplication. For example, at Rut...Actually, this would lead to possibly more code duplication.
For example, at Rutgers our CAS now accepts Username, Password and AuthenticationType (Safeword, Normal, etc.) as credentials.
Suppose we have 3 handlers. Each one supports the ability to check "UsernamePasswordAuthTypeCredentials". However, each one handles a specific case "Safeword, Normal, SecureId). However, they can all use the same type of principal resolver.
There are three ways to do this:
1. Have the code directly in each handler. This is possibly bad for a few reasons (1) its duplicated across all handlers, (2) if you want to change the resolver, you now have to rip it out and replace it in three places and the authentication is now tied to the resolver.
2. AbstractHandler. This solves problems (1) from above but not (2) or (3)
3. Seperate PrincipalToCredentialsResolver. This solves issues (1), (2) and (3).
As for performance, it would most likely be comparable (of course neither of us could probably prove this either way
). In both cases you would need somehow to reject a credentials you cannot authenticate with that handler and move on to the next one. One could also probably write a faster implementation of the AuthenticationManager that exists anyway (I know of a few ways to speed it up that I haven't done yet).
Jan 14, 2005
Nathan Kopp says:
I'm looking at things from a slightly different perspective (of course). We use ...I'm looking at things from a slightly different perspective (of course).
We use a customized version of CAS 2. In our application, we connect to multiple LDAP servers (one MS Active Directory and one Novell eDirectory) for authentication and principal resolution. Our users are unaware of the two directories, so we don't ask them to choose... we simply try both. Also, our Principal includes more than just the username. We fetch multiple attributes from the LDAP server and include them in the CAS response.
Basically, this means that, in our application , the Principal cannot be derived from the credentials without the process of authentication. (Note that, in theory, the same username could exist on both LDAP servers.) The principal resolver would need to actually reauthenticate in order to be assured that it is retrieving the correct principal that corresponds to the supplied credentials.
With the Andrew's AuthenticationResult idea, you could still have a seaparate CredentialsToPrincipalResolver, but the AuthenticationHandler would use it internally. Each of your three AuthenticationHandler objects would would make a one-line call to the same CredentialsToPrincipalResolver in order to generate the AuthenticationResponse.
Also, the AuthenticationResponse, IMHO, is far more flexible than the simple true/false boolean response of the current AuthenticationHandler.authenticate() method.
Jan 13, 2005
Scott Battaglia says:
In the case Bill is talking about, it makes sense to collapse (at least from my ...In the case Bill is talking about, it makes sense to collapse (at least from my perspective
).
For example, in the current implementation of CAS 3 you could write code that retrieves a TicketGrantingTicket from CAS without even attempting to authenticate. In the subsumed model, you ask for a TicketGrantingTicket and supply your credentials. If your credentials check out, then you are granted a TicketGrantingticket. That doesn't mean the AuthenticationManager no longer exists, its just not exposed to the "clients" (i.e. controllers, web service). The clients are only exposed to the methods they need to get their task done. There was no need for a client to have to manually call authenticate and then call getTicketGrantingTicket. You can't have a TicketGrantingTicket without being authenticated so it should be accomplished with one call.
Jan 14, 2005
Nathan Kopp says:
I missed that he was talking about the "outward looking" interface. So, actually...I missed that he was talking about the "outward looking" interface.
So, actually, I do fully agree. Sorry for not reading Bill's original comment carefully.
Jan 13, 2005
Scott Battaglia says:
This version of the AuthenticationHandler is missing a very important method.......This version of the AuthenticationHandler is missing a very important method.... supports(Credentials credentials). This method determines if a specific handler can even attempt to authenticate these credentials. Its also the reason for the existance of the AuthenticationManager.
The AuthenticationManager has the responsibility of checking if a particular handler can handle a type of credentials. If so it will delegate to that handler. Otherwise it will attempt to try the next one (or however you choose to do it).
I am also concerned with the concept of passing "Object". Object has no meaning. We are attempting to authenticate Credentials. Credentials tells a story. Its something we all understand. When I see Credentials, I say "oh, what I'm going to give you that proves who I am" vs. some object.
Jan 13, 2005
Scott Battaglia says:
Sorry I should say its one of the reasons for the existance of the Authenticatio...Sorry I should say its one of the reasons for the existance of the AuthenticationManager...its not the only reason it exists
Jan 14, 2005
Nathan Kopp says:
I agree with Scott that the "supports" method is very important and shouldn't be...I agree with Scott that the "supports" method is very important and shouldn't be dropped.
I do think that an AuthenticationManager could still be viewed as a special case (subclass) of of an AuthenticationHandler. If not, it should at least be made pluggable.
While I like the abstractness of Andrew's suggestion, I'm realizing that it could make configuration more difficult. With the current method, we only configure a single layer: the authentication handlers and principal resolvers that are used by the authentication manager. Andrew is basically suggesting a tree of authentication handlers, some of which are authentication managers. Building this tree from a configuration file could be tricky, though it would certainly make the system very flexible.
Jan 15, 2005
Scott Battaglia says:
Just about anything in CAS 3 will be pluggable. AuthenticationManager is an inte...Just about anything in CAS 3 will be pluggable. AuthenticationManager is an interface so if you didn't like our (basic?) implementation you're free to plug in your own implementation or one that someone else made.
I won't comment on the other stuff since we seem to be moving this to the dev list.
Good to see the community providing their input though