[security-dev] Credentials API redesign
Bill Burke
bburke at redhat.com
Thu Dec 6 08:46:33 EST 2012
Is there an API for determining the required credentials a new user must
specify before they are enabled? I'm thinking of registration. If
you're writing an IDP, the IDP is going to want to know:
* Whether or not it should be displayed on a login form
* A description of the credential type
* The type of the credential
If certificate generation is required key/value pairs of the cert and
other attributes of it. etc...
Just something to think about...
On 12/5/2012 9:18 PM, Shane Bryzak wrote:
> 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
>
--
Bill Burke
JBoss, a division of Red Hat
http://bill.burkecentral.com
More information about the security-dev
mailing list