Java tutorial
/* * Copyright 2005-2007 the original author or authors. * * 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.springframework.security.extensions.portlet; import java.lang.reflect.Method; import javax.portlet.ActionRequest; import javax.portlet.ActionResponse; import javax.portlet.EventRequest; import javax.portlet.EventResponse; import javax.portlet.PortletException; import javax.portlet.PortletRequest; import javax.portlet.PortletResponse; import javax.portlet.PortletSession; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; import javax.portlet.ResourceRequest; import javax.portlet.ResourceResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.context.SecurityContextPersistenceFilter; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; import org.springframework.web.portlet.HandlerInterceptor; import org.springframework.web.portlet.ModelAndView; /** * <p>This interceptor populates the {@link SecurityContextHolder} with information obtained from the * <code>PortletSession</code>. It is applied to both <code>ActionRequest</code>s and * <code>RenderRequest</code>s</p> * * <p>The <code>PortletSession</code> will be queried to retrieve the <code>SecurityContext</code> that should * be stored against the <code>SecurityContextHolder</code> for the duration of the portlet request. At the * end of the request, any updates made to the <code>SecurityContextHolder</code> will be persisted back to the * <code>PortletSession</code> by this interceptor.</p> * * <p> If a valid <code>SecurityContext</code> cannot be obtained from the <code>PortletSession</code> for * whatever reason, a fresh <code>SecurityContext</code> will be created and used instead. The created object * will be of the instance defined by the {@link #setContext(Class)} method. If this hasn't been set, a call to * {@link SecurityContextHolder#createEmptyContext()} will be used to create the instance. * * <p>A <code>PortletSession</code> may be created by this interceptor if one does not already exist. If at the * end of the portlet request the <code>PortletSession</code> does not exist, one will <b>only</b> be created if * the current contents of the <code>SecurityContextHolder</code> are not the {@link java.lang.Object#equals} * to a <code>new</code> instance of {@link #contextClass}. This avoids needless <code>PortletSession</code> creation, * and automates the storage of changes made to the <code>SecurityContextHolder</code>. There is one exception to * this rule, that is if the {@link #forceEagerSessionCreation} property is <code>true</code>, in which case * sessions will always be created irrespective of normal session-minimization logic (the default is * <code>false</code>, as this is resource intensive and not recommended).</p> * * <p>If for whatever reason no <code>PortletSession</code> should <b>ever</b> be created, the * {@link #allowSessionCreation} property should be set to <code>false</code>. Only do this if you really need * to conserve server memory and ensure all classes using the <code>SecurityContextHolder</code> are designed to * have no persistence of the <code>SecurityContext</code> between web requests. Please note that if * {@link #forceEagerSessionCreation} is <code>true</code>, the <code>allowSessionCreation</code> must also be * <code>true</code> (setting it to <code>false</code> will cause a startup-time error).</p> * <p>This interceptor <b>must</b> be executed <b>before</p> any authentication processing mechanisms. These * mechanisms (specifically {@link org.springframework.security.portlet.PortletProcessingInterceptor}) expect the * <code>SecurityContextHolder</code> to contain a valid <code>SecurityContext</code> by the time they execute.</p> * * <p>An important nuance to this interceptor is that (by default) the <code>SecurityContext</code> is stored * into the <code>APPLICATION_SCOPE</code> of the <code>PortletSession</code>. This doesn't just mean you will be * sharing it with all the other portlets in your webapp (which is generally a good idea). It also means that (if * you have done all the other appropriate magic), you will share this <code>SecurityContext</code> with servlets in * your webapp. This is very useful if you have servlets serving images or processing AJAX calls from your portlets * since they can now use the {@link SecurityContextPersistenceFilter} to access the same <code>SecurityContext<code> * object from the session. This allows these calls to be secured as well as the portlet calls.</p> * * Much of the logic of this interceptor comes from the {@link SecurityContextPersistenceFilter} class which * fills the same purpose on the servlet side. Ben Alex and Patrick Burlson are listed as authors here because they * are the authors of that class and there are blocks of code that essentially identical between the two. (Making this * a good candidate for refactoring someday.) * * <p>Unlike <code>HttpSessionContextIntegrationFilter</code>, this interceptor does not check to see if it is * getting applied multiple times. This shouldn't be a problem since the application of interceptors is under the * control of the Spring Portlet MVC framework and tends to be more explicit and more predictable than the application * of filters. However, you should still be careful to only apply this inteceptor to your request once.</p> * * @author John A. Lewis * @author Ben Alex * @author Patrick Burleson * @since 2.0 * @version $Id: PortletSessionContextIntegrationInterceptor.java 3615 2009-04-27 06:06:42Z ltaylor $ */ public class PortletSessionContextIntegrationInterceptor implements InitializingBean, HandlerInterceptor { //~ Static fields/initializers ===================================================================================== protected static final Log logger = LogFactory.getLog(PortletSessionContextIntegrationInterceptor.class); public static final String SPRING_SECURITY_CONTEXT_KEY = HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY; private static final String SESSION_EXISTED = PortletSessionContextIntegrationInterceptor.class.getName() + ".SESSION_EXISTED"; private static final String CONTEXT_HASHCODE = PortletSessionContextIntegrationInterceptor.class.getName() + ".CONTEXT_HASHCODE"; //~ Instance fields ================================================================================================ private Class<? extends SecurityContext> contextClass; private Object contextObject; /** * Indicates if this interceptor can create a <code>PortletSession</code> if * needed (sessions are always created sparingly, but setting this value to * <code>false</code> will prohibit sessions from ever being created). * Defaults to <code>true</code>. Do not set to <code>false</code> if * you are have set {@link #forceEagerSessionCreation} to <code>true</code>, * as the properties would be in conflict. */ private boolean allowSessionCreation = true; /** * Indicates if this interceptor is required to create a <code>PortletSession</code> * for every request before proceeding through the request process, even if the * <code>PortletSession</code> would not ordinarily have been created. By * default this is <code>false</code>, which is entirely appropriate for * most circumstances as you do not want a <code>PortletSession</code> * created unless the interceptor actually needs one. It is envisaged the main * situation in which this property would be set to <code>true</code> is * if using other interceptors that depend on a <code>PortletSession</code> * already existing. This is only required in specialized cases, so leave it set to * <code>false</code> unless you have an actual requirement and aware of the * session creation overhead. */ private boolean forceEagerSessionCreation = false; /** * Indicates whether the <code>SecurityContext</code> will be cloned from * the <code>PortletSession</code>. The default is to simply reference * (the default is <code>false</code>). 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 * <code>PortletSession</code>. 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 identity in other threads associated with * the same <code>PortletSession</code>. For unusual cases where this is not * permitted, change this value to <code>true</code> and ensure the * {@link #contextClass} is set to a <code>SecurityContext</code> that * implements {@link Cloneable} and overrides the <code>clone()</code> * method. */ private boolean cloneFromPortletSession = false; /** * Indicates wether the <code>APPLICATION_SCOPE</code> mode of the * <code>PortletSession</code> should be used for storing the * <code>SecurityContext</code>. The default is </code>true</code>. * This allows it to be shared between the portlets in the webapp and * potentially with servlets in the webapp as well. If this is set to * <code>false</code>, then the <code>PORTLET_SCOPE</code> will be used * instead. */ private boolean useApplicationScopePortletSession = true; //~ Constructors =================================================================================================== public PortletSessionContextIntegrationInterceptor() throws PortletException { this.contextObject = generateNewContext(); } //~ Methods ======================================================================================================== public void afterPropertiesSet() throws Exception { // check that session creation options make sense if ((forceEagerSessionCreation == true) && (allowSessionCreation == false)) { throw new IllegalArgumentException( "If using forceEagerSessionCreation, you must set allowSessionCreation to also be true"); } } public boolean preHandleAction(ActionRequest request, ActionResponse response, Object handler) throws Exception { // call to common preHandle method return preHandle(request, response, handler); } public boolean preHandleRender(RenderRequest request, RenderResponse response, Object handler) throws Exception { // call to common preHandle method return preHandle(request, response, handler); } public void postHandleRender(RenderRequest request, RenderResponse response, Object handler, ModelAndView modelAndView) throws Exception { // no-op } public void afterActionCompletion(ActionRequest request, ActionResponse response, Object handler, Exception ex) throws Exception { // call to common afterCompletion method afterCompletion(request, response, handler, ex); } public void afterRenderCompletion(RenderRequest request, RenderResponse response, Object handler, Exception ex) throws Exception { // call to common afterCompletion method afterCompletion(request, response, handler, ex); } /** * {@inheritDoc} */ public boolean preHandleResource(ResourceRequest request, ResourceResponse response, Object handler) throws Exception { return preHandle(request, response, handler); } /** * {@inheritDoc} */ public void postHandleResource(ResourceRequest request, ResourceResponse response, Object handler, ModelAndView modelAndView) throws Exception { // no-op } /** * {@inheritDoc} */ public void afterResourceCompletion(ResourceRequest request, ResourceResponse response, Object handler, Exception ex) throws Exception { // call to common afterCompletion method afterCompletion(request, response, handler, ex); } /** * {@inheritDoc} */ public boolean preHandleEvent(EventRequest request, EventResponse response, Object handler) throws Exception { return preHandle(request, response, handler); } /** * {@inheritDoc} */ public void afterEventCompletion(EventRequest request, EventResponse response, Object handler, Exception ex) throws Exception { // call to common afterCompletion method afterCompletion(request, response, handler, ex); } private boolean preHandle(PortletRequest request, PortletResponse response, Object handler) throws Exception { PortletSession portletSession = null; boolean portletSessionExistedAtStartOfRequest = false; // see if the portlet session already exists (or should be eagerly created) try { portletSession = request.getPortletSession(forceEagerSessionCreation); } catch (IllegalStateException ignored) { } // if there is a session, then see if there is a contextClass to bring in if (portletSession != null) { // remember that the session already existed portletSessionExistedAtStartOfRequest = true; // attempt to retrieve the contextClass from the session Object contextFromSessionObject = portletSession.getAttribute(SPRING_SECURITY_CONTEXT_KEY, portletSessionScope()); // if we got a contextClass then place it into the holder if (contextFromSessionObject != null) { // if we are supposed to clone it, then do so if (cloneFromPortletSession) { 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 what we got is a valid contextClass then place it into the holder, otherwise create a new one if (contextFromSessionObject instanceof SecurityContext) { if (logger.isDebugEnabled()) logger.debug("Obtained from SPRING_SECURITY_CONTEXT a valid SecurityContext and " + "set to SecurityContextHolder: '" + contextFromSessionObject + "'"); SecurityContextHolder.setContext((SecurityContext) contextFromSessionObject); } else { if (logger.isWarnEnabled()) logger.warn("SPRING_SECURITY_CONTEXT did not contain a SecurityContext but contained: '" + contextFromSessionObject + "'; are you improperly modifying the PortletSession directly " + "(you should always use SecurityContextHolder) or using the PortletSession attribute " + "reserved for this class? - new SecurityContext instance associated with " + "SecurityContextHolder"); SecurityContextHolder.setContext(generateNewContext()); } } else { // there was no contextClass in the session, so create a new contextClass and put it in the holder if (logger.isDebugEnabled()) logger.debug("PortletSession returned null object for SPRING_SECURITY_CONTEXT - new " + "SecurityContext instance associated with SecurityContextHolder"); SecurityContextHolder.setContext(generateNewContext()); } } else { // there was no session, so create a new contextClass and place it in the holder if (logger.isDebugEnabled()) logger.debug("No PortletSession currently exists - new SecurityContext instance " + "associated with SecurityContextHolder"); SecurityContextHolder.setContext(generateNewContext()); } // place attributes onto the request to remember if the session existed and the hashcode of the contextClass request.setAttribute(SESSION_EXISTED, new Boolean(portletSessionExistedAtStartOfRequest)); request.setAttribute(CONTEXT_HASHCODE, new Integer(SecurityContextHolder.getContext().hashCode())); return true; } private void afterCompletion(PortletRequest request, PortletResponse response, Object handler, Exception ex) throws Exception { PortletSession portletSession = null; // retrieve the attributes that remember if the session existed and the hashcode of the contextClass boolean portletSessionExistedAtStartOfRequest = ((Boolean) request.getAttribute(SESSION_EXISTED)) .booleanValue(); int oldContextHashCode = ((Integer) request.getAttribute(CONTEXT_HASHCODE)).intValue(); // try to retrieve an existing portlet session try { portletSession = request.getPortletSession(false); } catch (IllegalStateException ignored) { } // if there is now no session but there was one at the beginning then it must have been invalidated if ((portletSession == null) && portletSessionExistedAtStartOfRequest) { if (logger.isDebugEnabled()) logger.debug("PortletSession is now null, but was not null at start of request; " + "session was invalidated, so do not create a new session"); } // create a new portlet session if we need to if ((portletSession == null) && !portletSessionExistedAtStartOfRequest) { // if we're not allowed to create a new session, then report that if (!allowSessionCreation) { if (logger.isDebugEnabled()) logger.debug("The PortletSession is currently null, and the " + "PortletSessionContextIntegrationInterceptor is prohibited from creating a PortletSession " + "(because the allowSessionCreation property is false) - SecurityContext thus not " + "stored for next request"); } // if the contextClass was changed during the request, then go ahead and create a session else if (!contextObject.equals(SecurityContextHolder.getContext())) { if (logger.isDebugEnabled()) logger.debug("PortletSession being created as SecurityContextHolder contents are non-default"); try { portletSession = request.getPortletSession(true); } catch (IllegalStateException ignored) { } } // if nothing in the contextClass changed, then don't bother to create a session else { if (logger.isDebugEnabled()) logger.debug( "PortletSession is null, but SecurityContextHolder has not changed from default: ' " + SecurityContextHolder.getContext() + "'; not creating PortletSession or storing SecurityContextHolder contents"); } } // if the session exists and the contextClass has changes, then store the contextClass back into the session if ((portletSession != null) && (SecurityContextHolder.getContext().hashCode() != oldContextHashCode)) { portletSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext(), portletSessionScope()); if (logger.isDebugEnabled()) logger.debug( "SecurityContext stored to PortletSession: '" + SecurityContextHolder.getContext() + "'"); } // remove the contents of the holder SecurityContextHolder.clearContext(); if (logger.isDebugEnabled()) logger.debug("SecurityContextHolder set to new contextClass, as request processing completed"); } /** * Creates a new <code>SecurityContext</code> object. The specific class is * determined by the setting of the {@link #contextClass} property. * @return the new <code>SecurityContext</code> * @throws PortletException if the creation throws an <code>InstantiationException</code> or * an <code>IllegalAccessException</code>, then this method will wrap them in a * <code>PortletException</code> */ SecurityContext generateNewContext() throws PortletException { if (contextClass == null) { return SecurityContextHolder.createEmptyContext(); } try { return this.contextClass.newInstance(); } catch (InstantiationException ie) { throw new PortletException(ie); } catch (IllegalAccessException iae) { throw new PortletException(iae); } } private int portletSessionScope() { // return the appropriate scope setting based on our property value return (this.useApplicationScopePortletSession ? PortletSession.APPLICATION_SCOPE : PortletSession.PORTLET_SCOPE); } public Class<? extends SecurityContext> getContext() { return contextClass; } public void setContext(Class<? extends SecurityContext> secureContext) { this.contextClass = secureContext; } public boolean isAllowSessionCreation() { return allowSessionCreation; } public void setAllowSessionCreation(boolean allowSessionCreation) { this.allowSessionCreation = allowSessionCreation; } public boolean isForceEagerSessionCreation() { return forceEagerSessionCreation; } public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) { this.forceEagerSessionCreation = forceEagerSessionCreation; } public boolean isCloneFromPortletSession() { return cloneFromPortletSession; } public void setCloneFromPortletSession(boolean cloneFromPortletSession) { this.cloneFromPortletSession = cloneFromPortletSession; } public boolean isUseApplicationScopePortletSession() { return useApplicationScopePortletSession; } public void setUseApplicationScopePortletSession(boolean useApplicationScopePortletSession) { this.useApplicationScopePortletSession = useApplicationScopePortletSession; } }