acegi 학습 소감: httpSessionContextIntegrationFilter

25423 단어
밤새도록 httpSessionContextIntegrationFilter를 연구했습니다.
SessionContextIntegrationFilter:InitializingBean 인터페이스가 bean에 불러오면 afterProperties Set () 방법을 호출하여 Cloneable 인터페이스를 실현하고clone 방법은 복제 대상에 사용되는 Filter 메인 라인을 실현합니다: 1.Request 대상에서session을 가져오고 Request 대상에 FILTERAPPLIED는 TURE 2.session에서 SecurityContext 대상의 실례를 얻습니다. 이 대상은 인증 대상(Authentication)을 의미합니다. 이 인터페이스는 Principal 인터페이스를 실현하고 유일성을 표시하며 인증과 관련된 몇 가지 방법을 제공합니다.session에 SecurityContext 대상이 존재하지 않으면 SecurityContextImpl이 SecurityContext 인터페이스에 대한 구현 4.SecurityContext 대상의 실례적인hashcode를 가져옵니다. 만약 SecurityContext에 인증 대상이 존재하지 않는다면 이때hashcode는 -1이고 그렇지 않으면 값이 5.사용자 정의 대상 OnRedirect Update Session ResponseWrapper, httpservlet Response 인터페이스를 실현하여 매번 점프, 리셋, 출력 시session의 Security Context 대상을 갱신합니다 6.SecurityContextHolder 객체정책에 따라 MODE 선택THREADLOCAL,MODE_INHERITABLETHREADLOCAL,MODE_GLOBAL은 현재 스레드 저장소, 현재 스레드, 부 스레드 (주 스레드), 전역 저장 보안 Context 대상을 대표한다.7. 다음 Filter로 건너뛰기 8.마지막으로 모든 Filter를 실행한 후에 Security Context Holder의 Security Context 대상을 가져옵니다.sessino에 저장되지 않으면 Security Context를session에 저장하고 Security Context Holder의 Security Context 지우기
다음은 httpSessionContextIntegrationFilter 소스입니다.
/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.acegisecurity.context;

import java.io.IOException;
import java.lang.reflect.Method;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

/**
 * Populates the {@link SecurityContextHolder} with information obtained from
 * the HttpSession.
 * 
 * 
 * The HttpSession will be queried to retrieve the
 * SecurityContext that should be stored against the
 * SecurityContextHolder for the duration of the web request. At
 * the end of the web request, any updates made to the
 * SecurityContextHolder will be persisted back to the
 * HttpSession by this filter.
 * 
 * 
 * If a valid SecurityContext cannot be obtained from the
 * HttpSession for whatever reason, a fresh
 * SecurityContext will be created and used instead. The created
 * object will be of the instance defined by the {@link #setContext(Class)}
 * method (which defaults to {@link org.acegisecurity.context.SecurityContextImpl}.
 * 
 * 
 * No HttpSession will be created by this filter if one does not
 * already exist. If at the end of the web request the HttpSession
 * does not exist, a HttpSession will only be created if
 * the current contents of the SecurityContextHolder are not
 * {@link java.lang.Object#equals(java.lang.Object)} to a new
 * instance of {@link #setContext(Class)}. This avoids needless
 * HttpSession creation, but automates the storage of changes
 * made to the SecurityContextHolder. There is one exception to
 * this rule, that is if the {@link #forceEagerSessionCreation} property is
 * true, in which case sessions will always be created
 * irrespective of normal session-minimisation logic (the default is
 * false, as this is resource intensive and not recommended).
 * 
 * 
 * This filter will only execute once per request, to resolve servlet container
 * (specifically Weblogic) incompatibilities.
 * 
 * 
 * If for whatever reason no HttpSession should ever be
 * created (eg this filter is only being used with Basic authentication or
 * similar clients that will never present the same jsessionid
 * etc), the {@link #setAllowSessionCreation(boolean)} should be set to
 * false. Only do this if you really need to conserve server
 * memory and ensure all classes using the SecurityContextHolder
 * are designed to have no persistence of the SecurityContext
 * between web requests. Please note that if {@link #forceEagerSessionCreation}
 * is true, the allowSessionCreation must also be
 * true (setting it to false will cause a startup
 * time error).
 * 
 * 
 * This filter MUST be executed BEFORE any authentication processing mechanisms.
 * Authentication processing mechanisms (eg BASIC, CAS processing filters etc)
 * expect the SecurityContextHolder to contain a valid
 * SecurityContext by the time they execute.
 * 
 *
 * @author Ben Alex
 * @author Patrick Burleson
 * @author Luke Taylor
 * @author Martin Algesten
 *
 * @version $Id: HttpSessionContextIntegrationFilter.java 2004 2007-09-01 14:43:09Z raykrueger $
 */
