Product SiteDocumentation Site

4.5. Implementing a Custom CredentialHandler

In this section we'll dissect the PasswordCredentialHandler to learn how to create a custom credential handler. The AbstractCredentialHandler abstract class is provided to simplify the process of creating a new credential handler, and is also used by PasswordCredentialHandler as a base class:
Let's start by looking at the class declaration for PasswordCredentialHandler:
@SupportsCredentials(
        credentialClass = {UsernamePasswordCredentials.class, Password.class},
        credentialStorage = EncodedPasswordStorage.class)
public class PasswordCredentialHandler<S extends CredentialStore<?>, 
    V extends UsernamePasswordCredentials, 
    U extends Password>
    extends AbstractCredentialHandler<S, V, U> {
The @SupportsCredentials annotation is used to declare exactly which credential classes are supported by the credential handler (as indicated by the credentialClass annotation member). The supported credential classes include both credentials used to authenticate via the validate() method (i.e. a class that implements the org.picketlink.idm.credential.Credentials interface, in this example UsernamePasswordCredentials) and the actual encapsulated credential value (e.g. org.picketlink.idm.credential.Password). These supported credential classes are also reflected in the generic type declaration of the class itself - the V and U types in the code above. The credentialStorage annotation member declares the storage class used to persist the necessary state for the credential. The storage class must implement the org.picketlink.idm.credential.storage.CredentialStorage interface.
The setup() method is executed only once and is used to perform any required initialization for the credential handler. In the case of PasswordCredentialHandler, the setup() method reads the configuration properties (made available from store.getConfig().getCredentialHandlerProperties()) and uses those property values to initialize the state required for encoding password values.
@Override
public void setup(S store) {
    super.setup(store);

    Map<String, Object> options = store.getConfig().getCredentialHandlerProperties();

    if (options != null) {
        Object providedEncoder = options.get(PASSWORD_ENCODER);

        if (providedEncoder != null) {
            if (PasswordEncoder.class.isInstance(providedEncoder)) {
                this.passwordEncoder = (PasswordEncoder) providedEncoder;
            } else {
                throw new SecurityConfigurationException("The password encoder [" + 
                    providedEncoder + "] must be an instance of " + 
                    PasswordEncoder.class.getName());
            }
        }

        Object renewRandomNumberGeneratorInterval = options.get(
            RENEW_RANDOM_NUMBER_GENERATOR_INTERVAL);

        if (renewRandomNumberGeneratorInterval != null) {
            this.renewRandomNumberGeneratorInterval = Integer.valueOf(
                renewRandomNumberGeneratorInterval.toString());
        }

        Object secureRandomProvider = options.get(SECURE_RANDOM_PROVIDER);

        if (secureRandomProvider != null) {
            this.secureRandomProvider = (SecureRandomProvider) secureRandomProvider;
        } else {
            Object saltAlgorithm = options.get(ALGORITHM_RANDOM_NUMBER);

            if (saltAlgorithm == null) {
                saltAlgorithm = DEFAULT_SALT_ALGORITHM;
            }

            Object keyLengthRandomNumber = options.get(KEY_LENGTH_RANDOM_NUMBER);

            if (keyLengthRandomNumber == null) {
                keyLengthRandomNumber = Integer.valueOf(0);
            }

            this.secureRandomProvider = new DefaultSecureRandomProvider(
                saltAlgorithm.toString(), 
                Integer.valueOf(keyLengthRandomNumber.toString()));
        }
    }

    this.secureRandom = createSecureRandom();
}
The credential validation logic is defined by the validateCredential() method. This method (which the parent AbstractCredentialHandler class declares as an abstract method) checks the validity of the credential value passed in and either returns true if the credential is valid or false if it is not. The validateCredential() method is a convenience method which delegates much of the boilerplate code required for credential validation to AbstractCredentialHandler, allowing the subclass to simply define the bare minimum code required to validate the credential. If you were to implement a CredentialHandler without using AbstractCredentialHandler as a base class, you would instead need to implement the validate() method which in general requires a fair bit more code.
@Override
protected boolean validateCredential(final CredentialStorage storage, 
    final V credentials) {
    EncodedPasswordStorage hash = (EncodedPasswordStorage) storage;

    if (hash != null) {
        String rawPassword = new String(credentials.getPassword().getValue());
        return this.passwordEncoder.verify(saltPassword(rawPassword, 
            hash.getSalt()), hash.getEncodedHash());
    }

    return false;
}
The update() method (in contrast to validateCredential) is defined by the CredentialHandler interface itself, and is used to persist a credential value to the backend identity store. In PasswordCredentialHandler this method creates a new instance of EncodedPasswordStorage, a CredentialStorage implementation that represents a password's hash and salt values. The salt value in this implementation is randomly generated using the configured property values, and then used to encode the password hash. This value is then stored by calling the store.storeCredential() method.
@Override
public void update(IdentityContext context, Account account, U password, S store,
                   Date effectiveDate, Date expiryDate) {

    EncodedPasswordStorage hash = new EncodedPasswordStorage();

    if (password.getValue() == null || isNullOrEmpty(password.getValue().toString())) {
        throw MESSAGES.credentialInvalidPassword();
    }

    String rawPassword = new String(password.getValue());

    String passwordSalt = generateSalt();

    hash.setSalt(passwordSalt);
    hash.setEncodedHash(this.passwordEncoder.encode(saltPassword(rawPassword, 
        passwordSalt)));

    if (effectiveDate != null) {
        hash.setEffectiveDate(effectiveDate);
    }

    hash.setExpiryDate(expiryDate);

    store.storeCredential(context, account, hash);
}