Authentication module

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

AuthenticationHandler

/**
 * 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).

AuthenticationResult

/**
 * 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.

}

AuthenticationSuccess

public interface AuthenticationSuccess {

  public Principal getPrincipal();

}

AuthenticationFailure

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.

ManagingAuthenticationHandler
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;
        }

    }

}
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.