jsr375-experts@javaee-security-spec.java.net

[jsr375-experts] FORM authentication mechanism implemented

From: arjan tijms <arjan.tijms_at_gmail.com>
Date: Mon, 14 Mar 2016 00:58:37 +0100

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