Problem
...
Solution
...
Note | ||
---|---|---|
| ||
|
Implement the SAM
Info | ||
---|---|---|
| ||
SAMs closely resemble the functionality (e.g. manipulation or blocking of |
The following example is a simplistic "bridge" SAM, which, for every --which is obviously not ideal-- incoming request:
- Probes the received
HttpServletRequest
for presence of the "eduPersonPrincipalName" and "eduPersonAffiliation" attributes. - If both are present, registers a "caller"
Principal
and one or multiple "group"Principal
s --enclosing the values of "eduPersonPrincipalName" and "eduPersonAffiliation" as their respective names-- at the AS to represent the authenticated user. - Otherwise:
- If the targeted request is "public", it treats the user as a guest.
- Otherwise, it redirects the request to the SP's
SessionInitiator
.
Step 3.b. also occurs whenever the user explicitly asks to be authenticated, e.g. by clicking a "Login" button in the UI. Similarly, when the user wishes to be logged out, the SAM redirects her request to the SP's LogoutInitiator
.
Code Block | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
package org.my;
import java.io.IOException;
import java.security.Principal;
import java.util.Map;
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.module.ServerAuthModule;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class SpAsBridgeSam implements ServerAuthModule {
static class MyOrgPrincipal implements Principal {
public MyOrgPrincipal(String name) {
this.name = name;
}
private final String name;
@Override
public String getName() {
return name;
}
}
private static final Class<?>[] SUPPORTED_MESSAGE_TYPES = new Class<?>[] { HttpServletRequest.class, HttpServletResponse.class };
private static final String LOGIN_URL = "http://my.org/Shibboleth.sso/Login";
private static final String LOGOUT_URL = "http://my.org/Shibboleth.sso/Logout";
private boolean isProtectedResource;
private CallbackHandler ch;
// Post-construct initialization goes here
@Override
public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, CallbackHandler ch, Map options) throws AuthException {
this.ch = ch;
}
// Authentication logic and optional request pre-processing goes here
@Override
public AuthStatus validateRequest(MessageInfo mi, Subject client, Subject service) throws AuthException {
HttpServletRequest hreq = (HttpServletRequest) mi.getRequestMessage();
HttpServletResponse hres = (HttpServletResponse) mi.getResponseMessage();
String username = getUsername(hreq);
String[] groups = getUsergroups(hreq);
if ((username != null) && (!username.trim().isEmpty()) && (groups != null)) {
try {
ch.handle(new Callback[] {
new CallerPrincipalCallback(client, new MyOrgPrincipal(username)),
new GroupPrincipalCallback(client, groups) });
}
catch (UnsupportedCallbackException | IOException e) {
e.printStackTrace();
throw new AuthException("Could not authenticate user.");
}
return AuthStatus.SUCCESS;
}
else if (!isProtectedResource) {
return AuthStatus.SUCCESS;
}
else {
try {
hres.sendRedirect(LOGIN_URL);
}
catch (IOException ioe) {
ioe.printStackTrace();
throw new AuthException("Could not authenticate user.");
}
return AuthStatus.SEND_CONTINUE;
}
}
// Validation of response and optional post-processing goes here.
// Not typically used, as --unless it had initially been wrapped-- the response is committed at this point.
@Override
public AuthStatus secureResponse(MessageInfo messageInfo, Subject service) throws AuthException {
return AuthStatus.SEND_SUCCESS;
}
@Override
public void cleanSubject(MessageInfo mi, Subject client) throws AuthException {
HttpServletRequest hreq = (HttpServletRequest) mi.getRequestMessage();
HttpServletResponse hres = (HttpServletResponse) mi.getResponseMessage();
HttpSession hs = hreq.getSession(false);
if (hs != null) {
try {
hs.invalidate();
}
catch (IllegalStateException ise) {
ise.printStackTrace();
throw new AuthException("Could not invalidate user session.");
}
}
try {
hreq.logout();
hres.sendRedirect(LOGOUT_URL);
}
catch (ServletException | IOException e) {
e.printStackTrace();
throw new AuthException("Could not complete user logout.");
}
}
@Override
public Class<?>[] getSupportedMessageTypes() {
return SUPPORTED_MESSAGE_TYPES;
}
private String getUsername(HttpServletRequest hreq) {
return (String) hreq.getAttribute("eduPersonPrincipalName");
}
private String[] getUsergroups(HttpServletRequest hreq) {
String groupsAttribute = (String) hreq.getAttribute("eduPersonAffiliation");
String[] groups = null;
if (groupsAttribute != null) {
groups = groupsAttribute.split(";");
for (int i = 0; i < groups.length; i++) {
groups[i] = groups[i].trim();
}
}
return groups;
}
} |
Step-by-step guide
1 Java EE mostly disregards javax.security.auth.Subject
s; all what's left of them, that is, what most Java EE specifications tend to use, is this one "caller" Principal
, along with potential "group" Principal
s. A blog entry by Arjan Tijms titled "JAAS in Java EE is not the universal standard you may think it is" examines some common misconceptions surrounding JAAS in this scope.
2 JSR 196, Authentication Service Provider Interface for Containers (JASPIC, JASPI) is available in the Full Java EE 6+ Profile, and Servlet 3+ Containers included in compatible implementations, have been mandated to support JASPIC's Servlet Profile.
...
hidden | true |
---|
...