jsr375-experts@javaee-security-spec.java.net

[jsr375-experts] Re: Working example app demonstrating identity store usage

From: Alex Kosowski <alex.kosowski_at_oracle.com>
Date: Mon, 19 Oct 2015 13:46:25 -0400

Thanks Arjan! I cannot wait to try it!

On 10/19/15 1:27 PM, arjan tijms wrote:
> 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
> <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
>
>
>
>
>
>
>
>