Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

 

Problem

...

Solution

...

Note
titleAssumptions
  • Your SP your application's resources.
  • Your AS is an implementation of the Full Java EE 6+ Profile.
  • Your application comprises both "protected" and "public" resources.

Implement the SAM

Info
titleSAMs

SAMs closely resemble the functionality (e.g. manipulation or blocking of HttpServletRequests and HttpServletResponses) provided by common Servlet Filters; they may in fact be implemented as such. Unlike a Filter, however, a SAM is always invoked before the first FilterChain's Filter and/or after the last one, it is used by potentially multiple applications and is capable of communicating authentication decisions to the AS.

The following example is a simplistic "bridge" SAM, which, for every --which is obviously not ideal-- incoming request:

  1. Probes the received HttpServletRequest for presence of the "eduPersonPrincipalName" and "eduPersonAffiliation" attributes.
  2. If both are present, registers a "caller" Principal and one or multiple "group" Principals --enclosing the values of "eduPersonPrincipalName" and "eduPersonAffiliation" as their respective names-- at the AS to represent the authenticated user.
  3. Otherwise:
    1. If the targeted request is "public", it treats the user as a guest.
    2. 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
languagejava
themeEclipse
titleSpAsBridgeSam.java
collapsetrue
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.Subjects; 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" Principals. 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.

 

...

hiddentrue

...