net.java.jaspicoil.SimpleBasicServerAuthModule.java Source code

Java tutorial

Introduction

Here is the source code for net.java.jaspicoil.SimpleBasicServerAuthModule.java

Source

package net.java.jaspicoil;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
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.PasswordValidationCallback;
import javax.security.auth.message.module.ServerAuthModule;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;

/**
 * This JASPIC Module implements a HTTP Basic Authentication.
 * 
 * @author bugeaud at gmail dot com
 * @see http://tools.ietf.org/html/draft-ietf-httpauth-basicauth-enc-01
 * @license CDDL1 http://www.opensource.org/licenses/cddl1.txt
 * @license LGPL http://www.gnu.org/copyleft/lesser.html
 * 
 */
public class SimpleBasicServerAuthModule implements ServerAuthModule {

    private String jaasContextName;
    private static Logger LOG = Logger.getLogger("net.java.jaspicoil");
    private CallbackHandler handler;
    private boolean mandatory;

    public SimpleBasicServerAuthModule() {
    }

    public SimpleBasicServerAuthModule(String contextName) {
        this.jaasContextName = contextName;
    }

    /**
     * Initialize this module with request and response message policies to
     * enforce, a CallbackHandler, and any module-specific configuration
     * properties. The request policy and the response policy must not both be
     * null.
     * 
     * @param requestPolicy
     *            The request policy this module must enforce, or null.
     * @param responsePolicy
     *            The response policy this module must enforce, or null.
     * @param handler
     *            CallbackHandler used to request information.
     * @param options
     *            A Map of module-specific configuration properties.
     * @throws AuthException
     *             If module initialization fails, including for the case where
     *             the options argument contains elements that are not supported
     *             by the module.
     */
    public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, CallbackHandler handler,
            Map options) throws AuthException {
        this.handler = handler;
        // If none policy was provided, we assume this provider is mandatory.
        // This is unfortunately required as some container workaround
        this.mandatory = requestPolicy != null ? requestPolicy.isMandatory() : true;

        this.jaasContextName = (String) options.get(JAAS_LOGIN_CONTEXT);

    }

    private static Class<?>[] supportedMessageTypes = new Class<?>[] { javax.servlet.http.HttpServletRequest.class,
            javax.servlet.http.HttpServletResponse.class };

    /**
     * Get the one or more Class objects representing the message types
     * supported by the module.
     * 
     * @return An array of Class objects, with at least one element defining a
     *         message type supported by the module.
     */
    @SuppressWarnings("rawtypes")
    public Class[] getSupportedMessageTypes() {
        return supportedMessageTypes;
    }

    protected static final String JAAS_LOGIN_CONTEXT = "javax.security.auth.login.LoginContext";
    protected static final String AUTHORIZATION_HEADER = "Authorization";
    protected static final String AUTHENTICATE_HEADER = "WWW-Authenticate";
    protected static final String BASIC_PREFIX = "basic ";
    protected static final int BASIC_PREFIX_LENGTH = BASIC_PREFIX.length();
    protected static final String CHARSET_SUFFIX = ", charset=\"UTF-8\"";
    protected static final Charset UTF_8 = Charset.forName("UTF-8");

    /**
     * Authenticate a received service request.
     * <p/>
     * This method is called to transform the mechanism-specific request message
     * acquired by calling getRequestMessage (on messageInfo) into the validated
     * application message to be returned to the message processing runtime. If
     * the received message is a (mechanism-specific) meta-message, the method
     * implementation must attempt to transform the meta-message into a
     * corresponding mechanism-specific response message, or to the validated
     * application request message. The runtime will bind a validated
     * application message into the the corresponding service invocation.
     * <p>
     * This method conveys the outcome of its message processing either by
     * returning an AuthStatus value or by throwing an AuthException.
     * <p/>
     * From a performance point of view this method will be called twice for
     * each resource with a security constraint on it. Resources with no
     * security constraint do not result in a call to this method.
     * 
     * @param messageInfo
     *            A contextual object that encapsulates the client request and
     *            server response objects, and that may be used to save state
     *            across a sequence of calls made to the methods of this
     *            interface for the purpose of completing a secure message
     *            exchange.
     * @param clientSubject
     *            A Subject that represents the source of the service request.
     *            It is used by the method implementation to store Principals
     *            and credentials validated in the request.
     * @param serviceSubject
     *            A Subject that represents the recipient of the service
     *            request, or null. It may be used by the method implementation
     *            as the source of Principals or credentials to be used to
     *            validate the request. If the Subject is not null, the method
     *            implementation may add additional Principals or credentials
     *            (pertaining to the recipient of the service request) to the
     *            Subject.
     * @return An AuthStatus object representing the completion status of the
     *         processing performed by the method. The AuthStatus values that
     *         may be returned by this method are defined as follows:
     *         <p/>
     *         <ul>
     *         <li>AuthStatus.SUCCESS when the application request message was
     *         successfully validated. The validated request message is
     *         available by calling getRequestMessage on messageInfo.
     *         <p/>
     *         <li>AuthStatus.SEND_SUCCESS to indicate that
     *         validation/processing of the request message successfully
     *         produced the secured application response message (in
     *         messageInfo). The secured response message is available by
     *         calling getResponseMessage on messageInfo.
     *         <p/>
     *         <li>AuthStatus.SEND_CONTINUE to indicate that message validation
     *         is incomplete, and that a preliminary response was returned as
     *         the response message in messageInfo.
     *         <p/>
     *         When this status value is returned to challenge an application
     *         request message, the challenged request must be saved by the
     *         authentication module such that it can be recovered when the
     *         module's validateRequest message is called to process the request
     *         returned for the challenge.
     *         <p/>
     *         <li>AuthStatus.SEND_FAILURE to indicate that message validation
     *         failed and that an appropriate failure response message is
     *         available by calling getResponseMessage on messageInfo.
     *         </ul>
     * @throws AuthException When the message processing failed without
     *         establishing a failure response message (in messageInfo).
     */
    public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject)
            throws AuthException {
        // Get the servlet context
        final HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage();
        final HttpServletResponse response = (HttpServletResponse) messageInfo.getResponseMessage();
        final String auth = request.getHeader(AUTHORIZATION_HEADER);
        // Test prefix for HTTP BASIC Auth
        if (auth != null && StringUtils.startsWithIgnoreCase(auth, "basic ")) {
            // We might have a valid header, so try to decode it
            final String data = new String(Base64.decodeBase64(auth.substring(BASIC_PREFIX_LENGTH)), UTF_8);
            final int splitIndex = data.indexOf(':');
            if (splitIndex < 0) {
                return sendErrorAndAuthenticateRequest(request, response, "Wrong WWW-Authenticate header format");
            }
            final String username = data.substring(splitIndex);
            final char[] password = data.substring(splitIndex + 1, data.length()).toCharArray();

            // Prepare the JAAS callback to feed any LoginModule with user and password
            final NameCallback nameCallback = new NameCallback("username");
            nameCallback.setName(username);

            final PasswordCallback passwordCallback = new PasswordCallback(getRealm(request), false);
            passwordCallback.setPassword(password);

            final CallbackHandler delegatedHandler = new CallbackHandler() {
                public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
                    for (int i = 0; i < callbacks.length; i++) {
                        final Callback c = callbacks[i];
                        if (c instanceof NameCallback) {
                            ((NameCallback) c).setName(username);
                        } else if (c instanceof PasswordCallback) {
                            ((PasswordCallback) c).setPassword(password);
                        } else {
                            throw new UnsupportedOperationException(
                                    String.format("Callback type %s (%s) is not supported yet.", c.getClass(), c));
                        }
                    }
                }
            };

            if (this.jaasContextName == null) {
                throw new UnsupportedOperationException(
                        "No delegate JAAS context found. As per JASPIC JAAS Bridge profile, this parameter is requiered.");
            }

            try {
                // Create a new JAAS context with the delegated data & try to login
                final LoginContext context = new LoginContext(this.jaasContextName, delegatedHandler);
                context.login();

                // Get the authenticated subject from the JAAS context
                Subject authenticatedSubject = context.getSubject();

                final PasswordValidationCallback passwordValidationCallback = new PasswordValidationCallback(
                        authenticatedSubject, username, password);

                // notify JASPIC containerr for the name, password and subject
                this.handler.handle(new Callback[] { passwordValidationCallback });

            } catch (final LoginException ex) {
                // If there was any issue during the JAAS login, fail the process
                final AuthException aex = new AuthException(
                        String.format("Fail to login user %s with the delegated JAAS context %s", username,
                                this.jaasContextName));
                aex.initCause(ex);
            } catch (final IOException e) {
                LOG.log(Level.WARNING, "Unable to call the handlers for name=" + nameCallback, e);
            } catch (final UnsupportedCallbackException e) {
                LOG.log(Level.WARNING, "Unable to call the handlers for name=" + nameCallback, e);
            }

        } else if (this.mandatory) {
            return sendErrorAndAuthenticateRequest(request, response,
                    "AuthModule was mandatory but no valid credential was provided");
        } else {
            LOG.info("No authentication was provided bu Basic AuthModule is not mandatory so return SUCCESS.");
        }

        return AuthStatus.SUCCESS;
    }

    private String getRealm(HttpServletRequest request) {
        // TODO should implement this dynamically
        return "FakeRealm";
    }

    private AuthStatus sendErrorAndAuthenticateRequest(HttpServletRequest request, HttpServletResponse response,
            String message) {
        response.setHeader(AUTHENTICATE_HEADER, createAuthenticateValue(getRealm(request)));
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        LOG.fine(message);
        return AuthStatus.SEND_CONTINUE;
    }

    private AuthStatus sendErrorAndFail(HttpServletRequest request, HttpServletResponse response, String message) {
        response.setHeader(AUTHENTICATE_HEADER, createAuthenticateValue(getRealm(request)));
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        LOG.fine(message);
        return AuthStatus.FAILURE;
    }

    private String createAuthenticateValue(String realm) {
        final StringBuilder builder = new StringBuilder();
        builder.append("Basic realm=\"").append(realm).append("\", charset=\"UTF-8\"");
        return builder.toString();
    }

    /**
     * Secure a service response before sending it to the client.
     * <p/>
     * This method is called to transform the response message acquired by
     * calling getResponseMessage (on messageInfo) into the mechanism-specific
     * form to be sent by the runtime.
     * <p>
     * This method conveys the outcome of its message processing either by
     * returning an AuthStatus value or by throwing an AuthException.
     * 
     * @param messageInfo
     *            A contextual object that encapsulates the client request and
     *            server response objects, and that may be used to save state
     *            across a sequence of calls made to the methods of this
     *            interface for the purpose of completing a secure message
     *            exchange.
     * @param serviceSubject
     *            A Subject that represents the source of the service response,
     *            or null. It may be used by the method implementation to
     *            retrieve Principals and credentials necessary to secure the
     *            response. If the Subject is not null, the method
     *            implementation may add additional Principals or credentials
     *            (pertaining to the source of the service response) to the
     *            Subject.
     * @return An AuthStatus object representing the completion status of the
     *         processing performed by the method. The AuthStatus values that
     *         may be returned by this method are defined as follows:
     *         <p/>
     *         <ul>
     *         <li>AuthStatus.SEND_SUCCESS when the application response message
     *         was successfully secured. The secured response message may be
     *         obtained by calling getResponseMessage on messageInfo.
     *         <p/>
     *         <li>AuthStatus.SEND_CONTINUE to indicate that the application
     *         response message (within messageInfo) was replaced with a
     *         security message that should elicit a security-specific response
     *         (in the form of a request) from the peer.
     *         <p/>
     *         This status value serves to inform the calling runtime that (to
     *         successfully complete the message exchange) it will need to be
     *         capable of continuing the message dialog by processing at least
     *         one additional request/response exchange (after having sent the
     *         response message returned in messageInfo).
     *         <p/>
     *         When this status value is returned, the application response must
     *         be saved by the authentication module such that it can be
     *         recovered when the module's validateRequest message is called to
     *         process the elicited response.
     *         <p/>
     *         <li>AuthStatus.SEND_FAILURE to indicate that a failure occurred
     *         while securing the response message and that an appropriate
     *         failure response message is available by calling
     *         getResponseMeessage on messageInfo.
     *         </ul>
     * @throws AuthException When the message processing failed without
     *         establishing a failure response message (in messageInfo).
     */
    public AuthStatus secureResponse(MessageInfo messageInfo, Subject serviceSubject) throws AuthException {
        // nothing to do but go thru
        return AuthStatus.SUCCESS;
    }

    /**
     * Remove method specific principals and credentials from the subject.
     * 
     * @param messageInfo
     *            a contextual object that encapsulates the client request and
     *            server response objects, and that may be used to save state
     *            across a sequence of calls made to the methods of this
     *            interface for the purpose of completing a secure message
     *            exchange.
     * @param subject
     *            the Subject instance from which the Principals and credentials
     *            are to be removed.
     * @throws AuthException If an error occur during the Subject processing.
     */
    public void cleanSubject(MessageInfo messageInfo, Subject clientSubject) throws AuthException {
        // Nothing to do yet
    }
}