org.carewebframework.security.spring.DesktopSecurityContextRepository.java Source code

Java tutorial

Introduction

Here is the source code for org.carewebframework.security.spring.DesktopSecurityContextRepository.java

Source

/**
 * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
 * If a copy of the MPL was not distributed with this file, You can obtain one at
 * http://mozilla.org/MPL/2.0/.
 * 
 * This Source Code Form is also subject to the terms of the Health-Related Additional
 * Disclaimer of Warranty and Limitation of Liability available at
 * http://www.carewebframework.org/licensing/disclaimer.
 */
package org.carewebframework.security.spring;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.carewebframework.ui.LifecycleEventDispatcher;
import org.carewebframework.ui.LifecycleEventListener.ILifecycleCallback;

import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.web.context.HttpRequestResponseHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SaveContextOnUpdateOrErrorResponseWrapper;
import org.springframework.security.web.context.SecurityContextRepository;

import org.zkoss.zk.ui.Desktop;

/**
 * This is based on the HttpSessionSecurityContextRepository, but is desktop-, not session-, based.
 */
public class DesktopSecurityContextRepository implements SecurityContextRepository, ILifecycleCallback<Desktop> {

    private static final String CONTEXT_KEY = HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;

    protected final Log log = LogFactory.getLog(this.getClass());

    /** SecurityContext instance used to check for equality with default (unauthenticated) content */
    private final Object contextObject = SecurityContextHolder.createEmptyContext();

    private boolean allowSessionCreation = true;

    private boolean disableUrlRewriting;

    private final AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();

    /**
     * Given a servlet request, returns Spring security context object.
     * 
     * @param request HttpServletRequest
     * @return SecurityContext The Spring security context. First looks for the desktop-based
     *         security context. If not found, then looks for a session-based security context. This
     *         call will convert a session-based security context to desktop-based if a desktop
     *         identifier is found in the request object, a desktop-based security context does not
     *         exist, and a session-based security context does exist.
     * @throws IllegalStateException if session is invalidated
     */
    public static SecurityContext getSecurityContext(HttpServletRequest request) {
        final HttpSession session = request.getSession(false);
        boolean ignore = "rmDesktop".equals(request.getParameter("cmd_0"));
        return ignore || session == null ? SecurityContextHolder.createEmptyContext()
                : getSecurityContext(session, request.getParameter("dtid"));
    }

    /**
     * Given a desktop, returns Spring security context object.
     * 
     * @param desktop The desktop whose security context is sought.
     * @return SecurityContext The Spring security context.
     */
    public static SecurityContext getSecurityContext(Desktop desktop) {
        final String key = getDesktopContextKey(desktop.getId());
        HttpSession session = (HttpSession) desktop.getSession().getNativeSession();
        return (SecurityContext) session.getAttribute(key);
    }

    /**
     * Given a session and desktop id, returns Spring security context object.
     * 
     * @param session Session where security context is stored.
     * @param dtid Id of desktop whose security context is sought.
     * @return SecurityContext The Spring security context. First looks for the desktop-based
     *         security context. If not found, then looks for a session-based security context. This
     *         call will convert a session-based security context to desktop-based if a desktop
     *         identifier is found in the request object, a desktop-based security context does not
     *         exist, and a session-based security context does exist.
     * @throws IllegalStateException if session is invalidated
     */
    private static SecurityContext getSecurityContext(HttpSession session, String dtid) {
        final String key = getDesktopContextKey(dtid);

        if (key == null) {
            return getSecurityContext(session, false);
        }

        // Check for desktop-associated security context
        SecurityContext securityContext = (SecurityContext) session.getAttribute(key);

        // If no desktop security context, check session.
        if (securityContext == null) {
            securityContext = getSecurityContext(session, true);

            // If session security context found and this is a managed desktop, move into desktop.
            if (securityContext != null) {
                session.setAttribute(key, securityContext);
            }
        }
        return securityContext;
    }

    /**
     * Returns the security context from the session, if it exists.
     * 
     * @param session Session where security context is stored.
     * @param remove If true and a security context is found in the session, it is removed.
     * @return The security context, or null if none found.
     */
    private static SecurityContext getSecurityContext(HttpSession session, boolean remove) {
        SecurityContext securityContext = (SecurityContext) session.getAttribute(CONTEXT_KEY);

        if (securityContext != null && remove) {
            session.removeAttribute(CONTEXT_KEY);
        }

        return securityContext;
    }

    /**
     * Returns the desktop-specific (if desktop id available) or session-specific context key from
     * the request.
     * 
     * @param request The request object.
     * @return The context key (never null).
     */
    private static String getDesktopContextKey(HttpServletRequest request) {
        String key = getDesktopContextKey(request.getParameter("dtid"));
        return key == null ? CONTEXT_KEY : key;
    }

