Add AspectJ AuthorizationManager Support

Closes gh-11326
This commit is contained in:
Josh Cummings
2022-07-08 11:37:36 -06:00
parent a5351f3d89
commit 0f58620643
24 changed files with 1223 additions and 13 deletions

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method.aspectj;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.access.prepost.PostAuthorize;
/**
* Abstract AspectJ aspect for adapting a {@link MethodInvocation}
*
* @author Josh Cummings
* @since 5.8
*/
abstract aspect AbstractMethodInterceptorAspect {
protected abstract pointcut executionOfAnnotatedMethod();
private MethodInterceptor securityInterceptor;
Object around(): executionOfAnnotatedMethod() {
if (this.securityInterceptor == null) {
return proceed();
}
MethodInvocation invocation = new JoinPointMethodInvocation(thisJoinPoint, () -> proceed());
try {
return this.securityInterceptor.invoke(invocation);
} catch (Throwable t) {
throwUnchecked(t);
throw new IllegalStateException("Code unexpectedly reached", t);
}
}
public void setSecurityInterceptor(MethodInterceptor securityInterceptor) {
this.securityInterceptor = securityInterceptor;
}
private static void throwUnchecked(Throwable ex) {
AbstractMethodInterceptorAspect.<RuntimeException>throwAny(ex);
}
@SuppressWarnings("unchecked")
private static <E extends Throwable> void throwAny(Throwable ex) throws E {
throw (E) ex;
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method.aspectj;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import java.util.function.Supplier;
import org.aopalliance.intercept.MethodInvocation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.CodeSignature;
import org.springframework.util.Assert;
class JoinPointMethodInvocation implements MethodInvocation {
private final JoinPoint jp;
private final Method method;
private final Object target;
private final Supplier<Object> proceed;
JoinPointMethodInvocation(JoinPoint jp, Supplier<Object> proceed) {
this.jp = jp;
if (jp.getTarget() != null) {
this.target = jp.getTarget();
}
else {
// SEC-1295: target may be null if an ITD is in use
this.target = jp.getSignature().getDeclaringType();
}
String targetMethodName = jp.getStaticPart().getSignature().getName();
Class<?>[] types = ((CodeSignature) jp.getStaticPart().getSignature()).getParameterTypes();
Class<?> declaringType = jp.getStaticPart().getSignature().getDeclaringType();
this.method = findMethod(targetMethodName, declaringType, types);
Assert.notNull(this.method, () -> "Could not obtain target method from JoinPoint: '" + jp + "'");
this.proceed = proceed;
}
private Method findMethod(String name, Class<?> declaringType, Class<?>[] params) {
Method method = null;
try {
method = declaringType.getMethod(name, params);
}
catch (NoSuchMethodException ignored) {
}
if (method == null) {
try {
method = declaringType.getDeclaredMethod(name, params);
}
catch (NoSuchMethodException ignored) {
}
}
return method;
}
@Override
public Method getMethod() {
return this.method;
}
@Override
public Object[] getArguments() {
return this.jp.getArgs();
}
@Override
public AccessibleObject getStaticPart() {
return this.method;
}
@Override
public Object getThis() {
return this.target;
}
@Override
public Object proceed() throws Throwable {
return this.proceed.get();
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method.aspectj;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.access.prepost.PostAuthorize;
/**
* Concrete AspectJ aspect using Spring Security @PostAuthorize annotation.
*
* <p>
* When using this aspect, you <i>must</i> annotate the implementation class
* (and/or methods within that class), <i>not</i> the interface (if any) that
* the class implements. AspectJ follows Java's rule that annotations on
* interfaces are <i>not</i> inherited. This will vary from Spring AOP.
*
* @author Mike Wiesner
* @author Luke Taylor
* @author Josh Cummings
* @since 5.8
*/
public aspect PostAuthorizeAspect extends AbstractMethodInterceptorAspect {
/**
* Matches the execution of any method with a PostAuthorize annotation.
*/
protected pointcut executionOfAnnotatedMethod() : execution(* *(..)) && @annotation(PostAuthorize);
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method.aspectj;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.access.prepost.PostFilter;
/**
* Concrete AspectJ aspect using Spring Security @PostFilter annotation.
*
* <p>
* When using this aspect, you <i>must</i> annotate the implementation class
* (and/or methods within that class), <i>not</i> the interface (if any) that
* the class implements. AspectJ follows Java's rule that annotations on
* interfaces are <i>not</i> inherited. This will vary from Spring AOP.
*
* @author Mike Wiesner
* @author Luke Taylor
* @author Josh Cummings
* @since 5.8
*/
public aspect PostFilterAspect extends AbstractMethodInterceptorAspect {
/**
* Matches the execution of any method with a PostFilter annotation.
*/
protected pointcut executionOfAnnotatedMethod() : execution(* *(..)) && @annotation(PostFilter);
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method.aspectj;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.access.prepost.PreAuthorize;
/**
* Concrete AspectJ aspect using Spring Security @PreAuthorize annotation.
*
* <p>
* When using this aspect, you <i>must</i> annotate the implementation class
* (and/or methods within that class), <i>not</i> the interface (if any) that
* the class implements. AspectJ follows Java's rule that annotations on
* interfaces are <i>not</i> inherited. This will vary from Spring AOP.
*
* @author Mike Wiesner
* @author Luke Taylor
* @author Josh Cummings
* @since 5.8
*/
public aspect PreAuthorizeAspect extends AbstractMethodInterceptorAspect {
/**
* Matches the execution of any method with a PreAuthorize annotation.
*/
protected pointcut executionOfAnnotatedMethod() : execution(* *(..)) && @annotation(PreAuthorize);
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method.aspectj;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.access.prepost.PreFilter;
/**
* Concrete AspectJ aspect using Spring Security @PreFilter annotation.
*
* <p>
* When using this aspect, you <i>must</i> annotate the implementation class
* (and/or methods within that class), <i>not</i> the interface (if any) that
* the class implements. AspectJ follows Java's rule that annotations on
* interfaces are <i>not</i> inherited. This will vary from Spring AOP.
*
* @author Mike Wiesner
* @author Luke Taylor
* @author Josh Cummings
* @since 5.8
*/
public aspect PreFilterAspect extends AbstractMethodInterceptorAspect {
/**
* Matches the execution of any method with a PreFilter annotation.
*/
protected pointcut executionOfAnnotatedMethod() : execution(* *(..)) && @annotation(PreFilter);
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method.aspectj;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.access.annotation.Secured;
/**
* Concrete AspectJ aspect using Spring Security @Secured annotation.
*
* <p>
* When using this aspect, you <i>must</i> annotate the implementation class
* (and/or methods within that class), <i>not</i> the interface (if any) that
* the class implements. AspectJ follows Java's rule that annotations on
* interfaces are <i>not</i> inherited. This will vary from Spring AOP.
*
* @author Mike Wiesner
* @author Luke Taylor
* @author Josh Cummings
* @since 5.8
*/
public aspect SecuredAspect extends AbstractMethodInterceptorAspect {
/**
* Matches the execution of any public method in a type with the Secured
* annotation, or any subtype of a type with the Secured annotation.
*/
private pointcut executionOfAnyPublicMethodInAtSecuredType() :
execution(public * ((@Secured *)+).*(..)) && @this(Secured);
/**
* Matches the execution of any method with the Secured annotation.
*/
private pointcut executionOfSecuredMethod() :
execution(* *(..)) && @annotation(Secured);
protected pointcut executionOfAnnotatedMethod() :
executionOfAnyPublicMethodInAtSecuredType() ||
executionOfSecuredMethod();
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method.aspectj;
import org.aopalliance.intercept.MethodInterceptor;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockitoAnnotations;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor;
import org.springframework.security.core.context.SecurityContextHolder;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Luke Taylor
* @since 3.0.3
*/
public class PostAuthorizeAspectTests {
private TestingAuthenticationToken anne = new TestingAuthenticationToken("anne", "", "ROLE_A");
private MethodInterceptor interceptor;
private SecuredImpl secured = new SecuredImpl();
private SecuredImplSubclass securedSub = new SecuredImplSubclass();
private PrePostSecured prePostSecured = new PrePostSecured();
@BeforeEach
public final void setUp() {
MockitoAnnotations.initMocks(this);
this.interceptor = AuthorizationManagerAfterMethodInterceptor.postAuthorize();
PostAuthorizeAspect secAspect = PostAuthorizeAspect.aspectOf();
secAspect.setSecurityInterceptor(this.interceptor);
}
@AfterEach
public void clearContext() {
SecurityContextHolder.clearContext();
}
@Test
public void securedInterfaceMethodAllowsAllAccess() {
this.secured.securedMethod();
}
@Test
public void securedClassMethodDeniesUnauthenticatedAccess() {
assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class)
.isThrownBy(() -> this.secured.securedClassMethod());
}
@Test
public void securedClassMethodAllowsAccessToRoleA() {
SecurityContextHolder.getContext().setAuthentication(this.anne);
this.secured.securedClassMethod();
}
@Test
public void internalPrivateCallIsIntercepted() {
SecurityContextHolder.getContext().setAuthentication(this.anne);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.secured.publicCallsPrivate());
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.securedSub.publicCallsPrivate());
}
@Test
public void protectedMethodIsIntercepted() {
SecurityContextHolder.getContext().setAuthentication(this.anne);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.secured.protectedMethod());
}
@Test
public void overriddenProtectedMethodIsNotIntercepted() {
// AspectJ doesn't inherit annotations
this.securedSub.protectedMethod();
}
// SEC-1262
@Test
public void denyAllPreAuthorizeDeniesAccess() {
SecurityContextHolder.getContext().setAuthentication(this.anne);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.prePostSecured::denyAllMethod);
}
interface SecuredInterface {
@PostAuthorize("hasRole('X')")
void securedMethod();
}
static class SecuredImpl implements SecuredInterface {
// Not really secured because AspectJ doesn't inherit annotations from interfaces
@Override
public void securedMethod() {
}
@PostAuthorize("hasRole('A')")
void securedClassMethod() {
}
@PostAuthorize("hasRole('X')")
private void privateMethod() {
}
@PostAuthorize("hasRole('X')")
protected void protectedMethod() {
}
@PostAuthorize("hasRole('X')")
void publicCallsPrivate() {
privateMethod();
}
}
static class SecuredImplSubclass extends SecuredImpl {
@Override
protected void protectedMethod() {
}
@Override
public void publicCallsPrivate() {
super.publicCallsPrivate();
}
}
static class PrePostSecured {
@PostAuthorize("denyAll")
void denyAllMethod() {
}
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method.aspectj;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.aopalliance.intercept.MethodInterceptor;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockitoAnnotations;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Luke Taylor
* @since 3.0.3
*/
public class PostFilterAspectTests {
private MethodInterceptor interceptor;
private PrePostSecured prePostSecured = new PrePostSecured();
@BeforeEach
public final void setUp() {
MockitoAnnotations.initMocks(this);
this.interceptor = new PostFilterAuthorizationMethodInterceptor();
PostFilterAspect secAspect = PostFilterAspect.aspectOf();
secAspect.setSecurityInterceptor(this.interceptor);
}
@Test
public void preFilterMethodWhenListThenFilters() {
List<String> objects = new ArrayList<>(Arrays.asList("apple", "banana", "aubergine", "orange"));
assertThat(this.prePostSecured.postFilterMethod(objects)).containsExactly("apple", "aubergine");
}
static class PrePostSecured {
@PostFilter("filterObject.startsWith('a')")
List<String> postFilterMethod(List<String> objects) {
return objects;
}
}
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method.aspectj;
import org.aopalliance.intercept.MethodInterceptor;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockitoAnnotations;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.core.context.SecurityContextHolder;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Luke Taylor
* @since 3.0.3
*/
public class PreAuthorizeAspectTests {
private TestingAuthenticationToken anne = new TestingAuthenticationToken("anne", "", "ROLE_A");
private MethodInterceptor interceptor;
private SecuredImpl secured = new SecuredImpl();
private SecuredImplSubclass securedSub = new SecuredImplSubclass();
private PrePostSecured prePostSecured = new PrePostSecured();
@BeforeEach
public final void setUp() {
MockitoAnnotations.initMocks(this);
this.interceptor = AuthorizationManagerBeforeMethodInterceptor.preAuthorize();
PreAuthorizeAspect secAspect = PreAuthorizeAspect.aspectOf();
secAspect.setSecurityInterceptor(this.interceptor);
}
@AfterEach
public void clearContext() {
SecurityContextHolder.clearContext();
}
@Test
public void securedInterfaceMethodAllowsAllAccess() {
this.secured.securedMethod();
}
@Test
public void securedClassMethodDeniesUnauthenticatedAccess() {
assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class)
.isThrownBy(() -> this.secured.securedClassMethod());
}
@Test
public void securedClassMethodAllowsAccessToRoleA() {
SecurityContextHolder.getContext().setAuthentication(this.anne);
this.secured.securedClassMethod();
}
@Test
public void internalPrivateCallIsIntercepted() {
SecurityContextHolder.getContext().setAuthentication(this.anne);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.secured.publicCallsPrivate());
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.securedSub.publicCallsPrivate());
}
@Test
public void protectedMethodIsIntercepted() {
SecurityContextHolder.getContext().setAuthentication(this.anne);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.secured.protectedMethod());
}
@Test
public void overriddenProtectedMethodIsNotIntercepted() {
// AspectJ doesn't inherit annotations
this.securedSub.protectedMethod();
}
// SEC-1262
@Test
public void denyAllPreAuthorizeDeniesAccess() {
SecurityContextHolder.getContext().setAuthentication(this.anne);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.prePostSecured::denyAllMethod);
}
interface SecuredInterface {
@PreAuthorize("hasRole('X')")
void securedMethod();
}
static class SecuredImpl implements SecuredInterface {
// Not really secured because AspectJ doesn't inherit annotations from interfaces
@Override
public void securedMethod() {
}
@PreAuthorize("hasRole('A')")
void securedClassMethod() {
}
@PreAuthorize("hasRole('X')")
private void privateMethod() {
}
@PreAuthorize("hasRole('X')")
protected void protectedMethod() {
}
@PreAuthorize("hasRole('X')")
void publicCallsPrivate() {
privateMethod();
}
}
static class SecuredImplSubclass extends SecuredImpl {
@Override
protected void protectedMethod() {
}
@Override
public void publicCallsPrivate() {
super.publicCallsPrivate();
}
}
static class PrePostSecured {
@PreAuthorize("denyAll")
void denyAllMethod() {
}
}
}

View File

@@ -0,0 +1,66 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method.aspectj;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.aopalliance.intercept.MethodInterceptor;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockitoAnnotations;
import org.springframework.security.access.prepost.PreFilter;
import org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Luke Taylor
* @since 3.0.3
*/
public class PreFilterAspectTests {
private MethodInterceptor interceptor;
private PrePostSecured prePostSecured = new PrePostSecured();
@BeforeEach
public final void setUp() {
MockitoAnnotations.initMocks(this);
this.interceptor = new PreFilterAuthorizationMethodInterceptor();
PreFilterAspect secAspect = PreFilterAspect.aspectOf();
secAspect.setSecurityInterceptor(this.interceptor);
}
@Test
public void preFilterMethodWhenListThenFilters() {
List<String> objects = new ArrayList<>(Arrays.asList("apple", "banana", "aubergine", "orange"));
assertThat(this.prePostSecured.preFilterMethod(objects)).containsExactly("apple", "aubergine");
}
static class PrePostSecured {
@PreFilter("filterObject.startsWith('a')")
List<String> preFilterMethod(List<String> objects) {
return objects;
}
}
}

View File

@@ -0,0 +1,143 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method.aspectj;
import org.aopalliance.intercept.MethodInterceptor;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockitoAnnotations;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
import org.springframework.security.core.context.SecurityContextHolder;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* @author Luke Taylor
* @since 3.0.3
*/
public class SecuredAspectTests {
private TestingAuthenticationToken anne = new TestingAuthenticationToken("anne", "", "ROLE_A");
private MethodInterceptor interceptor;
private SecuredImpl secured = new SecuredImpl();
private SecuredImplSubclass securedSub = new SecuredImplSubclass();
@BeforeEach
public final void setUp() {
MockitoAnnotations.initMocks(this);
this.interceptor = AuthorizationManagerBeforeMethodInterceptor.secured();
SecuredAspect secAspect = SecuredAspect.aspectOf();
secAspect.setSecurityInterceptor(this.interceptor);
}
@AfterEach
public void clearContext() {
SecurityContextHolder.clearContext();
}
@Test
public void securedInterfaceMethodAllowsAllAccess() {
this.secured.securedMethod();
}
@Test
public void securedClassMethodDeniesUnauthenticatedAccess() {
assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class)
.isThrownBy(() -> this.secured.securedClassMethod());
}
@Test
public void securedClassMethodAllowsAccessToRoleA() {
SecurityContextHolder.getContext().setAuthentication(this.anne);
this.secured.securedClassMethod();
}
@Test
public void internalPrivateCallIsIntercepted() {
SecurityContextHolder.getContext().setAuthentication(this.anne);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.secured.publicCallsPrivate());
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.securedSub.publicCallsPrivate());
}
@Test
public void protectedMethodIsIntercepted() {
SecurityContextHolder.getContext().setAuthentication(this.anne);
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.secured.protectedMethod());
}
@Test
public void overriddenProtectedMethodIsNotIntercepted() {
// AspectJ doesn't inherit annotations
this.securedSub.protectedMethod();
}
interface SecuredInterface {
@Secured("ROLE_X")
void securedMethod();
}
static class SecuredImpl implements SecuredInterface {
// Not really secured because AspectJ doesn't inherit annotations from interfaces
@Override
public void securedMethod() {
}
@Secured("ROLE_A")
void securedClassMethod() {
}
@Secured("ROLE_X")
private void privateMethod() {
}
@Secured("ROLE_X")
protected void protectedMethod() {
}
@Secured("ROLE_X")
void publicCallsPrivate() {
privateMethod();
}
}
static class SecuredImplSubclass extends SecuredImpl {
@Override
protected void protectedMethod() {
}
@Override
public void publicCallsPrivate() {
super.publicCallsPrivate();
}
}
}