Consider AuthorizationManager for Method Security
Closes gh-9289
This commit is contained in:
committed by
Josh Cummings
parent
a8a7ab4ffa
commit
20778f727b
+87
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2002-2021 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.config.annotation.method.configuration;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.AdviceMode;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.security.access.annotation.Secured;
|
||||
|
||||
/**
|
||||
* Enables Spring Security Method Security.
|
||||
* @author Evgeniy Cheban
|
||||
* @since 5.5
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Documented
|
||||
@Import(MethodSecuritySelector.class)
|
||||
@Configuration
|
||||
public @interface EnableMethodSecurity {
|
||||
|
||||
/**
|
||||
* Determines if Spring Security's {@link Secured} annotation should be enabled.
|
||||
* Default is false.
|
||||
* @return true if {@link Secured} annotation should be enabled false otherwise
|
||||
*/
|
||||
boolean securedEnabled() default false;
|
||||
|
||||
/**
|
||||
* Determines if JSR-250 annotations should be enabled. Default is false.
|
||||
* @return true if JSR-250 should be enabled false otherwise
|
||||
*/
|
||||
boolean jsr250Enabled() default false;
|
||||
|
||||
/**
|
||||
* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed to
|
||||
* standard Java interface-based proxies. The default is {@code false}. <strong>
|
||||
* Applicable only if {@link #mode()} is set to {@link AdviceMode#PROXY}</strong>.
|
||||
* <p>
|
||||
* Note that setting this attribute to {@code true} will affect <em>all</em>
|
||||
* Spring-managed beans requiring proxying, not just those marked with
|
||||
* {@code @Cacheable}. For example, other beans marked with Spring's
|
||||
* {@code @Transactional} annotation will be upgraded to subclass proxying at the same
|
||||
* time. This approach has no negative impact in practice unless one is explicitly
|
||||
* expecting one type of proxy vs another, e.g. in tests.
|
||||
* @return true if subclass-based (CGLIB) proxies are to be created
|
||||
*/
|
||||
boolean proxyTargetClass() default false;
|
||||
|
||||
/**
|
||||
* Indicate how security advice should be applied. The default is
|
||||
* {@link AdviceMode#PROXY}.
|
||||
* @see AdviceMode
|
||||
* @return the {@link AdviceMode} to use
|
||||
*/
|
||||
AdviceMode mode() default AdviceMode.PROXY;
|
||||
|
||||
/**
|
||||
* Indicate the ordering of the execution of the security advisor when multiple
|
||||
* advices are applied at a specific joinpoint. The default is
|
||||
* {@link Ordered#LOWEST_PRECEDENCE}.
|
||||
* @return the order the security advisor should be applied
|
||||
*/
|
||||
int order() default Ordered.LOWEST_PRECEDENCE;
|
||||
|
||||
}
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* Copyright 2002-2021 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.config.annotation.method.configuration;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.security.DenyAll;
|
||||
import javax.annotation.security.PermitAll;
|
||||
import javax.annotation.security.RolesAllowed;
|
||||
|
||||
import org.springframework.aop.MethodMatcher;
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.aop.support.DefaultPointcutAdvisor;
|
||||
import org.springframework.aop.support.Pointcuts;
|
||||
import org.springframework.aop.support.StaticMethodMatcher;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportAware;
|
||||
import org.springframework.context.annotation.Role;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.security.access.annotation.Jsr250AuthorizationManager;
|
||||
import org.springframework.security.access.annotation.Secured;
|
||||
import org.springframework.security.access.annotation.SecuredAuthorizationManager;
|
||||
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
||||
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||
import org.springframework.security.access.intercept.aopalliance.AuthorizationMethodInterceptor;
|
||||
import org.springframework.security.access.method.AuthorizationManagerMethodAfterAdvice;
|
||||
import org.springframework.security.access.method.AuthorizationManagerMethodBeforeAdvice;
|
||||
import org.springframework.security.access.method.AuthorizationMethodAfterAdvice;
|
||||
import org.springframework.security.access.method.AuthorizationMethodBeforeAdvice;
|
||||
import org.springframework.security.access.method.DelegatingAuthorizationMethodAfterAdvice;
|
||||
import org.springframework.security.access.method.DelegatingAuthorizationMethodBeforeAdvice;
|
||||
import org.springframework.security.access.method.MethodAuthorizationContext;
|
||||
import org.springframework.security.access.prepost.PostAuthorize;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager;
|
||||
import org.springframework.security.authorization.method.PostFilterAuthorizationMethodAfterAdvice;
|
||||
import org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager;
|
||||
import org.springframework.security.authorization.method.PreFilterAuthorizationMethodBeforeAdvice;
|
||||
import org.springframework.security.config.core.GrantedAuthorityDefaults;
|
||||
|
||||
/**
|
||||
* Base {@link Configuration} for enabling Spring Security Method Security.
|
||||
*
|
||||
* @author Evgeniy Cheban
|
||||
* @see EnableMethodSecurity
|
||||
* @since 5.5
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
final class MethodSecurityConfiguration implements ImportAware {
|
||||
|
||||
private MethodSecurityExpressionHandler methodSecurityExpressionHandler;
|
||||
|
||||
private GrantedAuthorityDefaults grantedAuthorityDefaults;
|
||||
|
||||
private AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> authorizationMethodBeforeAdvice;
|
||||
|
||||
private AuthorizationMethodAfterAdvice<MethodAuthorizationContext> authorizationMethodAfterAdvice;
|
||||
|
||||
private AnnotationAttributes enableMethodSecurity;
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
DefaultPointcutAdvisor methodSecurityAdvisor(AuthorizationMethodInterceptor interceptor) {
|
||||
Pointcut pointcut = Pointcuts.union(getAuthorizationMethodBeforeAdvice(), getAuthorizationMethodAfterAdvice());
|
||||
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, interceptor);
|
||||
advisor.setOrder(order());
|
||||
return advisor;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
AuthorizationMethodInterceptor authorizationMethodInterceptor() {
|
||||
return new AuthorizationMethodInterceptor(getAuthorizationMethodBeforeAdvice(),
|
||||
getAuthorizationMethodAfterAdvice());
|
||||
}
|
||||
|
||||
private MethodSecurityExpressionHandler getMethodSecurityExpressionHandler() {
|
||||
if (this.methodSecurityExpressionHandler == null) {
|
||||
this.methodSecurityExpressionHandler = new DefaultMethodSecurityExpressionHandler();
|
||||
}
|
||||
return this.methodSecurityExpressionHandler;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
void setMethodSecurityExpressionHandler(MethodSecurityExpressionHandler methodSecurityExpressionHandler) {
|
||||
this.methodSecurityExpressionHandler = methodSecurityExpressionHandler;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
void setGrantedAuthorityDefaults(GrantedAuthorityDefaults grantedAuthorityDefaults) {
|
||||
this.grantedAuthorityDefaults = grantedAuthorityDefaults;
|
||||
}
|
||||
|
||||
private AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> getAuthorizationMethodBeforeAdvice() {
|
||||
if (this.authorizationMethodBeforeAdvice == null) {
|
||||
this.authorizationMethodBeforeAdvice = createDefaultAuthorizationMethodBeforeAdvice();
|
||||
}
|
||||
return this.authorizationMethodBeforeAdvice;
|
||||
}
|
||||
|
||||
private AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> createDefaultAuthorizationMethodBeforeAdvice() {
|
||||
List<AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>> beforeAdvices = new ArrayList<>();
|
||||
beforeAdvices.add(getPreFilterAuthorizationMethodBeforeAdvice());
|
||||
beforeAdvices.add(getPreAuthorizeAuthorizationMethodBeforeAdvice());
|
||||
if (securedEnabled()) {
|
||||
beforeAdvices.add(getSecuredAuthorizationMethodBeforeAdvice());
|
||||
}
|
||||
if (jsr250Enabled()) {
|
||||
beforeAdvices.add(getJsr250AuthorizationMethodBeforeAdvice());
|
||||
}
|
||||
return new DelegatingAuthorizationMethodBeforeAdvice(beforeAdvices);
|
||||
}
|
||||
|
||||
private PreFilterAuthorizationMethodBeforeAdvice getPreFilterAuthorizationMethodBeforeAdvice() {
|
||||
PreFilterAuthorizationMethodBeforeAdvice preFilterBeforeAdvice = new PreFilterAuthorizationMethodBeforeAdvice();
|
||||
preFilterBeforeAdvice.setExpressionHandler(getMethodSecurityExpressionHandler());
|
||||
return preFilterBeforeAdvice;
|
||||
}
|
||||
|
||||
private AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> getPreAuthorizeAuthorizationMethodBeforeAdvice() {
|
||||
MethodMatcher methodMatcher = new SecurityAnnotationsStaticMethodMatcher(PreAuthorize.class);
|
||||
PreAuthorizeAuthorizationManager authorizationManager = new PreAuthorizeAuthorizationManager();
|
||||
authorizationManager.setExpressionHandler(getMethodSecurityExpressionHandler());
|
||||
return new AuthorizationManagerMethodBeforeAdvice<>(methodMatcher, authorizationManager);
|
||||
}
|
||||
|
||||
private AuthorizationManagerMethodBeforeAdvice<MethodAuthorizationContext> getSecuredAuthorizationMethodBeforeAdvice() {
|
||||
MethodMatcher methodMatcher = new SecurityAnnotationsStaticMethodMatcher(Secured.class);
|
||||
SecuredAuthorizationManager authorizationManager = new SecuredAuthorizationManager();
|
||||
return new AuthorizationManagerMethodBeforeAdvice<>(methodMatcher, authorizationManager);
|
||||
}
|
||||
|
||||
private AuthorizationManagerMethodBeforeAdvice<MethodAuthorizationContext> getJsr250AuthorizationMethodBeforeAdvice() {
|
||||
MethodMatcher methodMatcher = new SecurityAnnotationsStaticMethodMatcher(DenyAll.class, PermitAll.class,
|
||||
RolesAllowed.class);
|
||||
Jsr250AuthorizationManager authorizationManager = new Jsr250AuthorizationManager();
|
||||
if (this.grantedAuthorityDefaults != null) {
|
||||
authorizationManager.setRolePrefix(this.grantedAuthorityDefaults.getRolePrefix());
|
||||
}
|
||||
return new AuthorizationManagerMethodBeforeAdvice<>(methodMatcher, authorizationManager);
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
void setAuthorizationMethodBeforeAdvice(
|
||||
AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> authorizationMethodBeforeAdvice) {
|
||||
this.authorizationMethodBeforeAdvice = authorizationMethodBeforeAdvice;
|
||||
}
|
||||
|
||||
private AuthorizationMethodAfterAdvice<MethodAuthorizationContext> getAuthorizationMethodAfterAdvice() {
|
||||
if (this.authorizationMethodAfterAdvice == null) {
|
||||
this.authorizationMethodAfterAdvice = createDefaultAuthorizationMethodAfterAdvice();
|
||||
}
|
||||
return this.authorizationMethodAfterAdvice;
|
||||
}
|
||||
|
||||
private AuthorizationMethodAfterAdvice<MethodAuthorizationContext> createDefaultAuthorizationMethodAfterAdvice() {
|
||||
List<AuthorizationMethodAfterAdvice<MethodAuthorizationContext>> afterAdvices = new ArrayList<>();
|
||||
afterAdvices.add(getPostFilterAuthorizationMethodAfterAdvice());
|
||||
afterAdvices.add(getPostAuthorizeAuthorizationMethodAfterAdvice());
|
||||
return new DelegatingAuthorizationMethodAfterAdvice(afterAdvices);
|
||||
}
|
||||
|
||||
private PostFilterAuthorizationMethodAfterAdvice getPostFilterAuthorizationMethodAfterAdvice() {
|
||||
PostFilterAuthorizationMethodAfterAdvice postFilterAfterAdvice = new PostFilterAuthorizationMethodAfterAdvice();
|
||||
postFilterAfterAdvice.setExpressionHandler(getMethodSecurityExpressionHandler());
|
||||
return postFilterAfterAdvice;
|
||||
}
|
||||
|
||||
private AuthorizationManagerMethodAfterAdvice<MethodAuthorizationContext> getPostAuthorizeAuthorizationMethodAfterAdvice() {
|
||||
MethodMatcher methodMatcher = new SecurityAnnotationsStaticMethodMatcher(PostAuthorize.class);
|
||||
PostAuthorizeAuthorizationManager authorizationManager = new PostAuthorizeAuthorizationManager();
|
||||
authorizationManager.setExpressionHandler(getMethodSecurityExpressionHandler());
|
||||
return new AuthorizationManagerMethodAfterAdvice<>(methodMatcher, authorizationManager);
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
void setAuthorizationMethodAfterAdvice(
|
||||
AuthorizationMethodAfterAdvice<MethodAuthorizationContext> authorizationMethodAfterAdvice) {
|
||||
this.authorizationMethodAfterAdvice = authorizationMethodAfterAdvice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
||||
Map<String, Object> attributes = importMetadata.getAnnotationAttributes(EnableMethodSecurity.class.getName());
|
||||
this.enableMethodSecurity = AnnotationAttributes.fromMap(attributes);
|
||||
}
|
||||
|
||||
private boolean securedEnabled() {
|
||||
return this.enableMethodSecurity.getBoolean("securedEnabled");
|
||||
}
|
||||
|
||||
private boolean jsr250Enabled() {
|
||||
return this.enableMethodSecurity.getBoolean("jsr250Enabled");
|
||||
}
|
||||
|
||||
private int order() {
|
||||
return this.enableMethodSecurity.getNumber("order");
|
||||
}
|
||||
|
||||
private static final class SecurityAnnotationsStaticMethodMatcher extends StaticMethodMatcher {
|
||||
|
||||
private final Set<Class<? extends Annotation>> annotationClasses;
|
||||
|
||||
@SafeVarargs
|
||||
private SecurityAnnotationsStaticMethodMatcher(Class<? extends Annotation>... annotationClasses) {
|
||||
this.annotationClasses = new HashSet<>(Arrays.asList(annotationClasses));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Method method, Class<?> targetClass) {
|
||||
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
|
||||
return hasAnnotations(specificMethod) || hasAnnotations(specificMethod.getDeclaringClass());
|
||||
}
|
||||
|
||||
private boolean hasAnnotations(AnnotatedElement annotatedElement) {
|
||||
Set<Annotation> annotations = AnnotatedElementUtils.findAllMergedAnnotations(annotatedElement,
|
||||
this.annotationClasses);
|
||||
return !annotations.isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2002-2021 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.config.annotation.method.configuration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.context.annotation.AdviceMode;
|
||||
import org.springframework.context.annotation.AdviceModeImportSelector;
|
||||
import org.springframework.context.annotation.AutoProxyRegistrar;
|
||||
|
||||
/**
|
||||
* Dynamically determines which imports to include using the {@link EnableMethodSecurity}
|
||||
* annotation.
|
||||
*
|
||||
* @author Evgeniy Cheban
|
||||
* @since 5.5
|
||||
*/
|
||||
final class MethodSecuritySelector extends AdviceModeImportSelector<EnableMethodSecurity> {
|
||||
|
||||
@Override
|
||||
protected String[] selectImports(AdviceMode adviceMode) {
|
||||
if (adviceMode == AdviceMode.PROXY) {
|
||||
return getProxyImports();
|
||||
}
|
||||
throw new IllegalStateException("AdviceMode '" + adviceMode + "' is not supported");
|
||||
}
|
||||
|
||||
private String[] getProxyImports() {
|
||||
List<String> result = new ArrayList<>();
|
||||
result.add(AutoProxyRegistrar.class.getName());
|
||||
result.add(MethodSecurityConfiguration.class.getName());
|
||||
return result.toArray(new String[0]);
|
||||
}
|
||||
|
||||
}
|
||||
+360
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
* Copyright 2002-2021 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.config.annotation.method.configuration;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.aop.MethodMatcher;
|
||||
import org.springframework.aop.support.JdkRegexpMethodPointcut;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.access.PermissionEvaluator;
|
||||
import org.springframework.security.access.annotation.BusinessService;
|
||||
import org.springframework.security.access.annotation.ExpressionProtectedBusinessServiceImpl;
|
||||
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
||||
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||
import org.springframework.security.access.method.AuthorizationManagerMethodBeforeAdvice;
|
||||
import org.springframework.security.access.method.AuthorizationMethodAfterAdvice;
|
||||
import org.springframework.security.access.method.AuthorizationMethodBeforeAdvice;
|
||||
import org.springframework.security.access.method.MethodAuthorizationContext;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.config.test.SpringTestRule;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
|
||||
import org.springframework.security.test.context.support.WithAnonymousUser;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Tests for {@link MethodSecurityConfiguration}.
|
||||
*
|
||||
* @author Evgeniy Cheban
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SecurityTestExecutionListeners
|
||||
public class MethodSecurityConfigurationTests {
|
||||
|
||||
@Rule
|
||||
public final SpringTestRule spring = new SpringTestRule();
|
||||
|
||||
@Autowired(required = false)
|
||||
MethodSecurityService methodSecurityService;
|
||||
|
||||
@Autowired(required = false)
|
||||
BusinessService businessService;
|
||||
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
@Test
|
||||
public void preAuthorizeWhenRoleAdminThenAccessDeniedException() {
|
||||
this.spring.register(MethodSecurityServiceConfig.class).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::preAuthorize)
|
||||
.withMessage("Access Denied");
|
||||
}
|
||||
|
||||
@WithAnonymousUser
|
||||
@Test
|
||||
public void preAuthorizePermitAllWhenRoleAnonymousThenPasses() {
|
||||
this.spring.register(MethodSecurityServiceConfig.class).autowire();
|
||||
String result = this.methodSecurityService.preAuthorizePermitAll();
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
|
||||
@WithAnonymousUser
|
||||
@Test
|
||||
public void preAuthorizeNotAnonymousWhenRoleAnonymousThenAccessDeniedException() {
|
||||
this.spring.register(MethodSecurityServiceConfig.class).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class)
|
||||
.isThrownBy(this.methodSecurityService::preAuthorizeNotAnonymous).withMessage("Access Denied");
|
||||
}
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void preAuthorizeNotAnonymousWhenRoleUserThenPasses() {
|
||||
this.spring.register(MethodSecurityServiceConfig.class).autowire();
|
||||
this.methodSecurityService.preAuthorizeNotAnonymous();
|
||||
}
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void securedWhenRoleUserThenAccessDeniedException() {
|
||||
this.spring.register(MethodSecurityServiceConfig.class).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::secured)
|
||||
.withMessage("Access Denied");
|
||||
}
|
||||
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
@Test
|
||||
public void securedWhenRoleAdminThenPasses() {
|
||||
this.spring.register(MethodSecurityServiceConfig.class).autowire();
|
||||
String result = this.methodSecurityService.secured();
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
@Test
|
||||
public void securedUserWhenRoleAdminThenAccessDeniedException() {
|
||||
this.spring.register(MethodSecurityServiceConfig.class).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::securedUser)
|
||||
.withMessage("Access Denied");
|
||||
}
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void securedUserWhenRoleUserThenPasses() {
|
||||
this.spring.register(MethodSecurityServiceConfig.class).autowire();
|
||||
String result = this.methodSecurityService.securedUser();
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void preAuthorizeAdminWhenRoleUserThenAccessDeniedException() {
|
||||
this.spring.register(MethodSecurityServiceConfig.class).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::preAuthorizeAdmin)
|
||||
.withMessage("Access Denied");
|
||||
}
|
||||
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
@Test
|
||||
public void preAuthorizeAdminWhenRoleAdminThenPasses() {
|
||||
this.spring.register(MethodSecurityServiceConfig.class).autowire();
|
||||
this.methodSecurityService.preAuthorizeAdmin();
|
||||
}
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void postHasPermissionWhenParameterIsNotGrantThenAccessDeniedException() {
|
||||
this.spring.register(CustomPermissionEvaluatorConfig.class, MethodSecurityServiceConfig.class).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class)
|
||||
.isThrownBy(() -> this.methodSecurityService.postHasPermission("deny")).withMessage("Access Denied");
|
||||
}
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void postHasPermissionWhenParameterIsGrantThenPasses() {
|
||||
this.spring.register(CustomPermissionEvaluatorConfig.class, MethodSecurityServiceConfig.class).autowire();
|
||||
String result = this.methodSecurityService.postHasPermission("grant");
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void postAnnotationWhenParameterIsNotGrantThenAccessDeniedException() {
|
||||
this.spring.register(MethodSecurityServiceConfig.class).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class)
|
||||
.isThrownBy(() -> this.methodSecurityService.postAnnotation("deny")).withMessage("Access Denied");
|
||||
}
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void postAnnotationWhenParameterIsGrantThenPasses() {
|
||||
this.spring.register(MethodSecurityServiceConfig.class).autowire();
|
||||
String result = this.methodSecurityService.postAnnotation("grant");
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
|
||||
@WithMockUser("bob")
|
||||
@Test
|
||||
public void methodReturningAListWhenPrePostFiltersConfiguredThenFiltersList() {
|
||||
this.spring.register(BusinessServiceConfig.class).autowire();
|
||||
List<String> names = new ArrayList<>();
|
||||
names.add("bob");
|
||||
names.add("joe");
|
||||
names.add("sam");
|
||||
List<?> result = this.businessService.methodReturningAList(names);
|
||||
assertThat(result).hasSize(1);
|
||||
assertThat(result.get(0)).isEqualTo("bob");
|
||||
}
|
||||
|
||||
@WithMockUser("bob")
|
||||
@Test
|
||||
public void methodReturningAnArrayWhenPostFilterConfiguredThenFiltersArray() {
|
||||
this.spring.register(BusinessServiceConfig.class).autowire();
|
||||
List<String> names = new ArrayList<>();
|
||||
names.add("bob");
|
||||
names.add("joe");
|
||||
names.add("sam");
|
||||
Object[] result = this.businessService.methodReturningAnArray(names.toArray());
|
||||
assertThat(result).hasSize(1);
|
||||
assertThat(result[0]).isEqualTo("bob");
|
||||
}
|
||||
|
||||
@WithMockUser("bob")
|
||||
@Test
|
||||
public void securedUserWhenCustomBeforeAdviceConfiguredAndNameBobThenPasses() {
|
||||
this.spring.register(CustomAuthorizationManagerBeforeAdviceConfig.class, MethodSecurityServiceConfig.class)
|
||||
.autowire();
|
||||
String result = this.methodSecurityService.securedUser();
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
|
||||
@WithMockUser("joe")
|
||||
@Test
|
||||
public void securedUserWhenCustomBeforeAdviceConfiguredAndNameNotBobThenAccessDeniedException() {
|
||||
this.spring.register(CustomAuthorizationManagerBeforeAdviceConfig.class, MethodSecurityServiceConfig.class)
|
||||
.autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::securedUser)
|
||||
.withMessage("Access Denied");
|
||||
}
|
||||
|
||||
@WithMockUser("bob")
|
||||
@Test
|
||||
public void securedUserWhenCustomAfterAdviceConfiguredAndNameBobThenGranted() {
|
||||
this.spring.register(CustomAuthorizationManagerAfterAdviceConfig.class, MethodSecurityServiceConfig.class)
|
||||
.autowire();
|
||||
String result = this.methodSecurityService.securedUser();
|
||||
assertThat(result).isEqualTo("granted");
|
||||
}
|
||||
|
||||
@WithMockUser("joe")
|
||||
@Test
|
||||
public void securedUserWhenCustomAfterAdviceConfiguredAndNameNotBobThenAccessDeniedException() {
|
||||
this.spring.register(CustomAuthorizationManagerAfterAdviceConfig.class, MethodSecurityServiceConfig.class)
|
||||
.autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::securedUser)
|
||||
.withMessage("Access Denied for User 'joe'");
|
||||
}
|
||||
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
@Test
|
||||
public void jsr250WhenRoleAdminThenAccessDeniedException() {
|
||||
this.spring.register(MethodSecurityServiceConfig.class).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::jsr250)
|
||||
.withMessage("Access Denied");
|
||||
}
|
||||
|
||||
@WithAnonymousUser
|
||||
@Test
|
||||
public void jsr250PermitAllWhenRoleAnonymousThenPasses() {
|
||||
this.spring.register(MethodSecurityServiceConfig.class).autowire();
|
||||
String result = this.methodSecurityService.jsr250PermitAll();
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
@Test
|
||||
public void rolesAllowedUserWhenRoleAdminThenAccessDeniedException() {
|
||||
this.spring.register(BusinessServiceConfig.class).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.businessService::rolesAllowedUser)
|
||||
.withMessage("Access Denied");
|
||||
}
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void rolesAllowedUserWhenRoleUserThenPasses() {
|
||||
this.spring.register(BusinessServiceConfig.class).autowire();
|
||||
this.businessService.rolesAllowedUser();
|
||||
}
|
||||
|
||||
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
|
||||
static class MethodSecurityServiceConfig {
|
||||
|
||||
@Bean
|
||||
MethodSecurityService methodSecurityService() {
|
||||
return new MethodSecurityServiceImpl();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableMethodSecurity(jsr250Enabled = true)
|
||||
static class BusinessServiceConfig {
|
||||
|
||||
@Bean
|
||||
BusinessService businessService() {
|
||||
return new ExpressionProtectedBusinessServiceImpl();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableMethodSecurity
|
||||
static class CustomPermissionEvaluatorConfig {
|
||||
|
||||
@Bean
|
||||
MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
|
||||
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
||||
expressionHandler.setPermissionEvaluator(new PermissionEvaluator() {
|
||||
@Override
|
||||
public boolean hasPermission(Authentication authentication, Object targetDomainObject,
|
||||
Object permission) {
|
||||
return "grant".equals(targetDomainObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType,
|
||||
Object permission) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
});
|
||||
return expressionHandler;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableMethodSecurity
|
||||
static class CustomAuthorizationManagerBeforeAdviceConfig {
|
||||
|
||||
@Bean
|
||||
AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> customBeforeAdvice() {
|
||||
JdkRegexpMethodPointcut methodMatcher = new JdkRegexpMethodPointcut();
|
||||
methodMatcher.setPattern(".*MethodSecurityServiceImpl.*securedUser");
|
||||
AuthorizationManager<MethodAuthorizationContext> authorizationManager = (a,
|
||||
o) -> new AuthorizationDecision("bob".equals(a.get().getName()));
|
||||
return new AuthorizationManagerMethodBeforeAdvice<>(methodMatcher, authorizationManager);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableMethodSecurity
|
||||
static class CustomAuthorizationManagerAfterAdviceConfig {
|
||||
|
||||
@Bean
|
||||
AuthorizationMethodAfterAdvice<MethodAuthorizationContext> customAfterAdvice() {
|
||||
JdkRegexpMethodPointcut methodMatcher = new JdkRegexpMethodPointcut();
|
||||
methodMatcher.setPattern(".*MethodSecurityServiceImpl.*securedUser");
|
||||
return new AuthorizationMethodAfterAdvice<MethodAuthorizationContext>() {
|
||||
@Override
|
||||
public MethodMatcher getMethodMatcher() {
|
||||
return methodMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object after(Supplier<Authentication> authentication,
|
||||
MethodAuthorizationContext methodAuthorizationContext, Object returnedObject) {
|
||||
Authentication auth = authentication.get();
|
||||
if ("bob".equals(auth.getName())) {
|
||||
return "granted";
|
||||
}
|
||||
throw new AccessDeniedException("Access Denied for User '" + auth.getName() + "'");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user