    /**
     * Returns the desktop-specific session key from the desktop id.
     * 
     * @param dtid The desktop id.
     * @return The desktop-specific session key.
     */
    private static String getDesktopContextKey(String dtid) {
        return StringUtils.isEmpty(dtid) ? null : CONTEXT_KEY + "-" + dtid;
    }

    public DesktopSecurityContextRepository() {
        super();
        LifecycleEventDispatcher.addDesktopCallback(this);
    }

    /**
     * Gets the security context for the current request (if available) and returns it.
     * <p>
     * If the session is null, the context object is null or the context object stored in the
     * session is not an instance of <tt>SecurityContext</tt>, a new context object will be
     * generated and returned.
     * <p>
     * If <tt>cloneFromHttpSession</tt> is set to true, it will attempt to clone the context object
     * first and return the cloned instance.
     */
    @Override
    public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
        HttpServletRequest request = requestResponseHolder.getRequest();
        HttpServletResponse response = requestResponseHolder.getResponse();
        HttpSession httpSession = request.getSession(false);
        SecurityContext context = readSecurityContextFromRequest(request);

        if (context == null) {
            if (log.isDebugEnabled()) {
                log.debug("No SecurityContext was available from the HttpSession: " + httpSession + ". "
                        + "A new one will be created.");
            }
            context = generateNewContext();

        }

        requestResponseHolder
                .setResponse(new SaveToSessionResponseWrapper(response, request, httpSession != null, context));