public class HttpSessionContextIntegrationFilter implements InitializingBean, Filter {
    //~ Static fields/initializers =====================================================================================

    protected static final Log logger = LogFactory.getLog(HttpSessionContextIntegrationFilter.class);

    static final String FILTER_APPLIED = "__acegi_session_integration_filter_applied";

    public static final String ACEGI_SECURITY_CONTEXT_KEY = "ACEGI_SECURITY_CONTEXT";

    //~ Instance fields ================================================================================================

    private Class context = SecurityContextImpl.class;

    private Object contextObject;

    /**
     * Indicates if this filter can create a HttpSession if
     * needed (sessions are always created sparingly, but setting this value to
     * false will prohibit sessions from ever being created).
     * Defaults to true. Do not set to false if
     * you are have set {@link #forceEagerSessionCreation} to true,
     * as the properties would be in conflict.
     */
    private boolean allowSessionCreation = true;

    /**
     * Indicates if this filter is required to create a HttpSession
     * for every request before proceeding through the filter chain, even if the
     * HttpSession would not ordinarily have been created. By
     * default this is false, which is entirely appropriate for
     * most circumstances as you do not want a HttpSession
     * created unless the filter actually needs one. It is envisaged the main
     * situation in which this property would be set to true is
     * if using other filters that depend on a HttpSession
     * already existing, such as those which need to obtain a session ID. This
     * is only required in specialised cases, so leave it set to
     * false unless you have an actual requirement and are
     * conscious of the session creation overhead.
     */
    private boolean forceEagerSessionCreation = false;

    /**
     * Indicates whether the SecurityContext will be cloned from
     * the HttpSession. The default is to simply reference (ie
     * the default is false). The default may cause issues if
     * concurrent threads need to have a different security identity from other
     * threads being concurrently processed that share the same
     * HttpSession. In most normal environments this does not
     * represent an issue, as changes to the security identity in one thread is
     * allowed to affect the security identitiy in other threads associated with
     * the same HttpSession. For unusual cases where this is not
     * permitted, change this value to true and ensure the
     * {@link #context} is set to a SecurityContext that
     * implements {@link Cloneable} and overrides the clone()
     * method.
     */
    private boolean cloneFromHttpSession = false;

    public boolean isCloneFromHttpSession() {
        return cloneFromHttpSession;
    }

    public void setCloneFromHttpSession(boolean cloneFromHttpSession) {
        this.cloneFromHttpSession = cloneFromHttpSession;
    }

    public HttpSessionContextIntegrationFilter() throws ServletException {
        this.contextObject = generateNewContext();
    }

    //~ Methods ========================================================================================================

    public void afterPropertiesSet() throws Exception {
        if ((this.context == null) || (!SecurityContext.class.isAssignableFrom(this.context))) {
            throw new IllegalArgumentException("context must be defined and implement SecurityContext "
                    + "(typically use org.acegisecurity.context.SecurityContextImpl; existing class is " + this.context
                    + ")");
        }

        if (forceEagerSessionCreation && !allowSessionCreation) {
            throw new IllegalArgumentException(
                    "If using forceEagerSessionCreation, you must set allowSessionCreation to also be true");
        }
    }

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
            ServletException {

        Assert.isInstanceOf(HttpServletRequest.class, req, "ServletRequest must be an instance of HttpServletRequest");
        Assert.isInstanceOf(HttpServletResponse.class, res, "ServletResponse must be an instance of HttpServletResponse");

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        if (request.getAttribute(FILTER_APPLIED) != null) {
            // ensure that filter is only applied once per request
            chain.doFilter(request, response);

            return;
        }

        HttpSession httpSession = null;

        try {
            httpSession = request.getSession(forceEagerSessionCreation);
        }
        catch (IllegalStateException ignored) {
        }

        boolean httpSessionExistedAtStartOfRequest = httpSession != null;

        SecurityContext contextBeforeChainExecution = readSecurityContextFromSession(httpSession);

        // Make the HttpSession null, as we don't want to keep a reference to it lying
        // around in case chain.doFilter() invalidates it.
        httpSession = null;

        if (contextBeforeChainExecution == null) {
            contextBeforeChainExecution = generateNewContext();

            if (logger.isDebugEnabled()) {
                logger.debug("New SecurityContext instance will be associated with SecurityContextHolder");
            }
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("Obtained a valid SecurityContext from ACEGI_SECURITY_CONTEXT to "
                        + "associate with SecurityContextHolder: '" + contextBeforeChainExecution + "'");
            }
        }

