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);
// }
// }
}
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.