How To Write an AuthenticationHandler
This page documents how to write a CAS3 AuthenticationHandler. If you already have a CAS2 PasswordHandler or TrustHandler, you can just use it in CAS3 without writing any new code and there is documentation intended specifically for explaining how to convert a CAS2 PasswordHandler to the CAS3 APIs. This page is intended to document CAS3 AuthenticationHandler implementation outside the context of CAS2.
The Domain of Authentication
Before we discuss actual APIs and code, it may be helpful to review the concepts of CAS authentication.
Credentials
Credentials are what someone presents to CAS as evidence on the basis of which they may be authenticated. A username, password pair is a Credentials, as is a cryptographic certificate, etc.
Principal
A Principal is an entity that is authenticated.
Coding
AuthenticationHandler
The core interface to implement for your authentication-handling plugin is AuthenticationHandler.
/** * Validate Credentials support for AuthenticationManagerImpl. * * Determines that Credentials are valid. Password-based credentials may be * tested against an external LDAP, Kerberos, JDBC source. Certificates may be * checked against a list of CA's and do the usual chain validation. * Implementations must be parameterized with their sources of information. * * Callers to this class should first call supports to determine if the * AuthenticationHandler can authenticate the credentials provided. * * @version $Revision: 1.11 $ $Date: 2005/06/17 13:24:38 $ */ public interface AuthenticationHandler { /** * Method to determine if the credentials supplied are valid. * * @param credentials The credentials to validate. * @return true if valid, return false otherwise. * @throws AuthenticationException An AuthenticationException can contain * details about why a particular authentication request failed. */ boolean authenticate(Credentials credentials) throws AuthenticationException; /** * Method to check if the handler knows how to handle the credentials * provided. It may be a simple check of the Credentials class or something * more complicated such as scanning the information contained in the * Credentials object. * * @param credentials The credentials to check. * @return true if the handler supports the Credentials, false othewrise. */ boolean supports(Credentials credentials); }
You can code directly to this interface.
Our trivial example
Suppose that the credentials we're examining consist of usernames and passwords and that we want to authenticate where the password is the integer representation of the length of the username.
/** * Authenticates where the presented password is the integer length of the * username. */ public class UsernameLengthAuthnHandler implements AuthenticationHandler { public boolean authenticate(Credentials credentials) throws AuthenticationException { UsernamePasswordCredentials upCredentials = (UsernamePasswordCredentials) credentials; String username = upCredentials.getUsername(); String password = upCredentials.getPassword(); String correctPassword = Integer.toString(username.length()); return correctPassword.equals(password); } public boolean supports(Credentials credentials) { // we support credentials that bear usernames and passwords return credentials instanceof UsernamePasswordCredentials; } }
See the Using the REMOTE_USER example for a more advanced example of coding directly to AuthenticationHandler to accomplish something other than username and password authentication.
AuthenticationHandlers that take usernames and passwords
You can extend AbstractUsernamePasswordAuthenticationHandler if you're dealing with usernames and passwords.
/** * Abstract class to override supports so that we don't need to duplicate the * check for UsernamePasswordCredentials. * * @version $Revision: 1.12 $ $Date: 2005/06/20 18:15:36 $ */ public abstract class AbstractUsernamePasswordAuthenticationHandler implements AuthenticationHandler, InitializingBean { /** * PasswordEncoder to be used by subclasses to encode passwords for * comparing against a resource. */ private PasswordEncoder passwordEncoder; /** Instance of logging for subclasses. */ private Log log = LogFactory.getLog(this.getClass()); /** * Method automatically handles conversion to UsernamePasswordCredentials * and delegates to abstract authenticateUsernamePasswordInternal so * subclasses do not need to cast. */ public final boolean authenticate(final Credentials credentials) throws AuthenticationException { return authenticateUsernamePasswordInternal((UsernamePasswordCredentials) credentials); } /** * Abstract convenience method that assumes the credentials passed in are a * subclass of UsernamePasswordCredentials. * * @param credentials the credentials representing the Username and Password * presented to CAS * @return true if the credentials are authentic, false otherwise. * @throws AuthenticationException if authenticity cannot be determined. */ protected abstract boolean authenticateUsernamePasswordInternal( final UsernamePasswordCredentials credentials) throws AuthenticationException; public final void afterPropertiesSet() throws Exception { if (this.passwordEncoder == null) { this.passwordEncoder = new PlainTextPasswordEncoder(); getLog().info( "No PasswordEncoder set. Using default: " + this.passwordEncoder.getClass().getName()); } afterPropertiesSetInternal(); } /** * Method designed to be overwritten subclasses that need to do additional * properties checking. * * @throws Exception if there is an error checking the properties. */ protected void afterPropertiesSetInternal() throws Exception { // this is designed to be overwritten } /** * Method to return the PasswordEncoder to be used to encode passwords. * * @return the PasswordEncoder associated with this class. */ public final PasswordEncoder getPasswordEncoder() { return this.passwordEncoder; } /** * Method to return the log instance in order for subclasses to have access * to the log object. * * @return the logging instance for this class. */ public final Log getLog() { return this.log; } /** * Sets the PasswordEncoder to be used with this class. * * @param passwordEncoder the PasswordEncoder to use when encoding * passwords. */ public final void setPasswordEncoder(final PasswordEncoder passwordEncoder) { this.passwordEncoder = passwordEncoder; } /** * @return true if the credentials are not null and the credentials class is * assignable from UsernamePasswordCredentials. */ public final boolean supports(final Credentials credentials) { return credentials != null && UsernamePasswordCredentials.class.isAssignableFrom(credentials .getClass()); } }
Our trivial example revisited
Suppose again that the password for any given username is the number of letters in that username.
By extending the abstract class, we can implement this handler more simply. The abstract class handles casting the Credentials to a UsernamePasswordCredentials and handles implementing the supports() method.
package org.jasig.cas.authentication.handler.support; import org.jasig.cas.authentication.handler.AuthenticationException; import org.jasig.cas.authentication.principal.UsernamePasswordCredentials; /** * Authenticates where the presented password is the integer length of the * username. */ public class UsernameLengthAuthnHandler extends AbstractUsernamePasswordAuthenticationHandler { protected boolean authenticateUsernamePasswordInternal( UsernamePasswordCredentials credentials) throws AuthenticationException { String username = credentials.getUsername(); String password = credentials.getPassword(); String correctPassword = Integer.toString(username.length()); return correctPassword.equals(password); } }