4

I have a custom, JSR-196 module, that basically delegates to a service that delegates roles to a OAuth "grants" call.

It does work from a servlet (request.getUserPrincipal() works fine).

It does not propagate to EJB calls, where SessionContext.getCallerPrincipal() returns a SimplePrincipal with "anonymous" instead of expected username / roles.

MycompanyPrincipal is a simple class, with a simple getName() and some custom properties.

It seems that SubjectInfo.getAuthenticatedSubject() has no principal.

I managed to make an ugly workaround for that, see "// WORKAROUND" below.

Still, I'd want to do it the right way (even standard/portable, if possible).

Here is where I define my security domain in standalone.xml:

                <security-domain name="mycompany" cache-type="default">
                    <authentication-jaspi>
                        <login-module-stack name="lm-stack">
                            <login-module code="UsersRoles" flag="required">
                                <module-option name="usersProperties" value="../standalone/configuration/jaspi-users.properties"/>
                                <module-option name="rolesProperties" value="../standalone/configuration/jaspi-roles.properties"/>
                            </login-module>
                        </login-module-stack>
                        <auth-module code="be.mycompany.api.authentication.jaspi.MycompanyAuthModule" flag="required" login-module-stack-ref="lm-stack"/>
                    </authentication-jaspi>
                </security-domain>

And here is my jboss-web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
    <context-root>api/rules/dev</context-root>
    <security-domain>mycompany</security-domain>
    <valve>
        <class-name>org.jboss.as.web.security.jaspi.WebJASPIAuthenticator</class-name>
    </valve>
</jboss-web>

The module itself is part of my application (a jar in my war). The EJBs are defined in other JARs, that also end-up in WEB-INF/lib.

serviceSubject.getPrincipals().add(degroofPrincipal)

Here is my module (changed ejb calls to static method calls):

package be.mycompany.api.authentication.jaspi;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.message.AuthException;
import javax.security.auth.message.AuthStatus;
import javax.security.auth.message.MessageInfo;
import javax.security.auth.message.MessagePolicy;
import javax.security.auth.message.callback.CallerPrincipalCallback;
import javax.security.auth.message.callback.GroupPrincipalCallback;
import javax.security.auth.message.config.ServerAuthContext;
import javax.security.auth.message.module.ServerAuthModule;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.HttpHeaders;
import org.jboss.security.SecurityContext;
import org.jboss.security.SecurityContextAssociation;
import org.jboss.security.SubjectInfo;

/**
 *
 * @author devyam
 */
public class MycompanyAuthModule implements ServerAuthModule, ServerAuthContext {

    private static final String BEARER_PREFIX = "bearer ";

    private CallbackHandler handler;
    private final Class<?>[] supportedMessageTypes = new Class[]{HttpServletRequest.class, HttpServletResponse.class};

    protected String delegatingLoginContextName = null;
//    private MycompanyAuthenticationService mycompanyAuthenticationService;

    /**
     * <p>
     * Creates an instance of {@code HTTPBasicServerAuthModule}.
     * </p>
     */
    public MycompanyAuthModule() {
//        lookupMycompanyAuthenticationService();
    }

    /**
     * <p>
     * Creates an instance of {@code HTTPBasicServerAuthModule} with the
     * specified delegating login context name.
     * </p>
     *
     * @param delegatingLoginContextName the name of the login context
     * configuration that contains the JAAS modules that are to be called by
     * this module.
     */
    public MycompanyAuthModule(String delegatingLoginContextName) {
        this();
        this.delegatingLoginContextName = delegatingLoginContextName;
    }

    @Override
    public void initialize(MessagePolicy requestPolicy,
            MessagePolicy responsePolicy, CallbackHandler handler,
            @SuppressWarnings("rawtypes") Map options) throws AuthException {

        this.handler = handler;
    }

