diff --git a/core/src/main/java/org/springframework/security/access/intercept/AbstractSecurityInterceptor.java b/core/src/main/java/org/springframework/security/access/intercept/AbstractSecurityInterceptor.java index b263230c18..0deb198670 100644 --- a/core/src/main/java/org/springframework/security/access/intercept/AbstractSecurityInterceptor.java +++ b/core/src/main/java/org/springframework/security/access/intercept/AbstractSecurityInterceptor.java @@ -66,7 +66,7 @@ import org.springframework.util.Assert; *
AbstractSecurityInterceptor
- * is re-called and tidies up correctly.AbstractSecurityInterceptor via the
* {@link #afterInvocation(InterceptorStatusToken, Object)} method.RunAsManager replaced the Authentication object, return the
@@ -91,6 +91,7 @@ import org.springframework.util.Assert;
*
*
* @author Ben Alex
+ * @author Rob Winch
*/
public abstract class AbstractSecurityInterceptor implements InitializingBean, ApplicationEventPublisherAware,
MessageSourceAware {
@@ -242,6 +243,23 @@ public abstract class AbstractSecurityInterceptor implements InitializingBean, A
}
}
+ /**
+ * Cleans up the work of the AbstractSecurityInterceptor after the secure object invocation has been
+ * completed. This method should be invoked after the secure object invocation and before afterInvocation regardless
+ * of the secure object invocation returning successfully (i.e. it should be done in a finally block).
+ *
+ * @param token as returned by the {@link #beforeInvocation(Object)} method
+ */
+ protected void finallyInvocation(InterceptorStatusToken token) {
+ if (token != null && token.isContextHolderRefreshRequired()) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Reverting to original Authentication: " + token.getSecurityContext().getAuthentication());
+ }
+
+ SecurityContextHolder.setContext(token.getSecurityContext());
+ }
+ }
+
/**
* Completes the work of the AbstractSecurityInterceptor after the secure object invocation has been
* completed.
@@ -256,13 +274,7 @@ public abstract class AbstractSecurityInterceptor implements InitializingBean, A
return returnedObject;
}
- if (token.isContextHolderRefreshRequired()) {
- if (logger.isDebugEnabled()) {
- logger.debug("Reverting to original Authentication: " + token.getSecurityContext().getAuthentication());
- }
-
- SecurityContextHolder.setContext(token.getSecurityContext());
- }
+ finallyInvocation(token); // continue to clean in this method for passivity
if (afterInvocationManager != null) {
// Attempt after invocation handling
diff --git a/core/src/main/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityInterceptor.java b/core/src/main/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityInterceptor.java
index a18a265822..f7eac2621d 100644
--- a/core/src/main/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityInterceptor.java
+++ b/core/src/main/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityInterceptor.java
@@ -34,6 +34,7 @@ import org.aopalliance.intercept.MethodInvocation;
* Refer to {@link AbstractSecurityInterceptor} for details on the workflow.
*
* @author Ben Alex
+ * @author Rob Winch
*/
public class MethodSecurityInterceptor extends AbstractSecurityInterceptor implements MethodInterceptor {
//~ Instance fields ================================================================================================
@@ -58,8 +59,12 @@ public class MethodSecurityInterceptor extends AbstractSecurityInterceptor imple
public Object invoke(MethodInvocation mi) throws Throwable {
InterceptorStatusToken token = super.beforeInvocation(mi);
- Object result = mi.proceed();
-
+ Object result;
+ try {
+ result = mi.proceed();
+ } finally {
+ super.finallyInvocation(token);
+ }
return super.afterInvocation(token, result);
}
diff --git a/core/src/main/java/org/springframework/security/access/intercept/aspectj/AspectJMethodSecurityInterceptor.java b/core/src/main/java/org/springframework/security/access/intercept/aspectj/AspectJMethodSecurityInterceptor.java
index 3ea0231802..a6bb9509d2 100644
--- a/core/src/main/java/org/springframework/security/access/intercept/aspectj/AspectJMethodSecurityInterceptor.java
+++ b/core/src/main/java/org/springframework/security/access/intercept/aspectj/AspectJMethodSecurityInterceptor.java
@@ -12,6 +12,7 @@ import org.springframework.security.access.intercept.aopalliance.MethodSecurityI
* Alternatively you can use one of the pre-defined aspects from the aspects module.
*
* @author Luke Taylor
+ * @author Rob Winch
* @since 3.0.3
*/
public final class AspectJMethodSecurityInterceptor extends MethodSecurityInterceptor {
@@ -39,7 +40,12 @@ public final class AspectJMethodSecurityInterceptor extends MethodSecurityInterc
public Object invoke(JoinPoint jp, AspectJCallback advisorProceed) {
InterceptorStatusToken token = super.beforeInvocation(new MethodInvocationAdapter(jp));
- Object result = advisorProceed.proceedWithObject();
+ Object result;
+ try {
+ result = advisorProceed.proceedWithObject();
+ }finally {
+ super.finallyInvocation(token);
+ }
return super.afterInvocation(token, result);
}
diff --git a/core/src/test/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityInterceptorTests.java b/core/src/test/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityInterceptorTests.java
index 9f5f54b784..87006a9bb9 100644
--- a/core/src/test/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityInterceptorTests.java
+++ b/core/src/test/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityInterceptorTests.java
@@ -45,11 +45,11 @@ import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import java.util.*;
-
/**
* Tests {@link MethodSecurityInterceptor}.
*
* @author Ben Alex
+ * @author Rob Winch
*/
@SuppressWarnings("unchecked")
public class MethodSecurityInterceptorTests {
@@ -267,6 +267,31 @@ public class MethodSecurityInterceptorTests {
assertSame(token, SecurityContextHolder.getContext().getAuthentication());
}
+ // SEC-1967
+ @Test
+ public void runAsReplacementCleansAfterException() throws Exception {
+ createTarget(true);
+ when(realTarget.makeUpperCase(anyString())).thenThrow(new RuntimeException());
+ SecurityContext ctx = SecurityContextHolder.getContext();
+ ctx.setAuthentication(token);
+ token.setAuthenticated(true);
+ final RunAsManager runAs = mock(RunAsManager.class);
+ final RunAsUserToken runAsToken =
+ new RunAsUserToken("key", "someone", "creds", token.getAuthorities(), TestingAuthenticationToken.class);
+ interceptor.setRunAsManager(runAs);
+ mdsReturnsUserRole();
+ when(runAs.buildRunAs(eq(token), any(MethodInvocation.class), any(List.class))).thenReturn(runAsToken);
+
+ try {
+ advisedTarget.makeUpperCase("hello");
+ fail("Expected Exception");
+ }catch(RuntimeException success) {}
+
+ // Check we've changed back
+ assertSame(ctx, SecurityContextHolder.getContext());
+ assertSame(token, SecurityContextHolder.getContext().getAuthentication());
+ }
+
@Test(expected=AuthenticationCredentialsNotFoundException.class)
public void emptySecurityContextIsRejected() throws Exception {
mdsReturnsUserRole();
diff --git a/core/src/test/java/org/springframework/security/access/intercept/aspectj/AspectJMethodSecurityInterceptorTests.java b/core/src/test/java/org/springframework/security/access/intercept/aspectj/AspectJMethodSecurityInterceptorTests.java
index c69fab8731..92ba83abd5 100644
--- a/core/src/test/java/org/springframework/security/access/intercept/aspectj/AspectJMethodSecurityInterceptorTests.java
+++ b/core/src/test/java/org/springframework/security/access/intercept/aspectj/AspectJMethodSecurityInterceptorTests.java
@@ -34,15 +34,19 @@ import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.access.intercept.AfterInvocationManager;
+import org.springframework.security.access.intercept.RunAsManager;
+import org.springframework.security.access.intercept.RunAsUserToken;
import org.springframework.security.access.method.MethodSecurityMetadataSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.ClassUtils;
import java.lang.reflect.Method;
import java.util.Collection;
+import java.util.List;
/**
@@ -50,6 +54,7 @@ import java.util.Collection;
*
* @author Ben Alex
* @author Luke Taylor
+ * @author Rob Winch
*/
@SuppressWarnings("deprecation")
public class AspectJMethodSecurityInterceptorTests {
@@ -151,4 +156,51 @@ public class AspectJMethodSecurityInterceptorTests {
verifyZeroInteractions(aim);
}
+ // SEC-1967
+ @Test
+ @SuppressWarnings("unchecked")
+ public void invokeWithAspectJCallbackRunAsReplacementCleansAfterException() throws Exception {
+ SecurityContext ctx = SecurityContextHolder.getContext();
+ ctx.setAuthentication(token);
+ token.setAuthenticated(true);
+ final RunAsManager runAs = mock(RunAsManager.class);
+ final RunAsUserToken runAsToken =
+ new RunAsUserToken("key", "someone", "creds", token.getAuthorities(), TestingAuthenticationToken.class);
+ interceptor.setRunAsManager(runAs);
+ when(runAs.buildRunAs(eq(token), any(MethodInvocation.class), any(List.class))).thenReturn(runAsToken);
+ when(aspectJCallback.proceedWithObject()).thenThrow(new RuntimeException());
+
+ try {
+ interceptor.invoke(joinPoint, aspectJCallback);
+ fail("Expected Exception");
+ }catch(RuntimeException success) {}
+
+ // Check we've changed back
+ assertSame(ctx, SecurityContextHolder.getContext());
+ assertSame(token, SecurityContextHolder.getContext().getAuthentication());
+ }
+
+ // SEC-1967
+ @Test
+ @SuppressWarnings("unchecked")
+ public void invokeRunAsReplacementCleansAfterException() throws Throwable {
+ SecurityContext ctx = SecurityContextHolder.getContext();
+ ctx.setAuthentication(token);
+ token.setAuthenticated(true);
+ final RunAsManager runAs = mock(RunAsManager.class);
+ final RunAsUserToken runAsToken =
+ new RunAsUserToken("key", "someone", "creds", token.getAuthorities(), TestingAuthenticationToken.class);
+ interceptor.setRunAsManager(runAs);
+ when(runAs.buildRunAs(eq(token), any(MethodInvocation.class), any(List.class))).thenReturn(runAsToken);
+ when(joinPoint.proceed()).thenThrow(new RuntimeException());
+
+ try {
+ interceptor.invoke(joinPoint);
+ fail("Expected Exception");
+ }catch(RuntimeException success) {}
+
+ // Check we've changed back
+ assertSame(ctx, SecurityContextHolder.getContext());
+ assertSame(token, SecurityContextHolder.getContext().getAuthentication());
+ }
}
diff --git a/web/src/main/java/org/springframework/security/web/access/intercept/FilterSecurityInterceptor.java b/web/src/main/java/org/springframework/security/web/access/intercept/FilterSecurityInterceptor.java
index 55dd0ce198..445796749f 100644
--- a/web/src/main/java/org/springframework/security/web/access/intercept/FilterSecurityInterceptor.java
+++ b/web/src/main/java/org/springframework/security/web/access/intercept/FilterSecurityInterceptor.java
@@ -39,6 +39,7 @@ import org.springframework.security.web.FilterInvocation;
* Refer to {@link AbstractSecurityInterceptor} for details on the workflow.
*
* @author Ben Alex
+ * @author Rob Winch
*/
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
//~ Static fields/initializers =====================================================================================
@@ -113,7 +114,11 @@ public class FilterSecurityInterceptor extends AbstractSecurityInterceptor imple
InterceptorStatusToken token = super.beforeInvocation(fi);
- fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
+ try {
+ fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
+ } finally {
+ super.finallyInvocation(token);
+ }
super.afterInvocation(token, null);
}
diff --git a/web/src/test/java/org/springframework/security/web/access/intercept/FilterSecurityInterceptorTests.java b/web/src/test/java/org/springframework/security/web/access/intercept/FilterSecurityInterceptorTests.java
index 9b7effb3ca..8d7a3aeeaa 100644
--- a/web/src/test/java/org/springframework/security/web/access/intercept/FilterSecurityInterceptorTests.java
+++ b/web/src/test/java/org/springframework/security/web/access/intercept/FilterSecurityInterceptorTests.java
@@ -15,7 +15,7 @@
package org.springframework.security.web.access.intercept;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.*;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
@@ -28,9 +28,11 @@ import org.springframework.security.access.SecurityConfig;
import org.springframework.security.access.event.AuthorizedEvent;
import org.springframework.security.access.intercept.AfterInvocationManager;
import org.springframework.security.access.intercept.RunAsManager;
+import org.springframework.security.access.intercept.RunAsUserToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.FilterInvocation;
@@ -44,6 +46,7 @@ import javax.servlet.http.HttpServletResponse;
*
* @author Ben Alex
* @author Luke Taylor
+ * @author Rob Winch
*/
public class FilterSecurityInterceptorTests {
private AuthenticationManager am;
@@ -132,6 +135,39 @@ public class FilterSecurityInterceptorTests {
verifyZeroInteractions(aim);
}
+ // SEC-1967
+ @Test
+ @SuppressWarnings("unchecked")
+ public void finallyInvocationIsInvokedIfExceptionThrown() throws Exception {
+ SecurityContext ctx = SecurityContextHolder.getContext();
+ Authentication token = new TestingAuthenticationToken("Test", "Password", "NOT_USED");
+ token.setAuthenticated(true);
+ ctx.setAuthentication(token);
+
+ RunAsManager runAsManager = mock(RunAsManager.class);
+ when(runAsManager.buildRunAs(eq(token), any(), anyCollection())).thenReturn(new RunAsUserToken("key", "someone", "creds", token.getAuthorities(), token.getClass()));
+ interceptor.setRunAsManager(runAsManager);
+
+ FilterInvocation fi = createinvocation();
+ FilterChain chain = fi.getChain();
+
+ doThrow(new RuntimeException()).when(chain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
+ when(ods.getAttributes(fi)).thenReturn(SecurityConfig.createList("MOCK_OK"));
+
+ AfterInvocationManager aim = mock(AfterInvocationManager.class);
+ interceptor.setAfterInvocationManager(aim);
+
+ try {
+ interceptor.invoke(fi);
+ fail("Expected exception");
+ } catch (RuntimeException expected) {
+ }
+
+ // Check we've changed back
+ assertSame(ctx, SecurityContextHolder.getContext());
+ assertSame(token, SecurityContextHolder.getContext().getAuthentication());
+ }
+
private FilterInvocation createinvocation() {
MockHttpServletResponse response = new MockHttpServletResponse();
MockHttpServletRequest request = new MockHttpServletRequest();