users@javaee-security-spec.java.net

[javaee-security-spec users] [jsr375-experts] Re: Remember me

From: arjan tijms <arjan.tijms_at_gmail.com>
Date: Tue, 12 Jan 2016 22:05:18 +0100

Hi,

I did an initial prototype. It's based on an @RememberMe interceptor that a
user puts on an authentication mechanism.

The body of the main interceptor method looks as follows:

   private AuthStatus validateRequest(InvocationContext invocationContext,
HttpServletRequest request, HttpServletResponse response,
HttpMessageContext httpMessageContext) throws Exception {

        RememberMeIdentityStore rememberMeIdentityStore =
rememberMeIdentityStoreInstance.get();

        Cookie rememberMeCookie = getCookie(request, "JREMEMBERMEID");

        if (rememberMeCookie != null) {

            // There's a JREMEMBERMEID cookie, see if we can use it to
authenticate

            CredentialValidationResult result =
rememberMeIdentityStore.validate(
                new RememberMeCredential(rememberMeCookie.getValue())
            );

            if (result.getStatus() == VALID) {
                // The remember me store contained an authenticated
identity associated with
                // the given token, use it to authenticate with the
container
                return httpMessageContext.notifyContainerAboutLogin(
                    result.getCallerPrincipal(), result.getCallerGroups());
            } else {
                // The token appears to be no longer valid, or perhaps
wasn't valid
                // to begin with. Remove the cookie.
                removeCookie(request, response, "JREMEMBERMEID");
            }
        }

        // Try to authenticate with the next interceptor or actual
authentication mechanism
        AuthStatus authstatus = (AuthStatus) invocationContext.proceed();

        if (authstatus == SUCCESS &&
httpMessageContext.getCallerPrincipal() != null) {

            // Authentication succeeded; store the authenticated identity
in the
            // remember me store and send a cookie with a token that can be
used
            // to retrieve this stored identity later

            String token = rememberMeIdentityStore.generateLoginToken(
                httpMessageContext.getCallerPrincipal(),
                httpMessageContext.getRoles()
            );

            saveCookie(request, response, "JREMEMBERMEID", token, (int)
DAYS.toSeconds(14));
        }

        return authstatus;
    }

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

Expiration time etc is hardcoded in this initial attempt, but would
probably best be adjustable via the annotation.

I've created a new demo app here:
https://github.com/arjantijms/mechanism-to-store-x/tree/master/app-custom-rememberme

This app provides a special RememberMeIdentityStore implementation:

@ApplicationScoped
public class TestRememberMeIdentityStore implements RememberMeIdentityStore
{

private final Map<String, CredentialValidationResult> identities = new
ConcurrentHashMap<>();
@Override
public CredentialValidationResult validate(RememberMeCredential credential)
{
if (identities.containsKey(credential.getToken())) {
return identities.get(credential.getToken());
}
return INVALID_RESULT;
}
@Override
public String generateLoginToken(CallerPrincipal callerPrincipal,
List<String> groups) {
String token = UUID.randomUUID().toString();
// NOTE: FOR EXAMPLE ONLY. AS TOKENKEY WOULD EFFECTIVELY BECOME THE
REPLACEMENT PASSWORD
// IT SHOULD NORMALLY NOT BE STORED DIRECTLY BUT EG USING STRONG HASHING
identities.put(token, new CredentialValidationResult(VALID,
callerPrincipal, groups));
return token;
}
@Override
public void removeLoginToken(String token) {
identities.remove(token);
}
}

This shows that the "intercepting" identity store is completely separate
from the main identity store. E.g. the main one could be an LDAP based one,
while this example store effectively uses a Map in application scope.
Obviously this is only for demonstration purposes and actual production
ready remember me stores would store at least a hash of the token, have
some token expiration policies in place, would likely use a database
instead the application scope and what have you.

One thing that the prototype doesn't do yet is letting an individual user
choose whether remember me is wanted or not. Theoretically this could be
done via an extra method on the RememberMeIdentityStore, but I feel this
would be mixing up concerns.

The identity store should just be about data in/out, while "something else"
should handle the user interaction.

At the cost of introducing an extra moving part, I'd like to separate out
this concern. One option would be another CDI bean with a predefined
interace, another would be an EL expression on the @RememberMe annotation
or a separate annotation to mark a method:

E.g.

@RememberMe(
    isRememberMe="#{myBean.isRememberMe}"
)
@RequestScoped
public class TestAuthenticationMechanism implements
HttpAuthenticationMechanism


or using a method from the annotated mechanism itself via EL:

@RememberMe(
    isRememberMe="#{testAuthenticationMechanism.isRememberMe}"
)
@RequestScoped
@Named
public class TestAuthenticationMechanism implements
HttpAuthenticationMechanism


or using an annotated method on the same type that has the @RememberMe:

@RememberMe
@RequestScoped
public class TestAuthenticationMechanism implements
HttpAuthenticationMechanism {

    @IsRememberMe
    public boolean isRememberMe() {
         return true; // or do whatever custom logic is needed
    }

    // ...
}


Thoughts?

Kind regards,
Arjan Tijms





On Tue, Jan 12, 2016 at 1:19 AM, arjan tijms <arjan.tijms_at_gmail.com> wrote:

> Hi,
>
> I created a new issue here and linked it to the authentication mechanism
> story. If you or anyone else it's better to have this be its own top-level
> story I can change that of course.
>
> I hope to succeed in prototyping an initial version soon.
>
> Kind regards,
> Arjan Tijms
>
>
>
> On Mon, Jan 11, 2016 at 7:18 AM, Werner Keil <werner.keil_at_gmail.com>
> wrote:
>
>> Arjan,
>>
>> Thanks for the Initiative.
>> Would you create a new Story for that in Jira?
>>
>> Cheers,
>> Werner
>> Am 10.01.2016 22:00 schrieb "arjan tijms" <arjan.tijms_at_gmail.com>:
>>
>>> Hi,
>>>
>>> One of the extra features that I was looking at for the initial EDR is a
>>> "remember me" facility.
>>>
>>> "Remember me" means that a caller initially authenticates with normal
>>> credentials, after which the container stores the authenticated identity
>>> (name + roles) somewhere and sends back a cookie with a token.
>>>
>>> This facility is not entirely trivial and goes a little beyond the low
>>> hanging fruit that Alex asked for. Still I'd like to give it a shot, but
>>> obviously this hasn't the highest priority and I would be okay with moving
>>> this to a later EDR.
>>>
>>> The design I was now thinking about involves the following:
>>>
>>> * Interceptor for the validateRequest and clearSubject methods
>>> * A special type of IdentityStore that has additional
>>> generateToken(String caller, List<String> groups) method and
>>> removeToken(String token) methods
>>> * Some helper code for setting/clearing cookies
>>>
>>> It would be essentially like the AutoApplySession interceptor, but using
>>> a user provided storage instead of the HTTP session. See
>>> https://github.com/arjantijms/mechanism-to-store-x/blob/master/jsr375/src/main/java/org/glassfish/jsr375/cdi/AutoApplySessionInterceptor.java
>>>
>>> Thoughts?
>>>
>>> Kind regards,
>>> Arjan Tijms
>>>
>>>
>