JASPIC Wildfly 9 validateRequest with session
Asked Answered
T

1

5

Based on this Jaspic Example I wrote the following validateRequest method for a ServerAuthModule:

public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject,
        Subject serviceSubject) throws AuthException {

    boolean authenticated = false;
    final HttpServletRequest request = 
                      (HttpServletRequest) messageInfo.getRequestMessage();
    final String token = request.getParameter("token");
    TokenPrincipal principal = (TokenPrincipal) request.getUserPrincipal();

    Callback[] callbacks = new Callback[] {
            new CallerPrincipalCallback(clientSubject, (TokenPrincipal) null) };

    if (principal != null) {
        callbacks = new Callback[] { 
                new CallerPrincipalCallback(clientSubject, principal) };
        authenticated = true;
    } else {
        if (token != null && token.length() == Constants.tokenLength) {
            try {
                principal = fetchUser(token);
            } catch (final Exception e) {
                throw (AuthException) new AuthException().initCause(e);
            }
            callbacks = new Callback[]
                        { 
                             new CallerPrincipalCallback(clientSubject, principal),
                             new GroupPrincipalCallback(clientSubject,
                                                        new String[] { "aRole" })
                        };
            messageInfo.getMap().put("javax.servlet.http.registerSession", "TRUE");
            authenticated = true;
        }
    }

    if (authenticated) {
        try {
            handler.handle(callbacks);
        } catch (final Exception e) {
            throw (AuthException) new AuthException().initCause(e);
        }
        return SUCCESS;
    }

    return AuthStatus.SEND_FAILURE;
}

This works as expected, for the first call of an ejb with @RolesAllowed("aRole") but for the next call this does not work at all. Wildfly denies it with this error message:

ERROR [org.jboss.as.ejb3.invocation] (default task-4) WFLYEJB0034: EJB Invocation 
    failed on component TestEJB for method public java.lang.String 
    com.jaspic.security.TestEJB.getPrincipalName():
    javax.ejb.EJBAccessException: WFLYSEC0027: Invalid User

If I guess right, the error occures in: org.jboss.as.security.service.SimpleSecurityManager line 367 of wilfly's source code, due to line 405, in which credential is checked, but seems to be null.

This seems equal in Wildfly 8/9/10CR (other versions not tested).

Again I'm not sure, if I'm doing it wrong, or if this is the same bug as https://issues.jboss.org/browse/WFLY-4626 ? And is it a bug at all, or is it expected behavior?

Trevelyan answered 18/11, 2015 at 22:6 Comment(0)
J
7

This sounds like a bug to me as well, as the caller identity (caller / group Principals) appears to be retained in subsequent calls to the web, yet not to the EJB container. My own JASPIC classes (which function properly on GlassFish 4.1) fail for the same reason on WildFly 9.0.2.Final and 10.0.0.CR4 when used along with a plain Servlet and an SLSB, even with the latter marked @PermitAll.

As I'm myself unfamiliar with WildFly security internals I can not assist you in that respect. Unless you can get this patched, the sole SAM-level workaround I can think of for the time being would be to not use the javax.servlet.http.registerSession callback property that seemingly triggers the problem, but instead have the CallbackHandler register both the caller Principal and its groups on every validateRequest(...) invocation. If applicable to your use case, you may wish to attach that information to the HttpSession so as to speed up the process a bit; otherwise repeat from scratch. So, for example:

public class Sam implements ServerAuthModule {

    // ...

