users@jaspic-spec.java.net

Re: [servlet-spec users] Re: request#authenticate - start new vs continue

From: arjan tijms <arjan.tijms_at_gmail.com>
Date: Wed, 20 Apr 2016 09:26:10 +0200

Hi,

On Wed, Apr 20, 2016 at 4:28 AM, Greg Wilkins <gregw_at_webtide.com> wrote:
>
>
> Specifically we have a concept of deferred authentication, were
> credentials may have been supplied (by BASIC/DIGEST headers or be a part of
> a current session), but that have not been checked because the request did
> not match a security constraint with authentication requirements.
>
> In such cases, a call to authenticate will attempt a login with the
> credentials/session already available and thus will often be able to return
> true and not need to start a *new* authentication interaction.
>

That's really interesting.

If I understand correctly:

* Caller accesses public resource and already provides header (pre-emptive
authentication)
* Jetty does not authenticate the caller, but remembers the header
* Caller accesses 0 to N other public resources
* Caller accesses public resource where applications calls
request.authenticate
* Jetty authenticates right away using the previously remembered header

Did I understand that correctly?

If that's the case then perhaps the need for a request#authenticate method
to express the intention (new or continue) is even bigger.

Namely, another case is that a user abandons a previously entered dialog
(could be Servlet's FORM or a custom authentication mechanisms like OAuth2
or multi-factor, or perhaps indeed Jetty's deferred authentication), and
then explicitly clicks a login button. If the application then calls
request#authenticate again and there's still authentication state, it's
hard for the authentication mechanism to determine whether new or continue
is meant.

Sometimes heuristics can be used to determine new vs continue, sometimes
(with custom authentication mechanisms) this is not possible.


Sorry I'm not following your example? I can see no call to authenticate()
> in that example code?
> Are you saying you want to authenticate without using a form target
> of /j_security_check ??? If so, isn't that what the login API is for?
>

The login API (HttpServletRequest#login) is unfortunately very restricted.
It only accepts username/password as credentials and when standard custom
authentication modules (JSR 196/JASPIC) are used it doesn't work at all
(spec mandates exception to be thrown).

In my example a custom authentication mechanism is used. Indeed, it does
not uses a virtual resource like /j_security_check.

Instead, it allows the application to collect the credentials itself, so
the form posts back to the application URL. The code running on that
postback can then do whatever validation it deems necessary (the example
uses JSF and bean validation for that, but it can be pure Servlet code, of
course).

The request#authenticate() call is behind the implementation of
SecurityContext#authenticate:

  public AuthStatus authenticate(HttpServletResponse response,
AuthenticationParameters parameters) {
        try {
            if (Jaspic.authenticate(request, response, parameters)) {
                // All servers return true when authentication actually
took place
                return SUCCESS;
            }

            // GlassFish returns false when either authentication is in
progress or authentication
            // failed (or was not done at all).
            // Therefore we need to rely on the status we saved as a
request attribute
            return getLastStatus(request);
        } catch (IllegalArgumentException e) { // TODO: exception type not
ideal
            // JBoss returns false when authentication is in progress, but
throws exception when
            // authentication fails (or was not done at all).
            return FAILURE;
        }
    }

And then:

public static boolean authenticate(HttpServletRequest request,
HttpServletResponse response, AuthenticationParameters authParameters) {
try {
   // JASPIC 1.1 does not have any way to distinguish between a
   // SAM called at start of a request or following request#authenticate.
   // See https://java.net/jira/browse/JASPIC_SPEC-5

   // We now add this as a request attribute instead, but should better
   // be the MessageInfo map
request.setAttribute(IS_AUTHENTICATION, true);
if (authParameters != null) {
request.setAttribute(AUTH_PARAMS, authParameters);
}
return request.authenticate(response);
} catch (ServletException | IOException e) {
throw new IllegalArgumentException(e);
} finally {
request.removeAttribute(IS_AUTHENTICATION);
if (authParameters != null) {
request.removeAttribute(AUTH_PARAMS);
}
}
}

See:
https://github.com/javaee-security-spec/soteria/blob/master/impl/src/main/java/org/glassfish/soteria/mechanisms/jaspic/Jaspic.java#L95

In this case the authentication mechanism is able to use heuristics to
determine new/continue:

   private boolean isOnProtectedURLWithStaleData(HttpMessageContext
httpMessageContext) {
        return
            httpMessageContext.isProtected() &&

            // When HttpServletRequest#authenticate is called, it counts as
"mandated" authentication
            // which here means isProtected() is true. But we want to use
HttpServletRequest#authenticate
            // to resume a dialog started by accessing a protected page, so
therefore exclude it here.
            !httpMessageContext.isAuthenticationRequest() &&
            getSavedRequest(httpMessageContext.getRequest()) != null &&
            getSavedAuthentication(httpMessageContext.getRequest()) == null
&&

            // Some servers consider the Servlet special URL
"/j_security_check" as
            // a protected URL

!httpMessageContext.getRequest().getRequestURI().endsWith("j_security_check");
    }

See:
https://github.com/javaee-security-spec/soteria/blob/master/impl/src/main/java/org/glassfish/soteria/cdi/LoginToContinueInterceptor.java#L225

But in general those heuristics are not always possible as mentioned or at
best quite complicated (and thus error prone).

Hope it's more clear now.

Kind regards,
Arjan Tijms






>
> Can you explain in a little bit more detail... perhaps leaving out JSF and
> expressing it in pure Servlet API terms?
>
> regards
>
>
> --
> Greg Wilkins <gregw@webtide.com> CTO http://webtide.com
>