SEC-1750: Make sure RunAs replacement is constrained to the SecurityContext of the current thread.

This commit is contained in:
Luke Taylor 2011-05-20 20:42:54 +01:00
parent 22b7c9b905
commit 76dc21469e
4 changed files with 34 additions and 18 deletions

View File

@ -26,8 +26,10 @@ import org.springframework.security.ConfigAttribute;
import org.springframework.security.ConfigAttributeDefinition; import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.RunAsManager; import org.springframework.security.RunAsManager;
import org.springframework.security.context.SecurityContext;
import org.springframework.security.context.SecurityContextHolder; import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.context.SecurityContextImpl;
import org.springframework.security.event.authorization.AuthenticationCredentialsNotFoundEvent; import org.springframework.security.event.authorization.AuthenticationCredentialsNotFoundEvent;
import org.springframework.security.event.authorization.AuthorizationFailureEvent; import org.springframework.security.event.authorization.AuthorizationFailureEvent;
import org.springframework.security.event.authorization.AuthorizedEvent; import org.springframework.security.event.authorization.AuthorizedEvent;
@ -110,7 +112,7 @@ public abstract class AbstractSecurityInterceptor implements InitializingBean, A
protected static final Log logger = LogFactory.getLog(AbstractSecurityInterceptor.class); protected static final Log logger = LogFactory.getLog(AbstractSecurityInterceptor.class);
//~ Instance fields ================================================================================================ //~ Instance fields ================================================================================================
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private ApplicationEventPublisher eventPublisher; private ApplicationEventPublisher eventPublisher;
private AccessDecisionManager accessDecisionManager; private AccessDecisionManager accessDecisionManager;
@ -140,21 +142,22 @@ public abstract class AbstractSecurityInterceptor implements InitializingBean, A
if (token.isContextHolderRefreshRequired()) { if (token.isContextHolderRefreshRequired()) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Reverting to original Authentication: " + token.getAuthentication().toString()); logger.debug("Reverting to original Authentication: " + token.getSecurityContext().getAuthentication());
} }
SecurityContextHolder.getContext().setAuthentication(token.getAuthentication()); SecurityContextHolder.setContext(token.getSecurityContext());
} }
if (afterInvocationManager != null) { if (afterInvocationManager != null) {
// Attempt after invocation handling // Attempt after invocation handling
try { try {
returnedObject = afterInvocationManager.decide(token.getAuthentication(), token.getSecureObject(), returnedObject = afterInvocationManager.decide(token.getSecurityContext().getAuthentication(),
token.getSecureObject(),
token.getAttr(), returnedObject); token.getAttr(), returnedObject);
} }
catch (AccessDeniedException accessDeniedException) { catch (AccessDeniedException accessDeniedException) {
AuthorizationFailureEvent event = new AuthorizationFailureEvent(token.getSecureObject(), token AuthorizationFailureEvent event = new AuthorizationFailureEvent(token.getSecureObject(), token
.getAttr(), token.getAuthentication(), accessDeniedException); .getAttr(), token.getSecurityContext().getAuthentication(), accessDeniedException);
publishEvent(event); publishEvent(event);
throw accessDeniedException; throw accessDeniedException;
@ -285,16 +288,20 @@ public abstract class AbstractSecurityInterceptor implements InitializingBean, A
} }
// no further work post-invocation // no further work post-invocation
return new InterceptorStatusToken(authenticated, false, attr, object); return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attr, object);
} else { } else {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("Switching to RunAs Authentication: " + runAs); logger.debug("Switching to RunAs Authentication: " + runAs);
} }
SecurityContextHolder.getContext().setAuthentication(runAs); SecurityContext originalContext = SecurityContextHolder.getContext();
SecurityContext runAsContext = new SecurityContextImpl();
runAsContext.setAuthentication(runAs);
// revert to token.Authenticated post-invocation SecurityContextHolder.setContext(runAsContext);
return new InterceptorStatusToken(authenticated, true, attr, object);
// revert to original context post-invocation
return new InterceptorStatusToken(originalContext, true, attr, object);
} }
} }

View File

