users@javaee-security-spec.java.net

[javaee-security-spec users] [jsr375-experts] Re: FORM authentication mechanism implemented

From: Ivar Grimstad <ivar.grimstad_at_gmail.com>
Date: Fri, 08 Apr 2016 15:38:45 +0000

On Fri, Apr 8, 2016 at 5:34 PM arjan tijms <arjan.tijms_at_gmail.com> wrote:

> Hi,
>
> On Fri, Apr 8, 2016 at 5:23 PM, Ivar Grimstad <ivar.grimstad_at_gmail.com>
> wrote:
>
>> Hope I have understood correctly... with 2 phase multi-mechanism, you
>> mean support for e.g. 2 factor authentication?
>> +1 to that!
>>
>
> It's something else still ;)
>
> 2 factor is more done by a single mechanism that goes into a dialog with
> the user and asks for multiple credentials. See e.g.
> https://github.com/arjantijms/two-factor-sam/blob/master/src/main/java/net/eisele/glassfish/twofactorsam/TwoFactorServerAuthModule.java
>
> 2 phase means that you have multiple authentication mechanisms installed
> and activated, e.g. HEADER and FORM. First they all get the opportunity to
> authenticate right away (typically if pre-emptive authentication is used,
> e.g. the client already sends credentials without the server having asked
> for it).
>
> In the first phase no mechanism is allowed to write to the response.
>
> If no mechanism could authenticate right away, the second phase is
> entered. Here mechanisms may write to the response.
>
> The purpose of writing to the response is engaging into a dialog with the
> user (i.e. challenging the user). Of course only one mechanism can actually
> write to the response. To facilitate this Darran introduced the "responder"
> concept, which is a piece of code (a lambda possibly) that the
> container/runtime will call when a mechanism has been selected to be
> allowed to write to the response.
>
> Hope I explained it correctly ;)
>
> Thanks! Got it!
“He who asks a question is a fool for five minutes; he who does not ask a
question remains a fool forever”


