SEC-1387: Support serialization of security advised beans.
MethodSecurityMetadataSourceAdvisor now takes the SecurityMetadataSource bean name as an extra constructor argument and re-obtains the bean from the BeanFactory in its readObject method. Beans that are advised using <global-method-security> should therefore now be serializable.
This commit is contained in:
		
							parent
							
								
									14ae36ac3b
								
							
						
					
					
						commit
						10dc72b017
					
				| 
						 | 
					@ -324,6 +324,7 @@ public class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionP
 | 
				
			||||||
        advisor.setSource(source);
 | 
					        advisor.setSource(source);
 | 
				
			||||||
        advisor.getConstructorArgumentValues().addGenericArgumentValue(interceptor.getBeanName());
 | 
					        advisor.getConstructorArgumentValues().addGenericArgumentValue(interceptor.getBeanName());
 | 
				
			||||||
        advisor.getConstructorArgumentValues().addGenericArgumentValue(metadataSource);
 | 
					        advisor.getConstructorArgumentValues().addGenericArgumentValue(metadataSource);
 | 
				
			||||||
 | 
					        advisor.getConstructorArgumentValues().addGenericArgumentValue(metadataSource.getBeanName());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        parserContext.getRegistry().registerBeanDefinition(BeanIds.METHOD_SECURITY_METADATA_SOURCE_ADVISOR, advisor);
 | 
					        parserContext.getRegistry().registerBeanDefinition(BeanIds.METHOD_SECURITY_METADATA_SOURCE_ADVISOR, advisor);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,18 @@
 | 
				
			||||||
package org.springframework.security.config.method;
 | 
					package org.springframework.security.config.method;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.ByteArrayInputStream;
 | 
				
			||||||
 | 
					import java.io.ByteArrayOutputStream;
 | 
				
			||||||
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					import java.io.ObjectInputStream;
 | 
				
			||||||
 | 
					import java.io.ObjectOutputStream;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.junit.After;
 | 
					import org.junit.After;
 | 
				
			||||||
import org.junit.Before;
 | 
					import org.junit.Before;
 | 
				
			||||||
import org.junit.Test;
 | 
					import org.junit.Test;
 | 
				
			||||||
import org.springframework.security.access.AccessDeniedException;
 | 
					import org.springframework.security.access.AccessDeniedException;
 | 
				
			||||||
import org.springframework.security.access.annotation.BusinessService;
 | 
					import org.springframework.security.access.annotation.BusinessService;
 | 
				
			||||||
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
 | 
					import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
 | 
				
			||||||
 | 
					import org.springframework.security.authentication.TestingAuthenticationToken;
 | 
				
			||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 | 
					import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 | 
				
			||||||
import org.springframework.security.config.ConfigTestUtils;
 | 
					import org.springframework.security.config.ConfigTestUtils;
 | 
				
			||||||
import org.springframework.security.config.util.InMemoryXmlApplicationContext;
 | 
					import org.springframework.security.config.util.InMemoryXmlApplicationContext;
 | 
				
			||||||