    @Override
    public AuthStatus validateRequest(MessageInfo mi, Subject client, Subject service) throws AuthException {
        boolean authenticated = false;
        boolean attachAuthnInfoToSession = false;
        final String callerSessionKey = "authn.caller";
        final String groupsSessionKey = "authn.groups";
        final HttpServletRequest req = (HttpServletRequest) mi.getRequestMessage();
        TokenPrincipal tp = null;
        String[] groups = null;
        String token = null;
        HttpSession hs = req.getSession(false);
        if (hs != null) {
            tp = (TokenPrincipal) hs.getAttribute(callerSessionKey);
            groups = (String[]) hs.getAttribute(groupsSessionKey);
        }
        Callback[] callbacks = null;
        if (tp != null) {
            callbacks = new Callback[] { new CallerPrincipalCallback(client, tp), new GroupPrincipalCallback(client, groups) };
            authenticated = true;
        }
        else if (isValid(token = req.getParameter("token"))) {
            tp = newTokenPrincipal(token);
            groups = fetchGroups(tp);
            callbacks = new Callback[] { new CallerPrincipalCallback(client, tp), new GroupPrincipalCallback(client, groups) };
            authenticated = true;
            attachAuthnInfoToSession = true;
        }
        if (authenticated) {
            try {
                handler.handle(callbacks);
                if (attachAuthnInfoToSession && ((hs = req.getSession(false)) != null)) {
                    hs.setAttribute(callerSessionKey, tp);
                    hs.setAttribute(groupsSessionKey, groups);
                }
            }
            catch (IOException | UnsupportedCallbackException e) {
                throw (AuthException) new AuthException().initCause(e);
            }
            return AuthStatus.SUCCESS;
        }
        return AuthStatus.SEND_FAILURE;
    }

    // ...

    @Override
    public void cleanSubject(MessageInfo mi, Subject subject) throws AuthException {
        // ...
        // just to be safe
        HttpSession hs = ((HttpServletRequest) mi.getRequestMessage()).getSession(false);
        if (hs != null) {
            hs.invalidate();
        }
    }

    private boolean isValid(String token) {
        // whatever
        return ((token != null) && (token.length() == 10));
    }

    private TokenPrincipal newTokenPrincipal(String token) {
        // whatever
        return new TokenPrincipal(token);
    }

    private String[] fetchGroups(TokenPrincipal tp) {
        // whatever
        return new String[] { "aRole" };
    }

}

I tested the above on the aforementioned WildFly versions and in the aforementioned fashion (i.e. with a single Servlet referencing a single SLSB marked @DeclareRoles / method-level @RolesAllowed) and it seems to work as expected. Obviously I cannot guarantee that this approach will not fail in other unexpected ways.


See also:
Jumper answered 20/11, 2015 at 13:6 Comment(8)
I tried this workaround but tp = (TokenPrincipal) hs.getAttribute(callerSessionKey);never returns something different than null seems like Wildfly 9.0.02 never takes the same session in my example.Trevelyan
Silly question but just to be sure --you are actually creating a session somewhere, right? As I wasn't sure whether using the HTTP session would be an option to you (you might have wanted to remain completely stateless, HTTP session-wise), the above sample doesn't, and only attaches the caller and its roles if there already is an active session. If you do create a session elsewhere and the SAM is still unable to retrieve the information, let me know and I'll retest this tomorrow. Otherwise on initial authentication within the SAM, req.getSession(false) will need to become req.getSession().Jumper
It did not work for me, because in web.xml ssl encryption was enforced, but not present. Thanks! Your answer is the workaround I will use.Trevelyan
@Trevelyan and Uux: This sounds like a nasty bug indeed. Did any of you try to report it with JBoss?Apocopate
@ArjanTijms yes, I did pass this to Redhat support and WFLY-4625 has been solved in WildFly. Got a patch for EAP 6.4. I hope it will be included in EAP 7.Bent
@Bent thanks for that! One question though, the bug that this SO question refers to seems to be that when using the "javax.servlet.http.registerSession" key EJB calls don't work. That's a rather different issue from WFLY-4625. "registerSession" issues can't be patched for EAP 6, since it concerns JASPIC 1.1, and EAP 6 only supports JASPIC 1.0 (Java EE 6).Apocopate
@ArjanTijms Yeah, you're probably right. I was reacting on the WFLY-4625 reference above. Still, I opened a couple of JASPIC bugs and they did patch the most important. Some of the latter ones I did file at RedHat where solved when the first patches came out, so there is a chance that it works better now.Bent
@Trevelyan and Uux; I finally found some time to get to the bottom of this issue and reported it with JBoss. See issues.jboss.org/browse/WFLY-6579 I've created a provisional patch using a modified JBoss internal class: github.com/omnifaces/omnisecurity-jaspic-undertow/blob/master/…Apocopate

© 2022 - 2024 — McMap. All rights reserved.