    /**
     * WebLogic 12c calls this before Servlet is called, Geronimo v3 after,
     * JBoss EAP 6 and GlassFish 3.1.2.2 don't call this at all. WebLogic
     * (seemingly) only continues if SEND_SUCCESS is returned, Geronimo
     * completely ignores return value.
     */
    @Override
    public AuthStatus secureResponse(MessageInfo messageInfo, Subject serviceSubject) throws AuthException {
        return AuthStatus.SEND_SUCCESS;
    }

    @Override
    public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject) throws AuthException {
        HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage();
        HttpServletResponse response = (HttpServletResponse) messageInfo.getResponseMessage();

        String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (authHeader != null && authHeader.startsWith(BEARER_PREFIX)) {
            String token = authHeader.substring(BEARER_PREFIX.length());

            MycompanyPrincipal mycompanyPrincipal = MycompanyAuthenticationService.createPrincipal(token);

            List<String> groups = MycompanyAuthenticationService.getGroups(mycompanyPrincipal);
            String[] groupArray = groups.toArray(new String[0]);

            CallerPrincipalCallback callerPrincipalCallback = new CallerPrincipalCallback(clientSubject, mycompanyPrincipal);
            GroupPrincipalCallback groupPrincipalCallback = new GroupPrincipalCallback(clientSubject, groupArray);

            try {
                handler.handle(new Callback[]{callerPrincipalCallback, groupPrincipalCallback});
            } catch (IOException | UnsupportedCallbackException exception) {
                throw new RuntimeException(exception);
            }

            //////// WORKAROUND: doesn't work without this in EJBs!
            SecurityContext oldContext = SecurityContextAssociation.getSecurityContext();
            SubjectInfo subjectInfo = oldContext.getSubjectInfo();
            subjectInfo.setAuthenticatedSubject(serviceSubject);
            SecurityContextAssociation.setPrincipal(mycompanyPrincipal);

            serviceSubject.getPrincipals().add(mycompanyPrincipal);
            ////////////// end of workaround

            return AuthStatus.SUCCESS;
        }

        response.setStatus(401);

        return AuthStatus.FAILURE;
    }

    /**
     * A compliant implementation should return HttpServletRequest and
     * HttpServletResponse, so the delegation class {@link ServerAuthContext}
     * can choose the right SAM to delegate to. In this example there is only
     * one SAM and thus the return value actually doesn't matter here.
     */
    @Override
    public Class<?>[] getSupportedMessageTypes() {
        return supportedMessageTypes;
    }

    @Override
    public void cleanSubject(MessageInfo messageInfo, Subject subject)
            throws AuthException {
    }

//    private void lookupMycompanyAuthenticationService() throws RuntimeException {
//        try {
//            BeanManager beanManager = InitialContext.doLookup("java:comp/BeanManager");
//            Bean<?> mycompanyAuthenticationServiceBean = beanManager.getBeans(MycompanyAuthenticationService.class).iterator().next();
//            CreationalContext creationalContext = beanManager.createCreationalContext(mycompanyAuthenticationServiceBean);
//            mycompanyAuthenticationService = (MycompanyAuthenticationService) beanManager.getReference(mycompanyAuthenticationServiceBean, MycompanyAuthenticationService.class, creationalContext);
//        } catch (NamingException exception) {
//            throw new RuntimeException(exception);
//        }
//    }
}
Arjan Tijms
  • 37,782
  • 12
  • 108
  • 140
ymajoros
  • 2,454
  • 3
  • 34
  • 60

3 Answers3

4

Propagating the authenticated identity from Servlet to EJB is unfortunately a never ending story with JBoss, despite the best efforts of the JBoss engineers.

There were some 6 individual bugs that had to be fixed so you could even get to the point where you are now in JBoss AS 7.4 (JBoss EAP 6.3 I assume), and there are a couple of bugs after this.

This particular bug is https://issues.jboss.org/browse/SECURITY-745 and was filed almost 2 years ago, but still open for the AS 7/EAP 6 branch. This one came right after https://issues.jboss.org/browse/SECURITY-744, which is listed as open but I think it's actually fixed.

