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:
Luke Taylor 2010-02-19 00:38:24 +00:00
parent 14ae36ac3b
commit 10dc72b017
4 changed files with 73 additions and 19 deletions

View File

@ -324,6 +324,7 @@ public class GlobalMethodSecurityBeanDefinitionParser implements BeanDefinitionP
advisor.setSource(source);
advisor.getConstructorArgumentValues().addGenericArgumentValue(interceptor.getBeanName());
advisor.getConstructorArgumentValues().addGenericArgumentValue(metadataSource);
advisor.getConstructorArgumentValues().addGenericArgumentValue(metadataSource.getBeanName());
parserContext.getRegistry().registerBeanDefinition(BeanIds.METHOD_SECURITY_METADATA_SOURCE_ADVISOR, advisor);
}

View File

@ -1,11 +1,18 @@
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.Before;
import org.junit.Test;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.annotation.BusinessService;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.ConfigTestUtils;
import org.springframework.security.config.util.InMemoryXmlApplicationContext;
@ -60,4 +67,39 @@ public class SecuredAnnotationDrivenBeanDefinitionParserTests {
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;
}
}

View File

@ -15,6 +15,9 @@
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.Method;
@ -31,7 +34,7 @@ import org.springframework.util.Assert;
/**
* Advisor driven by a {@link MethodSecurityMetadataSource}, used to exclude a {@link MethodSecurityInterceptor} from
* public (ie non-secure) methods.
* public (non-secure) methods.
* <p>
* 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.
@ -44,23 +47,25 @@ import org.springframework.util.Assert;
* Based on Spring's TransactionAttributeSourceAdvisor.
*
* @author Ben Alex
* @author Luke Taylor
*/
public class MethodSecurityMetadataSourceAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
//~ Instance fields ================================================================================================
private MethodSecurityMetadataSource attributeSource;
private MethodSecurityInterceptor interceptor;
private Pointcut pointcut = new MethodSecurityMetadataSourcePointcut();
private transient MethodSecurityMetadataSource attributeSource;
private transient MethodSecurityInterceptor interceptor;
private final Pointcut pointcut = new MethodSecurityMetadataSourcePointcut();
private BeanFactory beanFactory;
private String adviceBeanName;
private final Object adviceMonitor = new Object();
private String metadataSourceBeanName;
private final Serializable adviceMonitor = new Serializable() {};
//~ Constructors ===================================================================================================
/**
* @deprecated use the decoupled approach instead
*/
public MethodSecurityMetadataSourceAdvisor(MethodSecurityInterceptor advice) {
MethodSecurityMetadataSourceAdvisor(MethodSecurityInterceptor advice) {
Assert.notNull(advice.getSecurityMetadataSource(), "Cannot construct a MethodSecurityMetadataSourceAdvisor using a " +
"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
* bean name should be set. This prevents eager instantiation of the interceptor
* (and hence the AuthenticationManager). See SEC-773, for example.
* <p>
* 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.
* (and hence the AuthenticationManager). See SEC-773, for example. The metadataSourceBeanName is used rather than
* a direct reference to support serialization via a bean factory lookup.
*
* @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(attributeSource, "The attributeSource cannot be null");
Assert.notNull(attributeSourceBeanName, "The attributeSourceBeanName cannot be null");
this.adviceBeanName = adviceBeanName;
this.attributeSource = attributeSource;
this.metadataSourceBeanName = attributeSourceBeanName;
}
//~ Methods ========================================================================================================
@ -99,8 +105,7 @@ public class MethodSecurityMetadataSourceAdvisor extends AbstractPointcutAdvisor
if (interceptor == null) {
Assert.notNull(adviceBeanName, "'adviceBeanName' must be set for use with bean factory lookup.");
Assert.state(beanFactory != null, "BeanFactory must be set to resolve 'adviceBeanName'");
interceptor = (MethodSecurityInterceptor)
beanFactory.getBean(this.adviceBeanName, MethodSecurityInterceptor.class);
interceptor = beanFactory.getBean(this.adviceBeanName, MethodSecurityInterceptor.class);
}
return interceptor;
}
@ -110,9 +115,15 @@ public class MethodSecurityMetadataSourceAdvisor extends AbstractPointcutAdvisor
this.beanFactory = beanFactory;
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
attributeSource = beanFactory.getBean(metadataSourceBeanName, MethodSecurityMetadataSource.class);
}
//~ Inner Classes ==================================================================================================
class MethodSecurityMetadataSourcePointcut extends StaticMethodMatcherPointcut {
class MethodSecurityMetadataSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
@SuppressWarnings("unchecked")
public boolean matches(Method m, Class targetClass) {
return attributeSource.getAttributes(m, targetClass) != null;

View File

@ -15,19 +15,19 @@
package org.springframework.security.access.annotation;
import java.io.Serializable;
import java.util.List;
import javax.annotation.security.RolesAllowed;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
/**
*/
@Secured({"ROLE_USER"})
@PermitAll
public interface BusinessService {
public interface BusinessService extends Serializable {
//~ Methods ========================================================================================================
@Secured({"ROLE_ADMIN"})