        int contextHashBeforeChainExecution = contextBeforeChainExecution.hashCode();
        request.setAttribute(FILTER_APPLIED, Boolean.TRUE);

        // Create a wrapper that will eagerly update the session with the security context
        // if anything in the chain does a sendError() or sendRedirect().
        // See SEC-398

        OnRedirectUpdateSessionResponseWrapper responseWrapper =
                new OnRedirectUpdateSessionResponseWrapper( response, request,
                    httpSessionExistedAtStartOfRequest, contextHashBeforeChainExecution );

        // Proceed with chain

        try {
            // This is the only place in this class where SecurityContextHolder.setContext() is called
            SecurityContextHolder.setContext(contextBeforeChainExecution);

            chain.doFilter(request, responseWrapper);
        }
        finally {
            // This is the only place in this class where SecurityContextHolder.getContext() is called
            SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();

            // Crucial removal of SecurityContextHolder contents - do this before anything else.
            SecurityContextHolder.clearContext();

            request.removeAttribute(FILTER_APPLIED);

            // storeSecurityContextInSession() 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.isSessionUpdateDone() ) {
              storeSecurityContextInSession(contextAfterChainExecution, request,
                      httpSessionExistedAtStartOfRequest, contextHashBeforeChainExecution);
            }

            if (logger.isDebugEnabled()) {
                logger.debug("SecurityContextHolder now cleared, as request processing completed");
            }
        }
    }

    /**
     * Gets the security context from the session (if available) and returns it.
     * 
     * If the session is null, the context object is null or the context object stored in the session
     * is not an instance of SecurityContext it will return null.
     * 
     * If cloneFromHttpSession is set to true, it will attempt to clone the context object
     * and return the cloned instance.
     *
     * @param httpSession the session obtained from the request.
     */
    private SecurityContext readSecurityContextFromSession(HttpSession httpSession) {
        if (httpSession == null) {
            if (logger.isDebugEnabled()) {
                logger.debug("No HttpSession currently exists");
            }

            return null;
        }

        // Session exists, so try to obtain a context from it.

        Object contextFromSessionObject = httpSession.getAttribute(ACEGI_SECURITY_CONTEXT_KEY);

        if (contextFromSessionObject == null) {
            if (logger.isDebugEnabled()) {
                logger.debug("HttpSession returned null object for ACEGI_SECURITY_CONTEXT");
            }

            return null;
        }

        // We now have the security context object from the session.

        // Clone if required (see SEC-356)
        if (cloneFromHttpSession) {
            Assert.isInstanceOf(Cloneable.class, contextFromSessionObject,
                    "Context must implement Clonable and provide a Object.clone() method");
            try {
                Method m = contextFromSessionObject.getClass().getMethod("clone", new Class[]{});
                if (!m.isAccessible()) {
                    m.setAccessible(true);
                }
                contextFromSessionObject = m.invoke(contextFromSessionObject, new Object[]{});
            }
            catch (Exception ex) {
                ReflectionUtils.handleReflectionException(ex);
            }
        }

        if (!(contextFromSessionObject instanceof SecurityContext)) {
            if (logger.isWarnEnabled()) {
                logger.warn("ACEGI_SECURITY_CONTEXT did not contain a SecurityContext but contained: '"
                        + contextFromSessionObject
                        + "'; are you improperly modifying the HttpSession directly "
                        + "(you should always use SecurityContextHolder) or using the HttpSession attribute "
                        + "reserved for this class?");
            }

            return null;
        }

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

        return (SecurityContext) contextFromSessionObject;
    }

    /**
     * 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.
     *
     * @param securityContext 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.
     * @param request the request object (used to obtain the session, if one exists).
     * @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 contextHashBeforeChainExecution the hashcode of the context before the filter chain executed.
     *        The context will only be stored if it has a different hashcode, indicating that the context changed
     *        during the request.
     *
     */
    private void storeSecurityContextInSession(SecurityContext securityContext,
                                               HttpServletRequest request,
                                               boolean httpSessionExistedAtStartOfRequest,
                                               int contextHashBeforeChainExecution) {
        HttpSession httpSession = null;

        try {
            httpSession = request.getSession(false);
        }
        catch (IllegalStateException ignored) {
        }

        if (httpSession == null) {
            if (httpSessionExistedAtStartOfRequest) {
                if (logger.isDebugEnabled()) {
                    logger.debug("HttpSession is now null, but was not null at start of request; "
                            + "session was invalidated, so do not create a new session");
                }
            } else {
                // Generate a HttpSession only if we need to

                if (!allowSessionCreation) {
                    if (logger.isDebugEnabled()) {
                        logger.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");
                    }
                } else if (!contextObject.equals(securityContext)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("HttpSession being created as SecurityContextHolder contents are non-default");
                    }

                    try {
                        httpSession = request.getSession(true);
                    }
                    catch (IllegalStateException ignored) {
                    }
                } else {
                    if (logger.isDebugEnabled()) {
                        logger.debug("HttpSession is null, but SecurityContextHolder has not changed from default: ' "
                                + securityContext
                                + "'; not creating HttpSession or storing SecurityContextHolder contents");
                    }
                }
            }
        }

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

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

    public SecurityContext generateNewContext() throws ServletException {
        try {
            return (SecurityContext) this.context.newInstance();
        }
        catch (InstantiationException ie) {
            throw new ServletException(ie);
        }
        catch (IllegalAccessException iae) {
            throw new ServletException(iae);
        }
    }

    /**
     * Does nothing. We use IoC container lifecycle services instead.
     *
     * @param filterConfig ignored
     * @throws ServletException ignored
     */
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    /**
     * Does nothing. We use IoC container lifecycle services instead.
     */
    public void destroy() {
    }

    public boolean isAllowSessionCreation() {
        return allowSessionCreation;
    }

    public void setAllowSessionCreation(boolean allowSessionCreation) {
        this.allowSessionCreation = allowSessionCreation;
    }

    public Class getContext() {
        return context;
    }

    public void setContext(Class secureContext) {
        this.context = secureContext;
    }

    public boolean isForceEagerSessionCreation() {
        return forceEagerSessionCreation;
    }

    public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) {
        this.forceEagerSessionCreation = forceEagerSessionCreation;
    }


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

    /**
     * Wrapper that is applied to every request to update the HttpSession with
     * the SecurityContext when a sendError() or sendRedirect
     * happens. See SEC-398. The class contains the fields needed to call
     * storeSecurityContextInSession()
     */
    private class OnRedirectUpdateSessionResponseWrapper extends HttpServletResponseWrapper {

        HttpServletRequest request;
        boolean httpSessionExistedAtStartOfRequest;
        int contextHashBeforeChainExecution;

        // Used to ensure storeSecurityContextInSession() is only
        // called once.
        boolean sessionUpdateDone = false;

        /**
         * Takes the parameters required to call storeSecurityContextInSession() in
         * addition to the response object we are wrapping.
         * @see HttpSessionContextIntegrationFilter#storeSecurityContextInSession(SecurityContext, ServletRequest, boolean, int)
         */
        public OnRedirectUpdateSessionResponseWrapper(HttpServletResponse response,
                                                      HttpServletRequest request,
                                                      boolean httpSessionExistedAtStartOfRequest,
                                                      int contextHashBeforeChainExecution) {
            super(response);
            this.request = request;
            this.httpSessionExistedAtStartOfRequest = httpSessionExistedAtStartOfRequest;
            this.contextHashBeforeChainExecution = contextHashBeforeChainExecution;
        }

        /**
         * Makes sure the session is updated before calling the
         * superclass sendError()
         */
        public void sendError(int sc) throws IOException {
            doSessionUpdate();
            super.sendError(sc);
        }

        /**
         * Makes sure the session is updated before calling the
         * superclass sendError()
         */
        public void sendError(int sc, String msg) throws IOException {
            doSessionUpdate();
            super.sendError(sc, msg);
        }

        /**
         * Makes sure the session is updated before calling the
         * superclass sendRedirect()
         */
        public void sendRedirect(String location) throws IOException {
            doSessionUpdate();
            super.sendRedirect(location);
        }

        /**
         * Calls storeSecurityContextInSession()
         */
        private void doSessionUpdate() {
            if (sessionUpdateDone) {
                return;
            }
            SecurityContext securityContext = SecurityContextHolder.getContext();
            storeSecurityContextInSession(securityContext, request,
                    httpSessionExistedAtStartOfRequest, contextHashBeforeChainExecution);
            sessionUpdateDone = true;
        }

        /**
         * Tells if the response wrapper has called
         * storeSecurityContextInSession().
         */
        public boolean isSessionUpdateDone() {
            return sessionUpdateDone;
        }

    }

}

좋은 웹페이지 즐겨찾기