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: Sat, 24 Oct 2015 21:55:11 -0700

Hi Arjan,

That example app is terrific! I would like to demonstrate it during the
JavaOne JSR 375 BOF. The app does not look like much, but when you
realize the caller was authenticated using data from an annotation, you
realize how these simple standardizations will make a BIG impact.

The only issue I ran into when deploying on GlassFish 4.1 was the
default app name was " mechanism-to-store-app-1.0-SNAPSHOT", which made
the context root " /mechanism-to-store-app-1.0-SNAPSHOT". But I changed
the context root to "/mechanism-to-store-app" in the GF admin console
and the example works as you described.

Thanks again!
Alex

On 10/24/15 6:19 PM, Alex Kosowski wrote:
> Thanks Arjan,
>
> Perhaps you would provide an example of using the
> @CredentialCapable(UsernamePasswordCredential.class) qualifier? Also,
> perhaps you would have an example of extending a standard identity
> store with a custom defined one to support a custom defined credential?
>
> Just some suggestions.
>
> With regards,
> Alex
>
> On 10/19/15 2:53 PM, arjan tijms wrote:
>> Hi,
>>
>> On Mon, Oct 19, 2015 at 7:46 PM, Alex Kosowski
>> <alex.kosowski_at_oracle.com <mailto:alex.kosowski_at_oracle.com>> wrote:
>>
>> Thanks Arjan! I cannot wait to try it!
>>
>>
>> Looking forward to hearing feedback, thanks.
>>
>> I'll try later this week to create another project that implements
>> some more identity stores and also has an example for a user defined
>> one.
>>
>> Kind regards,
>> Arjan Tijms
>>
>>
>>
>>
>>
>>
>> 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>
>> <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
>>
>>
>>
>>
>>
>>
>>
>>
>>