        return context;
    }

    @Override
    public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
        SaveToSessionResponseWrapper responseWrapper = (SaveToSessionResponseWrapper) response;
        // saveContext() might already be called by the response wrapper
        // if something in the chain called sendError() or sendRedirect(). This ensures we only call it
        // once per request.
        if (!responseWrapper.isContextSaved()) {
            responseWrapper.saveContext(context);
        }
    }

    @Override
    public boolean containsContext(HttpServletRequest request) {
        return getSecurityContext(request) != null;
    }

    /**
     * Reads the security context from the request object.
     * 
     * @param request The servlet request.
     * @return The security context.
     */
    private SecurityContext readSecurityContextFromRequest(HttpServletRequest request) {
        // Session exists, so try to obtain a context from it.

        SecurityContext securityContext = getSecurityContext(request);

        if (securityContext == null) {
            if (log.isDebugEnabled()) {
                log.debug("HttpSession returned null object for SPRING_SECURITY_CONTEXT");
            }

            return null;
        }

        if (log.isDebugEnabled()) {
            log.debug("Obtained a valid SecurityContext from SPRING_SECURITY_CONTEXT: '" + securityContext + "'");
        }

        // Everything OK. The only non-null return from this method.

        return securityContext;
    }

    /**
     * By default, calls {@link SecurityContextHolder#createEmptyContext()} to obtain a new context
     * (there should be no context present in the holder when this method is called). Using this
     * approach the context creation strategy is decided by the
     * {@link SecurityContextHolderStrategy} in use. The default implementations will return a new
     * <tt>SecurityContextImpl</tt>.
     * <p>
     * An alternative way of customizing the <tt>SecurityContext</tt> implementation is by setting
     * the <tt>securityContextClass</tt> property. In this case, the method will attempt to invoke
     * the no-args constructor on the supplied class instead and return the created instance.
     * 
     * @return a new SecurityContext instance. Never null.
     */
    private SecurityContext generateNewContext() {
        return SecurityContextHolder.createEmptyContext();
    }

    /**
     * If set to true (the default), a session will be created (if required) to store the security
     * context if it is determined that its contents are different from the default empty context
     * value.
     * <p>
     * Note that setting this flag to false does not prevent this class from storing the security
     * context. If your application (or another filter) creates a session, then the security context
     * will still be stored for an authenticated user.
     * 
     * @param allowSessionCreation The session creation flag.
     */
    public void setAllowSessionCreation(boolean allowSessionCreation) {
        this.allowSessionCreation = allowSessionCreation;
    }

    /**
     * Allows the use of session identifiers in URLs to be disabled. Off by default.
     * 
     * @param disableUrlRewriting set to <tt>true</tt> to disable URL encoding methods in the
     *            response wrapper and prevent the use of <tt>jsessionid</tt> parameters.
     */
    public void setDisableUrlRewriting(boolean disableUrlRewriting) {
        this.disableUrlRewriting = disableUrlRewriting;
    }

    //~ Inner Classes ==================================================================================================

    /**
     * Wrapper that is applied to every request/response to update the <code>HttpSession<code> with
     * the <code>SecurityContext</code> when a <code>sendError()</code> or <code>sendRedirect</code>
     * happens. See SEC-398.
     * <p>
     * Stores the necessary state from the start of the request in order to make a decision about
     * whether the security context has changed before saving it.
     */
    final class SaveToSessionResponseWrapper extends SaveContextOnUpdateOrErrorResponseWrapper {

        private final HttpServletRequest request;

        private final boolean httpSessionExistedAtStartOfRequest;

        private final SecurityContext contextBeforeExecution;

        private final Authentication authBeforeExecution;

        /**
         * Takes the parameters required to call <code>saveContext()</code> successfully in addition
         * to the request and the response object we are wrapping.
         * 
         * @param response The response object
         * @param request The request object.
         * @param httpSessionExistedAtStartOfRequest Indicates whether there was a session in place
         *            before the filter chain executed. If this is true, and the session is found to
         *            be null, this indicates that it was invalidated during the request and a new
         *            session will now be created.
         * @param context The security context
         */
        SaveToSessionResponseWrapper(HttpServletResponse response, HttpServletRequest request,
                boolean httpSessionExistedAtStartOfRequest, SecurityContext context) {
            super(response, disableUrlRewriting);
            this.request = request;
            this.httpSessionExistedAtStartOfRequest = httpSessionExistedAtStartOfRequest;
            this.contextBeforeExecution = context;
            this.authBeforeExecution = context.getAuthentication();
        }

        /**
         * Stores the supplied security context in the session (if available) and if it has changed
         * since it was set at the start of the request. If the AuthenticationTrustResolver
         * identifies the current user as anonymous, then the context will not be stored.
         * 
         * @param context the context object obtained from the SecurityContextHolder after the
         *            request has been processed by the filter chain.
         *            SecurityContextHolder.getContext() cannot be used to obtain the context as it
         *            has already been cleared by the time this method is called.
         */
        @Override
        protected void saveContext(SecurityContext context) {
            Authentication authentication = context.getAuthentication();
            HttpSession httpSession = request.getSession(false);

            // See SEC-776
            if (authentication == null || authenticationTrustResolver.isAnonymous(authentication)) {
                if (log.isDebugEnabled()) {
                    log.debug(
                            "SecurityContext contents are anonymous - context will not be stored in HttpSession. ");
                }

                if (httpSession != null && !contextObject.equals(contextBeforeExecution)) {
                    // SEC-1587 A non-anonymous context may still be in the session
                    // SEC-1735 remove if the contextBeforeExecution was not anonymous
                    httpSession.removeAttribute(getDesktopContextKey(request));
                }
                return;
            }

            if (httpSession == null) {
                httpSession = createNewSessionIfAllowed(context);
            }

            // If HttpSession exists, store current SecurityContextHolder contents but only if
            // the SecurityContext has actually changed (see JIRA SEC-37)
            if (httpSession != null && contextChanged(context)) {
                httpSession.setAttribute(getDesktopContextKey(request), context);

                if (log.isDebugEnabled()) {
                    log.debug("SecurityContext stored to HttpSession: '" + context + "'");
                }
            }
        }

        private boolean contextChanged(SecurityContext context) {
            return context != contextBeforeExecution || context.getAuthentication() != authBeforeExecution;
        }

        private HttpSession createNewSessionIfAllowed(SecurityContext context) {
            if (httpSessionExistedAtStartOfRequest) {
                if (log.isDebugEnabled()) {
                    log.debug("HttpSession is now null, but was not null at start of request; "
                            + "session was invalidated, so do not create a new session");
                }

                return null;
            }

            if (!allowSessionCreation) {
                if (log.isDebugEnabled()) {
                    log.debug("The HttpSession is currently null, and the "
                            + "HttpSessionContextIntegrationFilter is prohibited from creating an HttpSession "
                            + "(because the allowSessionCreation property is false) - SecurityContext thus not "
                            + "stored for next request");
                }

                return null;
            }
            // Generate a HttpSession only if we need to

            if (contextObject.equals(context)) {
                if (log.isDebugEnabled()) {
                    log.debug(
                            "HttpSession is null, but SecurityContext has not changed from default empty context: ' "
                                    + context + "'; not creating HttpSession or storing SecurityContext");
                }

                return null;
            }

            if (log.isDebugEnabled()) {
                log.debug("HttpSession being created as SecurityContext is non-default");
            }

            try {
                return request.getSession(true);
            } catch (IllegalStateException e) {
                // Response must already be committed, therefore can't create a new session
                log.warn("Failed to create a session, as response has been committed. Unable to store"
                        + " SecurityContext.");
            }

            return null;
        }
    }

    /**
     * Force transfer of session-based security context to desktop.
     */
    @Override
    public void onInit(Desktop desktop) {
        HttpSession session = (HttpSession) desktop.getSession().getNativeSession();
        getSecurityContext(session, desktop.getId());
    }

    /**
     * Remove desktop security context on desktop cleanup.
     */
    @Override
    public void onCleanup(Desktop desktop) {
        HttpSession session = (HttpSession) desktop.getSession().getNativeSession();
        session.removeAttribute(getDesktopContextKey(desktop.getId()));
    }

    @Override
    public int getPriority() {
        return Integer.MIN_VALUE;
    }
}