diff --git a/core/src/main/java/org/springframework/security/remoting/rmi/ContextPropagatingRemoteInvocation.java b/core/src/main/java/org/springframework/security/remoting/rmi/ContextPropagatingRemoteInvocation.java index 3822e4547f..63b645d258 100644 --- a/core/src/main/java/org/springframework/security/remoting/rmi/ContextPropagatingRemoteInvocation.java +++ b/core/src/main/java/org/springframework/security/remoting/rmi/ContextPropagatingRemoteInvocation.java @@ -15,32 +15,30 @@ package org.springframework.security.remoting.rmi; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; - import org.aopalliance.intercept.MethodInvocation; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.remoting.support.RemoteInvocation; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import java.lang.reflect.InvocationTargetException; /** - * The actual RemoteInvocation that is passed from the client to the server, which contains the - * contents of {@link SecurityContextHolder}, being a {@link SecurityContext} object. + * The actual {@code RemoteInvocation} that is passed from the client to the server. *

- * When constructed on the client via {@link ContextPropagatingRemoteInvocationFactory}, the contents of the - * SecurityContext are stored inside the object. The object is then passed to the server that is - * processing the remote invocation. Upon the server invoking the remote invocation, it will retrieve the passed - * contents of the SecurityContextHolder and set them on the server-side - * SecurityContextHolder while the target object is invoked. When the target invocation has been - * completed, the security context will be cleared using a call to {@link SecurityContextHolder#clearContext()}. + * The principal and credentials information will be extracted from the current + * security context and passed to the server as part of the invocation object. + *

+ * To avoid potential serialization-based attacks, this implementation interprets the values as {@code String}s + * and creates a {@code UsernamePasswordAuthenticationToken} on the server side to hold them. If a different + * token type is required you can override the {@code createAuthenticationRequest} method. * * @author James Monaghan * @author Ben Alex + * @author Luke Taylor */ public class ContextPropagatingRemoteInvocation extends RemoteInvocation { //~ Static fields/initializers ===================================================================================== @@ -49,34 +47,40 @@ public class ContextPropagatingRemoteInvocation extends RemoteInvocation { //~ Instance fields ================================================================================================ - private SecurityContext securityContext; + private final String principal; + private final String credentials; //~ Constructors =================================================================================================== /** - * Constructs the object, storing the value of the client-side - * SecurityContextHolder inside the object. + * Constructs the object, storing the principal and credentials extracted from the client-side + * security context. * * @param methodInvocation the method to invoke */ public ContextPropagatingRemoteInvocation(MethodInvocation methodInvocation) { super(methodInvocation); - securityContext = SecurityContextHolder.getContext(); + Authentication currentUser = SecurityContextHolder.getContext().getAuthentication(); + + if (currentUser != null) { + principal = currentUser.getPrincipal().toString(); + credentials = currentUser.getCredentials().toString(); + } else { + principal = credentials = null; + } if (logger.isDebugEnabled()) { - logger.debug("RemoteInvocation now has SecurityContext: " + securityContext); + logger.debug("RemoteInvocation now has principal: " + principal); } } //~ Methods ======================================================================================================== /** - * Invoked on the server-side as described in the class JavaDocs. + * Invoked on the server-side. *

- * Invocations will always have their {@link org.springframework.security.core.Authentication#setAuthenticated(boolean)} - * set to false, which is guaranteed to always be accepted by Authentication - * implementations. This ensures that even remotely authenticated Authentications will be untrusted by - * the server-side, which is an appropriate security measure. + * The transmitted principal and credentials will be used to create an unauthenticated {@code Authentication} + * instance for processing by the {@code AuthenticationManager}. * * @param targetObject the target object to apply the invocation to * @@ -88,15 +92,15 @@ public class ContextPropagatingRemoteInvocation extends RemoteInvocation { */ public Object invoke(Object targetObject) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - SecurityContextHolder.setContext(securityContext); - if ((SecurityContextHolder.getContext() != null) - && (SecurityContextHolder.getContext().getAuthentication() != null)) { - SecurityContextHolder.getContext().getAuthentication().setAuthenticated(false); - } + if (principal != null) { + Authentication request = createAuthenticationRequest(principal, credentials); + request.setAuthenticated(false); + SecurityContextHolder.getContext().setAuthentication(request); - if (logger.isDebugEnabled()) { - logger.debug("Set SecurityContextHolder to contain: " + securityContext); + if (logger.isDebugEnabled()) { + logger.debug("Set SecurityContextHolder to contain: " + request); + } } try { @@ -109,4 +113,11 @@ public class ContextPropagatingRemoteInvocation extends RemoteInvocation { } } } + + /** + * Creates the server-side authentication request object. + */ + protected Authentication createAuthenticationRequest(String principal, String credentials) { + return new UsernamePasswordAuthenticationToken(principal, credentials); + } } diff --git a/core/src/test/java/org/springframework/security/remoting/rmi/ContextPropagatingRemoteInvocationTests.java b/core/src/test/java/org/springframework/security/remoting/rmi/ContextPropagatingRemoteInvocationTests.java index 33c3ec8e02..95dfd7c698 100644 --- a/core/src/test/java/org/springframework/security/remoting/rmi/ContextPropagatingRemoteInvocationTests.java +++ b/core/src/test/java/org/springframework/security/remoting/rmi/ContextPropagatingRemoteInvocationTests.java @@ -16,20 +16,13 @@ package org.springframework.security.remoting.rmi; import junit.framework.TestCase; - +import org.aopalliance.intercept.MethodInvocation; import org.springframework.security.TargetObject; - import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; - -import org.springframework.security.remoting.rmi.ContextPropagatingRemoteInvocation; -import org.springframework.security.remoting.rmi.ContextPropagatingRemoteInvocationFactory; - import org.springframework.security.util.SimpleMethodInvocation; -import org.aopalliance.intercept.MethodInvocation; - import java.lang.reflect.Method; @@ -57,8 +50,7 @@ public class ContextPropagatingRemoteInvocationTests extends TestCase { return (ContextPropagatingRemoteInvocation) factory.createRemoteInvocation(mi); } - public void testContextIsResetEvenIfExceptionOccurs() - throws Exception { + public void testContextIsResetEvenIfExceptionOccurs() throws Exception { // Setup client-side context Authentication clientSideAuthentication = new UsernamePasswordAuthenticationToken("rod", "koala"); SecurityContextHolder.getContext().setAuthentication(clientSideAuthentication); @@ -96,10 +88,10 @@ public class ContextPropagatingRemoteInvocationTests extends TestCase { } public void testNullContextHolderDoesNotCauseInvocationProblems() throws Exception { - SecurityContextHolder.getContext().setAuthentication(null); // just to be explicit + SecurityContextHolder.clearContext(); // just to be explicit ContextPropagatingRemoteInvocation remoteInvocation = getRemoteInvocation(); - SecurityContextHolder.getContext().setAuthentication(null); // unnecessary, but for explicitness + SecurityContextHolder.clearContext(); // unnecessary, but for explicitness assertEquals("some_string Authentication empty", remoteInvocation.invoke(new TargetObject())); }