users@javaee-security-spec.java.net

[javaee-security-spec users] [jsr375-experts] Working example app demonstrating identity store usage

From: arjan tijms <arjan.tijms_at_gmail.com>
Date: Mon, 19 Oct 2015 19:27:58 +0200

Hi,

I've created a working zero config demo application that shows how an
authentication mechanism can use an identity store.

It's located here: https://github.com/arjantijms/mechanism-to-store

It uses a selection of the types Alex proposed, with the adjustments as
discussed. This demo app uses one of the two ways to set an Identity Store;
"declaratively for a standard provided store" as proposed by Reza in issue
https://java.net/jira/browse/JAVAEE_SECURITY_SPEC-9 (the other option which
is not demoed here is a store fully defined by the application).

The store is declared using an annotation corresponding to option 3 in the
list presented earlier. I put this annotation on a test Servlet (but it can
be put anywhere):

@EmbeddedIdentityStoreDefinition({
    @Credentials(callerName = "reza", password = "secret1", groups = {
"foo", "bar" }),
    @Credentials(callerName = "alex", password = "secret2", groups = {
"foo", "kaz" }),
    @Credentials(callerName = "arjan", password = "secret3", groups = {
"foo" }) })
@DeclareRoles({ "foo", "bar", "kaz" })
@WebServlet("/servlet")
public class Servlet extends HttpServlet

See:
https://github.com/arjantijms/mechanism-to-store/blob/master/app/src/main/java/test/Servlet.java


This annotation is picked up by a CDI extension and a Bean<T> is created
for it:

    public <T> void processBean(@Observes ProcessBean<T> eventIn,
BeanManager beanManager) {

        ProcessBean<T> event = eventIn; // JDK8 u60 workaround

        Optional<EmbeddedIdentityStoreDefinition> result =
getAnnotation(beanManager, event.getAnnotated(),
EmbeddedIdentityStoreDefinition.class);
        if (result.isPresent()) {
            identityStoreBean = new CdiProducer<IdentityStore>()
                .scope(ApplicationScoped.class)
                .types(IdentityStore.class)
                .create(e -> new
EmbeddedIdentityStore(result.get().value()));

        }
    }

This Bean<T> is subsequently registered with the container:

    public void afterBean(final @Observes AfterBeanDiscovery
afterBeanDiscovery) {
        if (identityStoreBean != null) {
            afterBeanDiscovery.addBean(identityStoreBean);
        }
    }

See:
https://github.com/arjantijms/mechanism-to-store/blob/master/jsr375/src/main/java/org/glassfish/jsr375/cdi/CdiExtension.java


The Identity Store implementation is vendor specific. It's not in the
javax.security API package. This way vendors can optimise the
implementation and/or map it to their existing artefacts.

The sample implementation maps the data in the annotation to a Map:

    private Map<String, Credentials> callerToCredentials;

    public EmbeddedIdentityStore(Credentials[] credentials) {
        callerToCredentials = stream(credentials).collect(toMap(
            e -> e.callerName(),
            e -> e)
        );
    }

And in the validate() method it simply checks if the credentials for the
requested caller name are present:

    public CredentialValidationResult validate(UsernamePasswordCredential
usernamePasswordCredential) {
        Credentials credentials =
callerToCredentials.get(usernamePasswordCredential.getCaller());

        if (credentials != null &&
usernamePasswordCredential.getPassword().compareTo(credentials.password()))
{
            return new CredentialValidationResult(
                VALID,
                credentials.callerName(),
                asList(credentials.groups())
            );
        }

        return INVALID_RESULT;
    }

See:
https://github.com/arjantijms/mechanism-to-store/blob/master/jsr375/src/main/java/org/glassfish/jsr375/identitystores/EmbeddedIdentityStore.java


The application uses a very basic SAM. This one uses the plain
non-simplified JASPIC API. The SAM obtains the identity store via CDI and
then utilises it to perform the "credential to identity data" function:

    String name = request.getParameter("name");
    Password password = new Password(request.getParameter("password"));

    // Obtain a reference to the Identity Store
    IdentityStore identityStore =
CDI.current().select(IdentityStore.class).get();

    // Delegate the {credentials in -> identity data out} function to
    // the Identity Store
    CredentialValidationResult result = identityStore.validate(new
UsernamePasswordCredential(name, password));

    if (result.getStatus() == VALID) {
        callbacks = new Callback[] {
            // The name of the authenticated caller
            new CallerPrincipalCallback(clientSubject,
result.getCallerName()),
            // the groups of the authenticated caller (for test
            // assume non-null, non-empty)
            new GroupPrincipalCallback(clientSubject,
result.getCallerGroups().toArray(new String[0])) };
    } else {
        throw new AuthException("Login failed");
    }

See:
https://github.com/arjantijms/mechanism-to-store/blob/master/app/src/main/java/test/TestServerAuthModule.java


Finally the demo app uses a simple (non-protected) Servlet (the one shown
above) that prints out the details of the authenticated user. If the
application is deployed to a stock GlassFish without any configuration
whatsoever being done it can be requested via:

http://localhost:8080/mechanism-to-store-app/servlet?name=reza&password=secret1

If all went well this prints out the following:

This is a servlet
web username: reza
web user has role "foo": true
web user has role "bar": true
web user has role "kaz": false

Needless to say here that this is just for demo'ing one of the smallest
possible SAMs that interact with the caller. Putting the password in the
URL is of course not suited for any real live usage.

Note that this particular demo only demonstrates a few of the discussed
options. I also made a few practical choices here and there to be able to
implement the application which can of course be discussed further.

Thoughts?

Kind regards,
Arjan Tijms