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 Authentication
s 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()));
}