| 
						 | 
					@ -60,4 +67,39 @@ public class SecuredAnnotationDrivenBeanDefinitionParserTests {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        target.someAdminMethod();
 | 
					        target.someAdminMethod();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // SEC-1387
 | 
				
			||||||
 | 
					    @Test(expected=AuthenticationCredentialsNotFoundException.class)
 | 
				
			||||||
 | 
					    public void targetIsSerializableBeforeUse() throws Exception {
 | 
				
			||||||
 | 
					        BusinessService chompedTarget = (BusinessService) serializeAndDeserialize(target);
 | 
				
			||||||
 | 
					        chompedTarget.someAdminMethod();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Test(expected=AccessDeniedException.class)
 | 
				
			||||||
 | 
					    public void targetIsSerializableAfterUse() throws Exception {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            target.someAdminMethod();
 | 
				
			||||||
 | 
					        } catch (AuthenticationCredentialsNotFoundException expected) {
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("u","p","ROLE_A"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        BusinessService chompedTarget = (BusinessService) serializeAndDeserialize(target);
 | 
				
			||||||
 | 
					        chompedTarget.someAdminMethod();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private Object serializeAndDeserialize(Object o) throws IOException, ClassNotFoundException {
 | 
				
			||||||
 | 
					        ByteArrayOutputStream baos = new ByteArrayOutputStream();
 | 
				
			||||||
 | 
					        ObjectOutputStream oos = new ObjectOutputStream(baos);
 | 
				
			||||||
 | 
					        oos.writeObject(o);
 | 
				
			||||||
 | 
					        oos.flush();
 | 
				
			||||||
 | 
					        baos.flush();
 | 
				
			||||||
 | 
					        byte[] bytes = baos.toByteArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ByteArrayInputStream is = new ByteArrayInputStream(bytes);
 | 
				
			||||||
 | 
					        ObjectInputStream ois = new ObjectInputStream(is);
 | 
				
			||||||
 | 
					        Object o2 = ois.readObject();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return o2;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,6 +15,9 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package org.springframework.security.access.intercept.aopalliance;
 | 
					package org.springframework.security.access.intercept.aopalliance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					import java.io.ObjectInputStream;
 | 
				
			||||||
 | 
					import java.io.Serializable;
 | 
				
			||||||
import java.lang.reflect.AccessibleObject;
 | 
					import java.lang.reflect.AccessibleObject;
 | 
				
			||||||
import java.lang.reflect.Method;
 | 
					import java.lang.reflect.Method;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,7 +34,7 @@ import org.springframework.util.Assert;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Advisor driven by a {@link MethodSecurityMetadataSource}, used to exclude a {@link MethodSecurityInterceptor} from
 | 
					 * Advisor driven by a {@link MethodSecurityMetadataSource}, used to exclude a {@link MethodSecurityInterceptor} from
 | 
				
			||||||
 * public (ie non-secure) methods.
 | 
					 * public (non-secure) methods.
 | 
				
			||||||
 * <p>
 | 
					 * <p>
 | 
				
			||||||
 * Because the AOP framework caches advice calculations, this is normally faster than just letting the
 | 
					 * Because the AOP framework caches advice calculations, this is normally faster than just letting the
 | 
				
			||||||
 * <code>MethodSecurityInterceptor</code> run and find out itself that it has no work to do.
 | 
					 * <code>MethodSecurityInterceptor</code> run and find out itself that it has no work to do.
 | 
				
			||||||
| 
						 | 
					@ -44,23 +47,25 @@ import org.springframework.util.Assert;
 | 
				
			||||||
 * Based on Spring's TransactionAttributeSourceAdvisor.
 | 
					 * Based on Spring's TransactionAttributeSourceAdvisor.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @author Ben Alex
 | 
					 * @author Ben Alex
 | 
				
			||||||
 | 
					 * @author Luke Taylor
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class MethodSecurityMetadataSourceAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
 | 
					public class MethodSecurityMetadataSourceAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
 | 
				
			||||||
    //~ Instance fields ================================================================================================
 | 
					    //~ Instance fields ================================================================================================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private MethodSecurityMetadataSource attributeSource;
 | 
					    private transient MethodSecurityMetadataSource attributeSource;
 | 
				
			||||||
    private MethodSecurityInterceptor interceptor;
 | 
					    private transient MethodSecurityInterceptor interceptor;
 | 
				
			||||||
    private Pointcut pointcut = new MethodSecurityMetadataSourcePointcut();
 | 
					    private final Pointcut pointcut = new MethodSecurityMetadataSourcePointcut();
 | 
				
			||||||
    private BeanFactory beanFactory;
 | 
					    private BeanFactory beanFactory;
 | 
				
			||||||
    private String adviceBeanName;
 | 
					    private String adviceBeanName;
 | 
				
			||||||
    private final Object adviceMonitor = new Object();
 | 
					    private String metadataSourceBeanName;
 | 
				
			||||||
 | 
					    private final Serializable adviceMonitor = new Serializable() {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //~ Constructors ===================================================================================================
 | 
					    //~ Constructors ===================================================================================================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @deprecated use the decoupled approach instead
 | 
					     * @deprecated use the decoupled approach instead
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public MethodSecurityMetadataSourceAdvisor(MethodSecurityInterceptor advice) {
 | 
					    MethodSecurityMetadataSourceAdvisor(MethodSecurityInterceptor advice) {
 | 
				
			||||||
        Assert.notNull(advice.getSecurityMetadataSource(), "Cannot construct a MethodSecurityMetadataSourceAdvisor using a " +
 | 
					        Assert.notNull(advice.getSecurityMetadataSource(), "Cannot construct a MethodSecurityMetadataSourceAdvisor using a " +
 | 
				
			||||||
                "MethodSecurityInterceptor that has no SecurityMetadataSource configured");
 | 
					                "MethodSecurityInterceptor that has no SecurityMetadataSource configured");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -71,21 +76,22 @@ public class MethodSecurityMetadataSourceAdvisor extends AbstractPointcutAdvisor
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Alternative constructor for situations where we want the advisor decoupled from the advice. Instead the advice
 | 
					     * Alternative constructor for situations where we want the advisor decoupled from the advice. Instead the advice
 | 
				
			||||||
     * bean name should be set. This prevents eager instantiation of the interceptor
 | 
					     * bean name should be set. This prevents eager instantiation of the interceptor
 | 
				
			||||||
     * (and hence the AuthenticationManager). See SEC-773, for example.
 | 
					     * (and hence the AuthenticationManager). See SEC-773, for example. The metadataSourceBeanName is used rather than
 | 
				
			||||||
     * <p>
 | 
					     * a direct reference to support serialization via a bean factory lookup.
 | 
				
			||||||
     * This is essentially the approach taken by subclasses of Spring's {@code AbstractBeanFactoryPointcutAdvisor},
 | 
					 | 
				
			||||||
     * which this class should extend in future. The original hierarchy and constructor have been retained for backwards
 | 
					 | 
				
			||||||
     * compatibility.
 | 
					 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param adviceBeanName name of the MethodSecurityInterceptor bean
 | 
					     * @param adviceBeanName name of the MethodSecurityInterceptor bean
 | 
				
			||||||
     * @param attributeSource the attribute source (should be the same as the one used on the interceptor)
 | 
					     * @param attributeSource the SecurityMetadataSource (should be the same as the one used on the interceptor)
 | 
				
			||||||
 | 
					     * @param attributeSourceBeanName the bean name of the attributeSource (required for serialization)
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public MethodSecurityMetadataSourceAdvisor(String adviceBeanName, MethodSecurityMetadataSource attributeSource) {
 | 
					    public MethodSecurityMetadataSourceAdvisor(String adviceBeanName, MethodSecurityMetadataSource attributeSource,
 | 
				
			||||||
 | 
					            String attributeSourceBeanName) {
 | 
				
			||||||
        Assert.notNull(adviceBeanName, "The adviceBeanName cannot be null");
 | 
					        Assert.notNull(adviceBeanName, "The adviceBeanName cannot be null");
 | 
				
			||||||
        Assert.notNull(attributeSource, "The attributeSource cannot be null");
 | 
					        Assert.notNull(attributeSource, "The attributeSource cannot be null");
 | 
				
			||||||
 | 
					        Assert.notNull(attributeSourceBeanName, "The attributeSourceBeanName cannot be null");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.adviceBeanName = adviceBeanName;
 | 
					        this.adviceBeanName = adviceBeanName;
 | 
				
			||||||
        this.attributeSource = attributeSource;
 | 
					        this.attributeSource = attributeSource;
 | 
				
			||||||
 | 
					        this.metadataSourceBeanName = attributeSourceBeanName;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //~ Methods ========================================================================================================
 | 
					    //~ Methods ========================================================================================================
 | 
				
			||||||
| 
						 | 
					@ -99,8 +105,7 @@ public class MethodSecurityMetadataSourceAdvisor extends AbstractPointcutAdvisor
 | 
				
			||||||
            if (interceptor == null) {
 | 
					            if (interceptor == null) {
 | 
				
			||||||
                Assert.notNull(adviceBeanName, "'adviceBeanName' must be set for use with bean factory lookup.");
 | 
					                Assert.notNull(adviceBeanName, "'adviceBeanName' must be set for use with bean factory lookup.");
 | 
				
			||||||
                Assert.state(beanFactory != null, "BeanFactory must be set to resolve 'adviceBeanName'");
 | 
					                Assert.state(beanFactory != null, "BeanFactory must be set to resolve 'adviceBeanName'");
 | 
				
			||||||
                interceptor = (MethodSecurityInterceptor)
 | 
					                interceptor = beanFactory.getBean(this.adviceBeanName, MethodSecurityInterceptor.class);
 | 
				
			||||||
                        beanFactory.getBean(this.adviceBeanName, MethodSecurityInterceptor.class);
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            return interceptor;
 | 
					            return interceptor;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -110,9 +115,15 @@ public class MethodSecurityMetadataSourceAdvisor extends AbstractPointcutAdvisor
 | 
				
			||||||
        this.beanFactory = beanFactory;
 | 
					        this.beanFactory = beanFactory;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
 | 
				
			||||||
 | 
					        ois.defaultReadObject();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        attributeSource = beanFactory.getBean(metadataSourceBeanName, MethodSecurityMetadataSource.class);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //~ Inner Classes ==================================================================================================
 | 
					    //~ Inner Classes ==================================================================================================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    class MethodSecurityMetadataSourcePointcut extends StaticMethodMatcherPointcut {
 | 
					    class MethodSecurityMetadataSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
 | 
				
			||||||
        @SuppressWarnings("unchecked")
 | 
					        @SuppressWarnings("unchecked")
 | 
				
			||||||
        public boolean matches(Method m, Class targetClass) {
 | 
					        public boolean matches(Method m, Class targetClass) {
 | 
				
			||||||
            return attributeSource.getAttributes(m, targetClass) != null;
 | 
					            return attributeSource.getAttributes(m, targetClass) != null;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,19 +15,19 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package org.springframework.security.access.annotation;
 | 
					package org.springframework.security.access.annotation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.Serializable;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.annotation.security.RolesAllowed;
 | 
					 | 
				
			||||||
import javax.annotation.security.PermitAll;
 | 
					import javax.annotation.security.PermitAll;
 | 
				
			||||||
 | 
					import javax.annotation.security.RolesAllowed;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.springframework.security.access.annotation.Secured;
 | 
					 | 
				
			||||||
import org.springframework.security.access.prepost.PreAuthorize;
 | 
					import org.springframework.security.access.prepost.PreAuthorize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@Secured({"ROLE_USER"})
 | 
					@Secured({"ROLE_USER"})
 | 
				
			||||||
@PermitAll
 | 
					@PermitAll
 | 
				
			||||||
public interface BusinessService {
 | 
					public interface BusinessService extends Serializable {
 | 
				
			||||||
    //~ Methods ========================================================================================================
 | 
					    //~ Methods ========================================================================================================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Secured({"ROLE_ADMIN"})
 | 
					    @Secured({"ROLE_ADMIN"})
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue