users@javaee-security-spec.java.net

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

From: arjan tijms <arjan.tijms_at_gmail.com>
Date: Mon, 7 Dec 2015 10:37:06 +0100

Hi,

I have extended the example a little. To keep the original example as-is
and as small/simple as possible, I did the extended work in a new repo
here: https://github.com/arjantijms/mechanism-to-store-x

This adds:

* Working implementation and example of @DataBaseIdentityStoreDefinition
* Example of custom (app provided) identity store.

See:

*
https://github.com/arjantijms/mechanism-to-store-x/blob/master/app-db/src/main/java/test/Servlet.java
*
https://github.com/arjantijms/mechanism-to-store-x/blob/master/app-custom/src/main/java/test/TestIdentityStore.java

I also fixed the "mechanism-to-store-app-1.0-SNAPSHOT" name. The example
now uses simple names for the war and therefor the URL:
 localhost:8080/app-db, localhost:8080/app-mem, localhost:8080/app-custom.

The examples were tested on GlassFish (4.1.1) and JBoss (WildFly 10rc4).
Note that JBoss needs the proprietary JASPIC activation before this works
(still hoping Darran can do something here to lift this).

In order to work around a GlassFish bug (see
https://java.net/jira/browse/GLASSFISH-21447) I had to put the data source
in java:global. Due to another GlassFish bug where GF doesn't unbind the
data source when the application is undeployed you actually have to stop
and start GlassFish before the deploying the app again.

Kind regards,
Arjan Tijms




On Sun, Oct 25, 2015 at 5:55 AM, Alex Kosowski <alex.kosowski_at_oracle.com>
wrote:

> 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>
> 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
>>> >
>>>
>>> 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
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>>>
>