Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JASPIC Wildfly 9 validateRequest with session

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?

like image 599
knoe Avatar asked Nov 18 '15 22:11

knoe


1 Answers

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:
  • [WFLY-4625]
  • [SECURITY-744]
  • [SECURITY-745]
like image 81
Uux Avatar answered Sep 17 '22 01:09

Uux