@ -17,6 +17,7 @@ package org.springframework.security.intercept;
import org.springframework.security.Authentication; import org.springframework.security.Authentication;
import org.springframework.security.ConfigAttributeDefinition; import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.context.SecurityContext;
/** /**
@ -32,16 +33,16 @@ import org.springframework.security.ConfigAttributeDefinition;
public class InterceptorStatusToken { public class InterceptorStatusToken {
//~ Instance fields ================================================================================================ //~ Instance fields ================================================================================================
private Authentication authentication; private SecurityContext context;
private ConfigAttributeDefinition attr; private ConfigAttributeDefinition attr;
private Object secureObject; private Object secureObject;
private boolean contextHolderRefreshRequired; private boolean contextHolderRefreshRequired;
//~ Constructors =================================================================================================== //~ Constructors ===================================================================================================
public InterceptorStatusToken(Authentication authentication, boolean contextHolderRefreshRequired, public InterceptorStatusToken(SecurityContext context, boolean contextHolderRefreshRequired,
ConfigAttributeDefinition attr, Object secureObject) { ConfigAttributeDefinition attr, Object secureObject) {
this.authentication = authentication; this.context = context;
this.contextHolderRefreshRequired = contextHolderRefreshRequired; this.contextHolderRefreshRequired = contextHolderRefreshRequired;
this.attr = attr; this.attr = attr;
this.secureObject = secureObject; this.secureObject = secureObject;
@ -53,8 +54,8 @@ public class InterceptorStatusToken {
return attr; return attr;
} }
public Authentication getAuthentication() { public SecurityContext getSecurityContext() {
return authentication; return context;
} }
public Object getSecureObject() { public Object getSecureObject() {

View File

@ -18,6 +18,8 @@ package org.springframework.security.intercept;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.springframework.security.ConfigAttributeDefinition; import org.springframework.security.ConfigAttributeDefinition;
import org.springframework.security.context.SecurityContext;
import org.springframework.security.context.SecurityContextImpl;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken; import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.util.SimpleMethodInvocation; import org.springframework.security.util.SimpleMethodInvocation;
@ -58,12 +60,13 @@ public class InterceptorStatusTokenTests extends TestCase {
ConfigAttributeDefinition attr = new ConfigAttributeDefinition("FOO"); ConfigAttributeDefinition attr = new ConfigAttributeDefinition("FOO");
MethodInvocation mi = new SimpleMethodInvocation(); MethodInvocation mi = new SimpleMethodInvocation();
InterceptorStatusToken token = new InterceptorStatusToken(new UsernamePasswordAuthenticationToken("rod", SecurityContext ctx = new SecurityContextImpl();
"koala"), true, attr, mi);
InterceptorStatusToken token = new InterceptorStatusToken(ctx, true, attr, mi);
assertTrue(token.isContextHolderRefreshRequired()); assertTrue(token.isContextHolderRefreshRequired());
assertEquals(attr, token.getAttr()); assertEquals(attr, token.getAttr());
assertEquals(mi, token.getSecureObject()); assertEquals(mi, token.getSecureObject());
assertEquals("rod", token.getAuthentication().getPrincipal()); assertSame(ctx, token.getSecurityContext());
} }
} }

View File

@ -34,6 +34,7 @@ import org.springframework.security.MockAuthenticationManager;
import org.springframework.security.MockRunAsManager; import org.springframework.security.MockRunAsManager;
import org.springframework.security.RunAsManager; import org.springframework.security.RunAsManager;
import org.springframework.security.context.SecurityContext;
import org.springframework.security.context.SecurityContextHolder; import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.intercept.method.MethodDefinitionSource; import org.springframework.security.intercept.method.MethodDefinitionSource;
@ -166,11 +167,15 @@ public class MethodSecurityInterceptorTests extends TestCase {
public void testMethodCallWithRunAsReplacement() throws Exception { public void testMethodCallWithRunAsReplacement() throws Exception {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("Test", "Password", UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("Test", "Password",
new GrantedAuthority[] {new GrantedAuthorityImpl("MOCK_UPPER")}); new GrantedAuthority[] {new GrantedAuthorityImpl("MOCK_UPPER")});
SecurityContextHolder.getContext().setAuthentication(token); SecurityContext ctx = SecurityContextHolder.getContext();
ctx.setAuthentication(token);
ITargetObject target = makeInterceptedTarget(); ITargetObject target = makeInterceptedTarget();
String result = target.makeUpperCase("hello"); String result = target.makeUpperCase("hello");
assertEquals("HELLO org.springframework.security.MockRunAsAuthenticationToken true", result); assertEquals("HELLO org.springframework.security.MockRunAsAuthenticationToken true", result);
// Check reset afterwards
assertSame(ctx, SecurityContextHolder.getContext());
assertSame(token, SecurityContextHolder.getContext().getAuthentication());
} }
public void testMethodCallWithoutRunAsReplacement() public void testMethodCallWithoutRunAsReplacement()