> Kind regards,
> Arjan Tijms
>
>
>
>
>>
>>
>>>
>>>
>>>> Should also add this combination of mechanisms is not just something we
>>>> have thought we can do - it is based on real demand from the end users of
>>>> our application servers.
>>>
>>>
>>> This is great feedback, thanks!
>>>
>>>
>>>
>>>> I will have a look at this example in some more detail, the biggest
>>>> worry if mechanisms wasting resources generating redundant responses -
>>>> although with a small API change this is not far off how we have it for
>>>> Elytron.
>>>>
>>>
>>> It's indeed not the idea that mechanisms are going to waste responses.
>>> The code sketched below was just a sketch, but it should work exactly like
>>> you mentioned.
>>>
>>> Kind regards,
>>> Arjan Tijms
>>>
>>>
>>>
>>>
>>>
>>>
>>>>
>>>> Instead of the mechanism populating a response the mechanism registers
>>>> a responder during the evaluateRequest phase - if no mechanism successfully
>>>> authenticates then the responders are called.
>>>>
>>>> Something like:
>>>>>
>>>>> public class MultiAuthenticationMechanism implements
>>>>> HttpAuthenticationMechanism {
>>>>> public AuthStatus validateRequest(HttpServletRequest request,
>>>>> HttpServletResponse response, HttpMessageContext httpMsgContext) {
>>>>> List<HttpServerAuthenticationMechanism> mechanisms =
>>>>> getFromSomewhere();
>>>>> List<HttpServerRequestImpl> requests = new ArrayList();
>>>>>
>>>>> // First phase
>>>>> for (HttpServerAuthenticationMechanism mechanism :
>>>>> mechanisms) {
>>>>> HttpServerRequestImpl httpServerRequest = new
>>>>> HttpServerRequestImpl (request);
>>>>> requests.add(httpServerRequest);
>>>>> mechanism.evaluateRequest(httpServerRequest);
>>>>>
>>>>> if (httpServerRequest.authenticationComplete() {
>>>>> SecurityIdentity identity =
>>>>> httpServerRequest.getIdentity();
>>>>> return httpMsgContext.notifyContainerAboutLogin(
>>>>> identity.getPrincipal(),
>>>>> toList(identity.getRoles()));
>>>>>
>>>>> }
>>>>> }
>>>>>
>>>>> // Second phase
>>>>> for (HttpServerRequestImpl httpServerRequest : requests) {
>>>>> httpServerRequest.getResponder().sendResponse(response);
>>>>> if (httpServerRequest.authenticationInProgress()) {
>>>>> return SEND_CONTINUE;
>>>>> }
>>>>> }
>>>>> }
>>>>>
>>>>> I don't know yet how your API exactly works, so the above is just a
>>>>> rough sketch ;)
>>>>>
>>>>> Kind regards,
>>>>> Arjan Tijms
>>>>>
>>>>>
>>>>>
>>>>>
>>>>>
>>>>>
>>>>> On 21/03/16 18:31, arjan tijms wrote:
>>>>>
>>>>> True, but remember the mechanism as it's defined now is only
>>>>> EDR1
>>>>> status. Much can still be changed.
>>>>>
>>>>> Time as always is a bit of the issue. I only have so much hours
>>>>> I can
>>>>> spend on JSR 375, and there's quite an amount of other things
>>>>> that also
>>>>> would be really nice to have. For instance, we also don't have
>>>>> the
>>>>> multiple identity stores done yet.
>>>>>
>>>>> As for the multiple mechanisms though, is there any other
>>>>> server or
>>>>> security framework that you know of that has this? Or is
>>>>> Undertow/Elytron currently the only one?
>>>>>
>>>>> To get the multiple authentication mechanisms story going,
>>>>> perhaps best
>>>>> to start with creating an issue at the JSR 375 tracker and
>>>>> coding up a
>>>>> proposal for the
>>>>>
>>>>> https://github.com/javaee-security-spec/javaee-security-proposals
>>>>> repo?
>>>>> Do you think the multiple mechanisms could be implemented for
>>>>> the
>>>>> proposal using the current mechanism as a base? E.g. a single
>>>>> HttpAuthenticationMechanism implementation that does the
>>>>> 2-phased
>>>>> "try-authenticate" as you explained before?
>>>>>
>>>>> Additionally, I wonder if any of the other EG members have a
>>>>> particular
>>>>> opinion, idea or use case for the multiple mechanisms story.
>>>>> Would be
>>>>> great to collect the ideas around this.
>>>>>
>>>>> Kind regards,
>>>>> Arjan Tijms
>>>>>
>>>>>
>>>>>
>>>>>
>>>>>
>>>>> On Mon, Mar 21, 2016 at 6:53 PM, Darran Lofthouse
>>>>> <darran.lofthouse_at_redhat.com
>>>>> <mailto:darran.lofthouse_at_redhat.com>
>>>>> <mailto:darran.lofthouse_at_redhat.com
>>>>>
>>>>> <mailto:darran.lofthouse_at_redhat.com>>> wrote:
>>>>>
>>>>> I still think there is big gap here in that multiple
>>>>> mechanisms will
>>>>> not be able to operate concurrently.
>>>>>
>>>>>
>>>>> On 13/03/16 23:58, arjan tijms wrote:
>>>>>
>>>>> Hi,
>>>>>
>>>>> I just implemented a clone of Servlet's FORM
>>>>> authentication
>>>>> mechanism
>>>>> using the proposed JSR 375 API. A demo app showing it
>>>>> can be
>>>>> found at
>>>>>
>>>>> https://github.com/javaee-security-spec/soteria/tree/master/test/app-mem-form
>>>>>
>>>>> The mechanism itself looks like this:
>>>>>
>>>>> @AutoApplySession
>>>>> @LoginToContinue
>>>>> @Typed(FormAuthenticationMechanism.class)
>>>>> public class FormAuthenticationMechanism implements
>>>>> HttpAuthenticationMechanism, LoginToContinueHolder {
>>>>> private LoginToContinue loginToContinue;
>>>>>
>>>>> @Inject
>>>>> private IdentityStore identityStore;
>>>>> @Override
>>>>> public AuthStatus validateRequest(HttpServletRequest
>>>>> request,
>>>>> HttpServletResponse response, HttpMessageContext
>>>>> httpMessageContext)
>>>>> throws AuthException {
>>>>> if ("POST".equals(request.getMethod()) &&
>>>>>
>>>>> request.getRequestURI().endsWith("/j_security_check")) {
>>>>> if
>>>>> (notNull(request.getParameter("j_username"),
>>>>> request.getParameter("j_password"))) {
>>>>>
>>>>> CredentialValidationResult result =
>>>>> identityStore.validate(
>>>>> new UsernamePasswordCredential(
>>>>>
>>>>> request.getParameter("j_username"),
>>>>> new
>>>>> Password(request.getParameter("j_password"))));
>>>>>
>>>>> if (result.getStatus() == VALID) {
>>>>> return
>>>>> httpMessageContext.notifyContainerAboutLogin(
>>>>> result.getCallerPrincipal(),
>>>>> result.getCallerGroups());
>>>>> } else {
>>>>> throw new AuthException("Login
>>>>> failed");
>>>>> }
>>>>> }
>>>>> }
>>>>> return httpMessageContext.doNothing();
>>>>> }
>>>>>
>>>>> See
>>>>>
>>>>> https://github.com/javaee-security-spec/soteria/blob/master/impl/src/main/java/org/glassfish/soteria/mechanisms/FormAuthenticationMechanism.java
>>>>>
>>>>>
>>>>> This leans heavily on the @AutoApplySession and
>>>>> @LoginToContinue
>>>>> interceptors, which can both be re-used by other
>>>>> mechanisms,
>>>>>
>>>>> The heart of the @LoginToContinue interceptor contains
>>>>> this code:
>>>>>
>>>>> private AuthStatus validateRequest(InvocationContext
>>>>> invocationContext,
>>>>> HttpServletRequest request, HttpServletResponse
>>>>> response,
>>>>> HttpMessageContext httpMessageContext) throws
>>>>> Exception {
>>>>>
>>>>> if
>>>>> (isOnProtectedURLWithStaleData(httpMessageContext)) {
>>>>> removeSavedRequest(request);
>>>>> }
>>>>> if
>>>>> (isOnInitialProtectedURL(httpMessageContext)) {
>>>>> saveRequest(request);
>>>>> return httpMessageContext.forward(
>>>>>
>>>>>
>>>>> getLoginToContinueAnnotation(invocationContext).loginPage());
>>>>> }
>>>>> if (isOnLoginPostback(request)) {
>>>>> AuthStatus authstatus = null;
>>>>> try {
>>>>> authstatus = (AuthStatus)
>>>>> invocationContext.proceed();
>>>>> } catch (AuthException e) {
>>>>> authstatus = FAILURE;
>>>>> }
>>>>> if (authstatus == SUCCESS) {
>>>>> if
>>>>> (httpMessageContext.getCallerPrincipal() ==
>>>>> null) {
>>>>> return SUCCESS;
>>>>> }
>>>>> RequestData savedRequest =
>>>>> getSavedRequest(request);
>>>>> if
>>>>> (!savedRequest.matchesRequest(request)) {
>>>>> saveAuthentication(request, new
>>>>> CredentialValidationResult(
>>>>> VALID,
>>>>>
>>>>> httpMessageContext.getCallerPrincipal(),
>>>>>
>>>>> httpMessageContext.getGroups()));
>>>>> return
>>>>>
>>>>> httpMessageContext.redirect(savedRequest.getFullRequestURL());
>>>>> } // else return success
>>>>> } else {
>>>>> return httpMessageContext.redirect(
>>>>> // TODO:
>>>>> or forward?
>>>>> getBaseURL(request) +
>>>>>
>>>>>
>>>>> getLoginToContinueAnnotation(invocationContext).errorPage());
>>>>> }
>>>>> }
>>>>>
>>>>> if
>>>>> (isOnOriginalURLAfterAuthenticate(request)) {
>>>>> return httpMessageContext
>>>>> .withRequest(new
>>>>> HttpServletRequestDelegator(request,
>>>>> requestData))
>>>>> .notifyContainerAboutLogin(
>>>>> result.getCallerPrincipal(),
>>>>> result.getCallerGroups());
>>>>> }
>>>>> return httpMessageContext.doNothing();
>>>>> }
>>>>>
>>>>> See:
>>>>>
>>>>> https://github.com/javaee-security-spec/soteria/blob/master/impl/src/main/java/org/glassfish/soteria/cdi/LoginToContinueInterceptor.java
>>>>>
>>>>>
>>>>> FORM is a rather nasty mechanism to implement, and
>>>>> going for the
>>>>> re-usable parts took a little extra time, but it does
>>>>> show that the
>>>>> proposed mechanism API is capable of implementing a
>>>>> quite
>>>>> demanding set
>>>>> of requirements (FORM is really surprisingly demanding
>>>>> for such
>>>>> an old
>>>>> mechanism).
>>>>>
>>>>> Do note that both design and implementation are rather
>>>>> rough at the
>>>>> moment and could do with some refinements still. But I
>>>>> think
>>>>> this could
>>>>> work for an early draft.
>>>>>
>>>>> Kind regards,
>>>>> Arjan Tijms
>>>>>
>>>>>
>>>>>
>>>>>
>>>>> --
>>>>> Darran Lofthouse - Principal Software Engineer
>>>>>
>>>>> Registered in England and Wales under Company Registration No.
>>>>> 03798903
>>>>> Directors: Michael Cunningham (US), Michael O'Neill(Ireland), Paul
>>>>> Argiry (US)
>>>>>
>>>>>
>>>>>
>>>> --
>>>> Darran Lofthouse - Principal Software Engineer
>>>>
>>>> Registered in England and Wales under Company Registration No. 03798903
>>>> Directors: Michael Cunningham (US), Michael O'Neill(Ireland), Paul
>>>> Argiry (US)
>>>>
>>>