[security-dev] Credentials API redesign

Pedro Igor Silva psilva at redhat.com
Thu Dec 6 08:07:37 EST 2012


Maybe another design would be:

   public interface Credential<T extends Serializable> {
             
       String getUserName();
       T getCredential();

       void invalidate();

   }

    public interface CredentialHandler<T extends Credential> {
  
        T getCredential();

        void store();
        Credential load();

        boolean validate();

        void setIdentityManager(IdentityManager identityManager);        
    }

    public interface IdentityManager {
        
        void updateCredential(CredentialHandler<T extends Credential> credentialHandler);
        boolean validateCredential(CredentialHandler<T extends Credential> credentialHandler);

    }

    The main idea is decouple from the IDM the logic to store, load and validate credentials. That should be done by some client API like PicketLink Core, PicketBox or some other authentication API. The IDM can provide some built-in implementations for PasswordCredentialHandler or X509CertificateCredentialHandler, for example.

    Another possible benefit is that the IDM does not need to know about how credentials/handlers are configured.

    The handler can just use the IdentityManager to store the credentials, using user's attributes or something like that.

    Let's say we want to perform a username/password authentication:

         Client Code

         UsernamePasswordCredential credential = new UsernamePasswordCredential("username","passwd");
         PasswordCredentialHandler credentialHandler = new PasswordCredentialHandler(credential);

         if (identityManager.validateCredential(credentialHandler)) {
             User user = identityManager.getUser(credential.getUsername);

             // continue with the authentication
         } else {
             // handle bad user
         }

     Now, let's say we want some some authentication that requires several steps (otp authentication using username + password + token):

         OTPCredential credential = new OTPCredential("username", "passwd"); // the user can also provide the token. no challenge.
         OTPCredentialHandler credentialHandler = new OTPCredentialHandler(credential);

         if (identityManager.validateCredential(credentialHandler)) {
            // valid user, token was already provided by the user
         } else if (credentialHandler.getStatus().equals(OTPCredentialHandler.TOKEN_CHALLENGE)) {
            // tell the user that we also need the token to perform the authenticatin
         } else {
            // handle bad user
         }

      Makes sense ?

Regards.
Pedro Igor
         

----- Original Message -----
From: "Shane Bryzak" <sbryzak at redhat.com>
To: security-dev at lists.jboss.org
Sent: Thursday, December 6, 2012 12:18:35 AM
Subject: [security-dev] Credentials API redesign

Hey guys,

I've completed the first round of redesigns for the credentials API 
based on the feedback provided by Darran and others.  It might require 
some minimal tweaking to address a couple of minor edge cases, however 
at this point in time I'd like to run through the basic design to see 
what you guys think.  Up front I must say that it's more complex than I 
had hoped, however after analysing all the use cases I feel that the 
complexity is a necessity for providing a truly robust, extensible API.

To try and present this in a logical manner, I'm going to describe the 
credentials API in terms of the chronological order of events during the 
authentication process.  To start with, let's take a look at the 
LoginCredentials interface:

public interface LoginCredentials {
     void invalidate();
}

Implementations of this interface are intended to contain the state 
provided by the user in order to carry out authentication.  While the 
Credential interface is designed to represent an atomic credential value 
(such as a password, certificate, or biometric data such as a 
fingerprint), LoginCredentials is designed to encapsulate the credential 
value with additional state required by the authentication process.  
Let's take a look at an implementation supporting the most common form 
of credentials, a username and password:

public class UsernamePasswordCredentials implements LoginCredentials {

     private String username;

     private PasswordCredential password;

     public String getUsername() {
         return username;
     }

     public void setUsername(String username) {
         this.username = username;
     }

     public PasswordCredential getPassword() {
         return password;
     }

     public void setPassword(PasswordCredential password) {
         this.password = password;
     }

     @Override
     public void invalidate() {
         username = null;
         password.clear();
     }
}

This implementation provides the capability for setting a username 
(represented as a String) and a PasswordCredential.  Other 
implementations are free to define whichever properties they require, 
for example a CertificateCredentials class may simply contain an 
X509CertificateCredential and nothing else.

Once the user provides their credentials, the LoginCredentials instance 
can be passed to IdentityManager via the following method:

public interface IdentityManager {
     User validateCredentials(LoginCredentials credentials);
}

To handle the actual processing of the credentials and perform whatever 
business logic is required to authenticate the user, the following SPI 
interface is used:

public interface CredentialHandler {
     User validate(LoginCredentials credentials, IdentityStore store);
     void update(User user, Credential credential, IdentityStore store);
}

Since authentication is handled differently depending on what type of 
IdentityStore is being used, there will be many CredentialHandler 
implementations to address each use case combination.  To determine the 
correct CredentialHandler implementation to use for authentication, the 
IdentityManager calls CredentialHandlerFactory.getCredentialHandler(), 
passing in the class of the LoginCredentials parameter and the class of 
the IdentityStore parameter:

public interface CredentialHandlerFactory {

