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

[jsr375-experts] Re: FORM authentication mechanism implemented

From: Ivar Grimstad <ivar.grimstad_at_gmail.com>
Date: Mon, 14 Mar 2016 16:05:12 +0000

On Mon, Mar 14, 2016 at 4:45 PM arjan tijms <arjan.tijms_at_gmail.com> wrote:

> Hi,
>
> On Mon, Mar 14, 2016 at 4:15 PM, Ivar Grimstad <ivar.grimstad_at_gmail.com>
> wrote:
>
>> Hi Arjan,
>>
>> It looks great! Thanks!
>>
>
> Thanks ;)
>
>
>> Should the form name and parameter names be configurable with defaults
>> j_security_check, j_username and j_password? Or are this names so common to
>> use that they can be considered standard? WDYT?
>>
>
> For the Servlet FORM mechanism they are hardcoded for now indeed, but the
> two interceptors used, @AutoApplySession and @LoginToContinue are designed
> to be re-used for other mechanisms, which can behave largely differently.
>
> So two options here ;) Make them configurable, or have a second "extended
> form" mechanism.
>

>
At any length I wanted to code up a second mechanism that is largely what
> FORM does, only depending on request.authenticate() instead of a POST to
> j_security_check. With that you can create your own form and have JSF (or
> whatever) validations, but still have the automatic remember and forward to
> a login page whenever you try to access a protected page without being
> logged-in.
>
>
> I ported your servlet example to MVC 1.0. It works as a charm :)
>> Since there are no Servlet, the
>> @ServletSecurity(@HttpConstraint(rolesAllowed = "foo")) does not kick in
>> and this currently needs to be configured in web.xml, but I guess this will
>> be solved when we start looking at the Role/Permission Assignment epic.
>>
>
> In the case of MVC, it's mainly the @RolesAllowed (or variant on that) CDI
> interceptor, right? Or did you had something else in mind here?
>

Yes, that is what I meant.


>
>
>> My sample code is here:
>> https://github.com/ivargrimstad/security-samples/tree/master/form-embedded-mvc
>>
>
> Nice one ;)
>
> I was doubting a little btw about this construct now:
>
> @FormAuthenticationMechanismDefinition(
> loginToContinue = @LoginToContinue(
> loginPage = "/ui/login",
> errorPage = "/ui/login?auth=-1"))
>
> It now re-uses the @LoginToContinue annotation. Variants are:
>
> @FormAuthenticationMechanismDefinition(
> @LoginToContinue(
> loginPage = "/ui/login",
> errorPage = "/ui/login?auth=-1"))
>
> and
>
> @FormAuthenticationMechanismDefinition(
> loginPage = "/ui/login",
> errorPage = "/ui/login?auth=-1")
>
> Last one looks a bit nicer, but since the FORM mechanism re-uses
> @LoginToContinue you'd have to keep the two in sync. Not sure yet what is
> best...
>

Yes, the last one looks a little better. I think developers will appreciate
less typing, even if the IDEs give you code completion...


>
> Kind regards,
> Arjan Tijms
>
>
>
>
>
>
>
>>
>>
>> Ivar
>>
>> On Mon, Mar 14, 2016 at 12:59 AM arjan tijms <arjan.tijms_at_gmail.com>
>> 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
>>>
>>>
>>>
>>>
>>>
>>>