SEC-1262: Added new (replacement) AspectJ interceptor which wraps the JoinPoint in a MethodInvocation adapter to provide compatibility with classes which only support MethodInvocation instances.
Also deprecated the existing AspectJ interceptors. This will also allow future simplification of the AbstractMethodSecurityMetadataSource, as it no longer needs to support JoinPoints.
This commit is contained in:
+2
-1
@@ -6,8 +6,9 @@ package org.springframework.security.access.intercept.aspectj;
|
||||
* AspectJ processing to continue.
|
||||
*
|
||||
* @author Mike Wiesner
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
@Deprecated
|
||||
public interface AspectJAnnotationCallback {
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
|
||||
+2
@@ -11,7 +11,9 @@ import org.aspectj.lang.JoinPoint;
|
||||
* AspectJ interceptor that supports @Aspect notation.
|
||||
*
|
||||
* @author Mike Wiesner
|
||||
* @deprecated Use AspectJMethodSecurityInterceptor instead
|
||||
*/
|
||||
@Deprecated
|
||||
public class AspectJAnnotationSecurityInterceptor extends AbstractSecurityInterceptor {
|
||||
//~ Instance fields ================================================================================================
|
||||
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package org.springframework.security.access.intercept.aspectj;
|
||||
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.springframework.security.access.intercept.InterceptorStatusToken;
|
||||
import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor;
|
||||
|
||||
/**
|
||||
* AspectJ {@code JoinPoint} security interceptor which wraps the {@code JoinPoint} in a {@code MethodInvocation}
|
||||
* adapter to make it compatible with security infrastructure classes which only support {@code MethodInvocation}s.
|
||||
* <p>
|
||||
* One of the {@code invoke} methods should be called from the {@code around()} advice in your aspect.
|
||||
* Alternatively you can use one of the pre-defined aspects from the aspects module.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @since 3.0.3
|
||||
*/
|
||||
public final class AspectJMethodSecurityInterceptor extends MethodSecurityInterceptor {
|
||||
|
||||
/**
|
||||
* Method that is suitable for user with @Aspect notation.
|
||||
*
|
||||
* @param jp The AspectJ joint point being invoked which requires a security decision
|
||||
* @return The returned value from the method invocation
|
||||
* @throws Throwable if the invocation throws one
|
||||
*/
|
||||
public Object invoke(JoinPoint jp) throws Throwable {
|
||||
return super.invoke(new MethodInvocationAdapter(jp));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that is suitable for user with traditional AspectJ-code aspects.
|
||||
*
|
||||
* @param jp The AspectJ joint point being invoked which requires a security decision
|
||||
* @param advisorProceed the advice-defined anonymous class that implements {@code AspectJCallback} containing
|
||||
* a simple {@code return proceed();} statement
|
||||
*
|
||||
* @return The returned value from the method invocation
|
||||
*/
|
||||
public Object invoke(JoinPoint jp, AspectJCallback advisorProceed) {
|
||||
Object result = null;
|
||||
InterceptorStatusToken token = super.beforeInvocation(new MethodInvocationAdapter(jp));
|
||||
|
||||
try {
|
||||
result = advisorProceed.proceedWithObject();
|
||||
} finally {
|
||||
result = super.afterInvocation(token, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+2
@@ -37,7 +37,9 @@ import org.aspectj.lang.JoinPoint;
|
||||
* Refer to {@link AbstractSecurityInterceptor} for details on the workflow.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @deprecated Use AspectJMethodSecurityInterceptor instead
|
||||
*/
|
||||
@Deprecated
|
||||
public class AspectJSecurityInterceptor extends AbstractSecurityInterceptor {
|
||||
//~ Instance fields ================================================================================================
|
||||
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package org.springframework.security.access.intercept.aspectj;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.reflect.CodeSignature;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Decorates a JoinPoint to allow it to be used with method-security infrastructure
|
||||
* classes which support {@code MethodInvocation} instances.
|
||||
*
|
||||
* @author Luke Taylor
|
||||
* @since 3.0.3
|
||||
*/
|
||||
public final class MethodInvocationAdapter implements MethodInvocation {
|
||||
private final ProceedingJoinPoint jp;
|
||||
private final Method method;
|
||||
private final Object target;
|
||||
|
||||
MethodInvocationAdapter(JoinPoint jp) {
|
||||
this.jp = (ProceedingJoinPoint)jp;
|
||||
if (jp.getTarget() != null) {
|
||||
target = jp.getTarget();
|
||||
} else {
|
||||
// SEC-1295: target may be null if an ITD is in use
|
||||
target = jp.getSignature().getDeclaringType();
|
||||
}
|
||||
String targetMethodName = jp.getStaticPart().getSignature().getName();
|
||||
Class<?>[] types = ((CodeSignature) jp.getStaticPart().getSignature()).getParameterTypes();
|
||||
Class<?> declaringType = ((CodeSignature) jp.getStaticPart().getSignature()).getDeclaringType();
|
||||
|
||||
method = ClassUtils.getMethodIfAvailable(declaringType, targetMethodName, types);
|
||||
Assert.notNull(method, "Could not obtain target method from JoinPoint: '"+ jp + "'");
|
||||
|
||||
}
|
||||
|
||||
public Method getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public Object[] getArguments() {
|
||||
return jp.getArgs();
|
||||
}
|
||||
|
||||
public AccessibleObject getStaticPart() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public Object getThis() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public Object proceed() throws Throwable {
|
||||
return jp.proceed();
|
||||
}
|
||||
}
|
||||
+7
-1
@@ -49,7 +49,13 @@ public abstract class AbstractMethodSecurityMetadataSource implements MethodSecu
|
||||
if (object instanceof MethodInvocation) {
|
||||
MethodInvocation mi = (MethodInvocation) object;
|
||||
Object target = mi.getThis();
|
||||
return getAttributes(mi.getMethod(), target == null ? null : target.getClass());
|
||||
Class<?> targetClass = null;
|
||||
|
||||
if (target != null) {
|
||||
targetClass = target instanceof Class<?> ? (Class<?>)target : target.getClass();
|
||||
}
|
||||
|
||||
return getAttributes(mi.getMethod(), targetClass);
|
||||
}
|
||||
|
||||
if (object instanceof JoinPoint) {
|
||||
|
||||
+26
-34
@@ -15,26 +15,28 @@
|
||||
|
||||
package org.springframework.security.access.intercept.aspectj;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.jmock.integration.junit4.JUnit4Mockery;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.security.MockJoinPoint;
|
||||
import org.springframework.security.TargetObject;
|
||||
import org.springframework.security.access.AccessDecisionManager;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.access.SecurityConfig;
|
||||
import org.springframework.security.access.intercept.aspectj.AspectJCallback;
|
||||
import org.springframework.security.access.intercept.aspectj.AspectJSecurityInterceptor;
|
||||
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.SecurityContextHolder;
|
||||
|
||||
|
||||
@@ -42,33 +44,33 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
||||
* Tests {@link AspectJSecurityInterceptor}.
|
||||
*
|
||||
* @author Ben Alex
|
||||
* @author Luke Taylor
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class AspectJSecurityInterceptorTests {
|
||||
private Mockery jmock = new JUnit4Mockery();
|
||||
private TestingAuthenticationToken token;
|
||||
private AspectJSecurityInterceptor interceptor;
|
||||
private AccessDecisionManager adm;
|
||||
private MethodSecurityMetadataSource mds;
|
||||
private AuthenticationManager authman;
|
||||
private AspectJCallback aspectJCallback;
|
||||
private @Mock AccessDecisionManager adm;
|
||||
private @Mock MethodSecurityMetadataSource mds;
|
||||
private @Mock AuthenticationManager authman;
|
||||
private @Mock AspectJCallback aspectJCallback;
|
||||
private JoinPoint joinPoint;
|
||||
|
||||
//~ Methods ========================================================================================================
|
||||
|
||||
@Before
|
||||
public final void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
SecurityContextHolder.clearContext();
|
||||
token = new TestingAuthenticationToken("Test", "Password");
|
||||
interceptor = new AspectJSecurityInterceptor();
|
||||
adm = jmock.mock(AccessDecisionManager.class);
|
||||
authman = jmock.mock(AuthenticationManager.class);
|
||||
mds = jmock.mock(MethodSecurityMetadataSource.class);
|
||||
interceptor.setAccessDecisionManager(adm);
|
||||
interceptor.setAuthenticationManager(authman);
|
||||
interceptor.setSecurityMetadataSource(mds);
|
||||
Method method = TargetObject.class.getMethod("countLength", new Class[] {String.class});
|
||||
joinPoint = new MockJoinPoint(new TargetObject(), method);
|
||||
aspectJCallback = jmock.mock(AspectJCallback.class);
|
||||
when(mds.getAttributes(any(JoinPoint.class))).thenReturn(SecurityConfig.createList("ROLE_USER"));
|
||||
when(authman.authenticate(token)).thenReturn(token);
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -77,33 +79,23 @@ public class AspectJSecurityInterceptorTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void callbackIsInvokedWhenPermissionGranted() throws Exception {
|
||||
jmock.checking(new Expectations() {{
|
||||
oneOf(mds).getAttributes(with(any(JoinPoint.class))); will (returnValue(SecurityConfig.createList("ROLE_USER")));
|
||||
oneOf(authman).authenticate(token); will(returnValue(token));
|
||||
oneOf(adm).decide(with(token), with(aNonNull(JoinPoint.class)), with(aNonNull(List.class)));
|
||||
oneOf(aspectJCallback).proceedWithObject();
|
||||
}});
|
||||
|
||||
SecurityContextHolder.getContext().setAuthentication(token);
|
||||
interceptor.invoke(joinPoint, aspectJCallback);
|
||||
jmock.assertIsSatisfied();
|
||||
verify(aspectJCallback).proceedWithObject();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test(expected=AccessDeniedException.class)
|
||||
@Test
|
||||
public void callbackIsNotInvokedWhenPermissionDenied() throws Exception {
|
||||
jmock.checking(new Expectations() {{
|
||||
oneOf(mds).getAttributes(with(any(JoinPoint.class))); will (returnValue(SecurityConfig.createList("ROLE_USER")));
|
||||
oneOf(authman).authenticate(token); will(returnValue(token));
|
||||
oneOf(adm).decide(with(token), with(aNonNull(JoinPoint.class)), with(aNonNull(List.class)));
|
||||
will(throwException(new AccessDeniedException("denied")));
|
||||
never(aspectJCallback).proceedWithObject();
|
||||
}});
|
||||
doThrow(new AccessDeniedException("denied")).when(adm).decide(any(Authentication.class), any(), any(Collection.class));
|
||||
|
||||
SecurityContextHolder.getContext().setAuthentication(token);
|
||||
interceptor.invoke(joinPoint, aspectJCallback);
|
||||
jmock.assertIsSatisfied();
|
||||
try {
|
||||
interceptor.invoke(joinPoint, aspectJCallback);
|
||||
fail("Expected AccessDeniedException");
|
||||
} catch (AccessDeniedException expected) {
|
||||
}
|
||||
verify(aspectJCallback, never()).proceedWithObject();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user