     CredentialHandler getCredentialHandler(Class<? extends 
LoginCredentials> credentialsClass,
             Class<? extends IdentityStore> identityStoreClass);
}

This allows us to provide a separate CredentialHandler implementation 
for each combination of LoginCredentials and IdentityStore.  For 
example, for a standard username/password authentication using 
JPAIdentityStore we may have a JPAUsernamePasswordCredentialHandler that 
compares a calculated hash based on the password (and possible other 
values) against a pre-calculated hash stored in the database.  For a 
username/password authentication against LDAPIdentityStore we may have 
an LDAPUsernamePasswordCredentialHandler that performs a bind operation 
against the directory to ensure that the credentials supplied are 
valid.  As the SPI is extensible, it is also possible for developers to 
provide their own CredentialHandler implementations to suit whichever 
authentication process they require.

If the CredentialHandler.validate() method completes successfully, it is 
expected to return a User instance to indicate that authentication was 
successful.  If authentication is unsuccessful (or possibly requires 
multiple steps) then it's up to the caller and implementation to cater 
for this, most likely by providing additional state within the 
LoginCredentials implementation.

To sidetrack for a moment, the CredentialHandlerFactory is configured as 
part of the IdentityConfiguration which is passed to the 
IdentityManager's bootstrap method during startup:

public class IdentityConfiguration {
       public CredentialHandlerFactory getCredentialHandlerFactory() {
         return credentialHandlerFactory;
     }

     public void setCredentialHandlerFactory(CredentialHandlerFactory 
factory) {
         this.credentialHandlerFactory = factory;
     }
}

Continuing on, the CredentialHandler is free to execute whichever 
business logic is required to authenticate the provided LoginCredentials 
value.  It has direct access to the IdentityStore instance, in which the 
previous credential management methods have been replaced with the 
following two new methods (side note - the CredentialHandler doesn't 
*have* to use these methods, it could instead possibly invoke other 
IdentityStore methods by casting the IdentityStore to the appropriate type):

public interface IdentityStore {
     void storeCredential(CredentialStorage storage);

     CredentialStorage retrieveCredential(Class<? extends 
CredentialStorage> storageClass);
}

At present CredentialStorage is a simple SPI interface that declares a 
single method:

public interface CredentialStorage {
     Date getExpiryDate();
}

The reason that we have introduced a totally new interface here rather 
than just use the Credential interface, is that credential information 
stored by the IdentityStore is often different to the credential 
information provided by a user to authenticate.  For example, in 
username/password authentication the user provides a plain text 
password, however the data we have stored in the IdentityStore is 
typically not the password itself, but a hash value (or at least I would 
hope so).  With that in mind, the introduction of the CredentialStorage 
interface formalizes the disconnect between user-provided credential 
state and stored credential state, and in turn provides a more flexible 
design.  This is not to say that a Credential implementation can't be 
dual purpose, in fact this is fully supported thanks to 
CredentialStorage essentially being a marker interface, so 
X509CertificateCredential for example could easily implement both the 
Credential and CredentialStorage interfaces.

To round this off with a solid example, here's what a CredentialStorage 
implementation that stores a password hash might look like:

public class PasswordHash implements CredentialStorage {
     private byte[] hash;
     public void setHash(byte[] hash) {
         this.hash = hash;
     }
     public byte[] getHash() {
         return hash;
     }
}

There's actually one more step here, and that's the metadata required by 
the IdentityStore to know which state of the CredentialStorage instance 
to actually store.  To enable this, we have introduced the @Stored 
annotation, which when added to the above example looks like this:

public class PasswordHash implements CredentialStorage {
     @Stored private byte[] hash;
     public void setHash(byte[] hash) {
         this.hash = hash;
     }
     public byte[] getHash() {
         return hash;
     }
}

Any properties annotated with @Stored must be Serializable, to allow 
storage by the IdentityStore.

One last thing that needs to be covered is the updating of credentials.  
IdentityManager also provides this method which is used to update a 
user's credentials:

public interface IdentityManager {
     void updateCredential(User user, Credential credential);
}

Like credential validation, a CredentialHandler is looked up the same 
way and its update() method is invoked with the User, Credential and 
IdentityStore parameters.  It is up to the CredentialHandler 
implementation to create the appropriate CredentialStorage instances (or 
perform whichever other business logic that might be required) to 
persist to the backend IdentityStore.

That pretty much covers everything.  I think this design in essences 
addresses all of the use cases discussed so far, and should be 
relatively future-proof.  Sorry again for another wall of text, but I'm 
looking forward to hearing feedback on this.  There's still quite a lot 
of work to do in this area on the implementation side of things (such as 
decoupling the existing credential validation code from the 
IdentityStore implementations and implementing it as CredentialHandlers) 
but we'll get to that once everyone has a chance to review this new design.

Thanks,
Shane
_______________________________________________
security-dev mailing list
security-dev at lists.jboss.org
https://lists.jboss.org/mailman/listinfo/security-dev


More information about the security-dev mailing list