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?
This sounds like a bug to me as well, as the caller identity (caller / group Principal
s) 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.