The WF 8/EAP 7 branch doesn't have this bug, but both branches do suffer from https://issues.jboss.org/browse/SECURITY-746 and https://issues.jboss.org/browse/SECURITY-876

So it's a known bug in JBoss. If you want to get it solved my advice would be to contact JBoss about it.

An alternative workaround which I used for the AS 7 branch is providing my own modified org.jboss.as.web.security.jaspi.WebJASPIAuthenticator implementation, but then you'll run right away into SECURITY-746, so you need the custom be.mycompany.api.authentication.jaspi.MycompanyAuthModule module that you used anyway.

Arjan Tijms
  • 37,782
  • 12
  • 108
  • 140
  • Thanks a lot. I guess this answers the question from a community point of view. I'm in discussing this issue with JBoss support, and will give any feedback here. – ymajoros May 07 '15 at 08:15
  • Arjan, can I get a copy of your own WebJASPIAuthenticator implementation? – ymajoros May 07 '15 at 08:20
  • @ymajoros great, will appreciate any updates here. I think one of the last versions of WebJASPIAuthenticator that I used was this one: https://github.com/javaeekickoff/jboss-as-jaspic-patch/tree/master/src/main/java/patch/jboss I'm not 100% if I committed the last changes back then, but hope its helpful anyway. – Arjan Tijms May 07 '15 at 22:21
3

(LATEST NEWS: see my other answer for a "definitive" solution).

UPDATE:

Got a test patch from RedHat and it works :-)

I'll update this answer when more information is available.

UPDATE 2: RedHat says that the patch should be in 7.3.3... But it's not complete in my opinion (found another use case where this does not work). (support case 01440434)

UPDATE 3: besides the working patch, I alternatively had a workaround in my authentication module that made it work for JAX-RS and EJB:

        // TODO: remove this when fixed in JBoss - WORKAROUND to get authentication to propagate to EJBs
        SecurityContext oldContext = SecurityContextAssociation.getSecurityContext();
        SubjectInfo subjectInfo = oldContext.getSubjectInfo();
        subjectInfo.setAuthenticatedSubject(serviceSubject);
        SecurityContextAssociation.setPrincipal(degroofPrincipal);
        serviceSubject.getPrincipals().add(degroofPrincipal);

... but for whatever reason, it doesn't work in a JSF context.

See Arjan Tijms's provided link, https://github.com/javaeekickoff/jboss-as-jaspic-patch/tree/master/src/main/java/patch/jboss . This does work with a few changes for 7.4 (removing logging, finding the right jars, some custom changes to get it to compile).

I can share this if necessary, but I just opened support case 01494061 at RedHat for that. Hope they will eventually patch it...


Red Hat's answer for now:

Hi ---,

Yes, I think information Arjan Tijms provided is correct. For the principal to be propagated to the ejb layer, it must be put into the subject. This works with the approach shown in [1]. However, it works because the HTTPBasicServerAuthModule defers to JAAS/JBossWebRealm to handle the authentication. This setups up the subject so that it will propagate the principal to the ejb layer.

I am researching the suggested enhancements and discussing this with our engineers.

I will provide an update early next week.

Thanks,

[1] https://developer.jboss.org/wiki/JBossAS7EnablingJASPIAuthenticationForWebApplications

ymajoros
  • 2,454
  • 3
  • 34
  • 60
3

At last, it seems Red Hat fixed the bug. I got an official patch that works well in JBoss EAP 6.4.3.

For those interested, my support case number was 01440434, and the patch file name was

Googling for it leads me to https://bugzilla.redhat.com/show_bug.cgi?id=1243553 and https://github.com/wildfly/wildfly/pull/7469/files

They also talk about https://github.com/jbossas/jboss-eap/pull/2480 but I get a 404 on this.

Didn't try it in Wildfly, but I like the simplicity of this fix.

There are still cases that seem to be broken though (e.g. @RolesAllowed doesn't seem to work), but I'll open new support cases for that, as I didn't ask specifically for this in my first support case.

ymajoros
  • 2,454
  • 3
  • 34
  • 60