mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-05-31 17:22:13 +00:00
Consider AuthorizationManager for Method Security
Closes gh-9289
This commit is contained in:
parent
a8a7ab4ffa
commit
20778f727b
@ -38,6 +38,7 @@ dependencies {
|
|||||||
optional'org.springframework:spring-websocket'
|
optional'org.springframework:spring-websocket'
|
||||||
optional 'org.jetbrains.kotlin:kotlin-reflect'
|
optional 'org.jetbrains.kotlin:kotlin-reflect'
|
||||||
optional 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
|
optional 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
|
||||||
|
optional 'javax.annotation:jsr250-api'
|
||||||
|
|
||||||
provided 'javax.servlet:javax.servlet-api'
|
provided 'javax.servlet:javax.servlet-api'
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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() + "'");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* 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.access.annotation;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
|
|
||||||
|
import org.springframework.core.MethodClassKey;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.security.access.method.MethodAuthorizationContext;
|
||||||
|
import org.springframework.security.authorization.AuthorizationManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract registry which provides an {@link AuthorizationManager} for the
|
||||||
|
* {@link MethodInvocation}.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
* @since 5.5
|
||||||
|
*/
|
||||||
|
abstract class AbstractAuthorizationManagerRegistry {
|
||||||
|
|
||||||
|
static final AuthorizationManager<MethodAuthorizationContext> NULL_MANAGER = (a, o) -> null;
|
||||||
|
|
||||||
|
private final Map<MethodClassKey, AuthorizationManager<MethodAuthorizationContext>> cachedManagers = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an {@link AuthorizationManager} for the {@link MethodAuthorizationContext}.
|
||||||
|
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to use
|
||||||
|
* @return an {@link AuthorizationManager} to use
|
||||||
|
*/
|
||||||
|
final AuthorizationManager<MethodAuthorizationContext> getManager(
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext) {
|
||||||
|
MethodInvocation methodInvocation = methodAuthorizationContext.getMethodInvocation();
|
||||||
|
Method method = methodInvocation.getMethod();
|
||||||
|
Class<?> targetClass = methodAuthorizationContext.getTargetClass();
|
||||||
|
MethodClassKey cacheKey = new MethodClassKey(method, targetClass);
|
||||||
|
return this.cachedManagers.computeIfAbsent(cacheKey, (k) -> resolveManager(method, targetClass));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subclasses should implement this method to provide the non-null
|
||||||
|
* {@link AuthorizationManager} for the method and the target class.
|
||||||
|
* @param method the method
|
||||||
|
* @param targetClass the target class
|
||||||
|
* @return the non-null {@link AuthorizationManager}
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
abstract AuthorizationManager<MethodAuthorizationContext> resolveManager(Method method, Class<?> targetClass);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,123 @@
|
|||||||
|
/*
|
||||||
|
* 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.access.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.AnnotatedElement;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import javax.annotation.security.DenyAll;
|
||||||
|
import javax.annotation.security.PermitAll;
|
||||||
|
import javax.annotation.security.RolesAllowed;
|
||||||
|
|
||||||
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
|
|
||||||
|
import org.springframework.aop.support.AopUtils;
|
||||||
|
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.security.access.method.MethodAuthorizationContext;
|
||||||
|
import org.springframework.security.authorization.AuthorityAuthorizationManager;
|
||||||
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
|
import org.springframework.security.authorization.AuthorizationManager;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link AuthorizationManager} which can determine if an {@link Authentication} has
|
||||||
|
* access to the {@link MethodInvocation} by evaluating if the {@link Authentication}
|
||||||
|
* contains a specified authority from the JSR-250 security annotations.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
* @since 5.5
|
||||||
|
*/
|
||||||
|
public final class Jsr250AuthorizationManager implements AuthorizationManager<MethodAuthorizationContext> {
|
||||||
|
|
||||||
|
private static final Set<Class<? extends Annotation>> JSR250_ANNOTATIONS = new HashSet<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
JSR250_ANNOTATIONS.add(DenyAll.class);
|
||||||
|
JSR250_ANNOTATIONS.add(PermitAll.class);
|
||||||
|
JSR250_ANNOTATIONS.add(RolesAllowed.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Jsr250AuthorizationManagerRegistry registry = new Jsr250AuthorizationManagerRegistry();
|
||||||
|
|
||||||
|
private String rolePrefix = "ROLE_";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the role prefix. Defaults to "ROLE_".
|
||||||
|
* @param rolePrefix the role prefix to use
|
||||||
|
*/
|
||||||
|
public void setRolePrefix(String rolePrefix) {
|
||||||
|
Assert.notNull(rolePrefix, "rolePrefix cannot be null");
|
||||||
|
this.rolePrefix = rolePrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if an {@link Authentication} has access to the {@link MethodInvocation}
|
||||||
|
* by evaluating if the {@link Authentication} contains a specified authority from the
|
||||||
|
* JSR-250 security annotations.
|
||||||
|
* @param authentication the {@link Supplier} of the {@link Authentication} to check
|
||||||
|
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
|
||||||
|
* @return an {@link AuthorizationDecision} or null if the JSR-250 security
|
||||||
|
* annotations is not present
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public AuthorizationDecision check(Supplier<Authentication> authentication,
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext) {
|
||||||
|
AuthorizationManager<MethodAuthorizationContext> delegate = this.registry
|
||||||
|
.getManager(methodAuthorizationContext);
|
||||||
|
return delegate.check(authentication, methodAuthorizationContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class Jsr250AuthorizationManagerRegistry extends AbstractAuthorizationManagerRegistry {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
AuthorizationManager<MethodAuthorizationContext> resolveManager(Method method, Class<?> targetClass) {
|
||||||
|
for (Annotation annotation : findJsr250Annotations(method, targetClass)) {
|
||||||
|
if (annotation instanceof DenyAll) {
|
||||||
|
return (a, o) -> new AuthorizationDecision(false);
|
||||||
|
}
|
||||||
|
if (annotation instanceof PermitAll) {
|
||||||
|
return (a, o) -> new AuthorizationDecision(true);
|
||||||
|
}
|
||||||
|
if (annotation instanceof RolesAllowed) {
|
||||||
|
RolesAllowed rolesAllowed = (RolesAllowed) annotation;
|
||||||
|
return AuthorityAuthorizationManager.hasAnyRole(Jsr250AuthorizationManager.this.rolePrefix,
|
||||||
|
rolesAllowed.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL_MANAGER;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<Annotation> findJsr250Annotations(Method method, Class<?> targetClass) {
|
||||||
|
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
|
||||||
|
Set<Annotation> annotations = findAnnotations(specificMethod);
|
||||||
|
return (annotations.isEmpty()) ? findAnnotations(specificMethod.getDeclaringClass()) : annotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<Annotation> findAnnotations(AnnotatedElement annotatedElement) {
|
||||||
|
return AnnotatedElementUtils.findAllMergedAnnotations(annotatedElement, JSR250_ANNOTATIONS);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* 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.access.annotation;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
|
|
||||||
|
import org.springframework.aop.support.AopUtils;
|
||||||
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.security.access.method.MethodAuthorizationContext;
|
||||||
|
import org.springframework.security.authorization.AuthorityAuthorizationManager;
|
||||||
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
|
import org.springframework.security.authorization.AuthorizationManager;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link AuthorizationManager} which can determine if an {@link Authentication} has
|
||||||
|
* access to the {@link MethodInvocation} by evaluating if the {@link Authentication}
|
||||||
|
* contains a specified authority from the Spring Security's {@link Secured} annotation.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
* @since 5.5
|
||||||
|
*/
|
||||||
|
public final class SecuredAuthorizationManager implements AuthorizationManager<MethodAuthorizationContext> {
|
||||||
|
|
||||||
|
private final SecuredAuthorizationManagerRegistry registry = new SecuredAuthorizationManagerRegistry();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if an {@link Authentication} has access to the {@link MethodInvocation}
|
||||||
|
* by evaluating if the {@link Authentication} contains a specified authority from the
|
||||||
|
* Spring Security's {@link Secured} annotation.
|
||||||
|
* @param authentication the {@link Supplier} of the {@link Authentication} to check
|
||||||
|
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
|
||||||
|
* @return an {@link AuthorizationDecision} or null if the {@link Secured} annotation
|
||||||
|
* is not present
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public AuthorizationDecision check(Supplier<Authentication> authentication,
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext) {
|
||||||
|
AuthorizationManager<MethodAuthorizationContext> delegate = this.registry
|
||||||
|
.getManager(methodAuthorizationContext);
|
||||||
|
return delegate.check(authentication, methodAuthorizationContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class SecuredAuthorizationManagerRegistry extends AbstractAuthorizationManagerRegistry {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
AuthorizationManager<MethodAuthorizationContext> resolveManager(Method method, Class<?> targetClass) {
|
||||||
|
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
|
||||||
|
Secured secured = findSecuredAnnotation(specificMethod);
|
||||||
|
return (secured != null) ? AuthorityAuthorizationManager.hasAnyAuthority(secured.value()) : NULL_MANAGER;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Secured findSecuredAnnotation(Method method) {
|
||||||
|
Secured secured = AnnotationUtils.findAnnotation(method, Secured.class);
|
||||||
|
return (secured != null) ? secured
|
||||||
|
: AnnotationUtils.findAnnotation(method.getDeclaringClass(), Secured.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* 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.access.intercept.aopalliance;
|
||||||
|
|
||||||
|
import org.aopalliance.intercept.MethodInterceptor;
|
||||||
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
|
|
||||||
|
import org.springframework.aop.support.AopUtils;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
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.authentication.AuthenticationCredentialsNotFoundException;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides security interception of AOP Alliance based method invocations.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
* @since 5.5
|
||||||
|
*/
|
||||||
|
public final class AuthorizationMethodInterceptor implements MethodInterceptor {
|
||||||
|
|
||||||
|
private final AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> beforeAdvice;
|
||||||
|
|
||||||
|
private final AuthorizationMethodAfterAdvice<MethodAuthorizationContext> afterAdvice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
* @param beforeAdvice the {@link AuthorizationMethodBeforeAdvice} to use
|
||||||
|
* @param afterAdvice the {@link AuthorizationMethodAfterAdvice} to use
|
||||||
|
*/
|
||||||
|
public AuthorizationMethodInterceptor(AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> beforeAdvice,
|
||||||
|
AuthorizationMethodAfterAdvice<MethodAuthorizationContext> afterAdvice) {
|
||||||
|
this.beforeAdvice = beforeAdvice;
|
||||||
|
this.afterAdvice = afterAdvice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should be used to enforce security on a {@link MethodInvocation}.
|
||||||
|
* @param mi the method being invoked which requires a security decision
|
||||||
|
* @return the returned value from the {@link MethodInvocation}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Object invoke(@NonNull MethodInvocation mi) throws Throwable {
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = getMethodAuthorizationContext(mi);
|
||||||
|
this.beforeAdvice.before(this::getAuthentication, methodAuthorizationContext);
|
||||||
|
Object returnedObject = mi.proceed();
|
||||||
|
return this.afterAdvice.after(this::getAuthentication, methodAuthorizationContext, returnedObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodAuthorizationContext getMethodAuthorizationContext(MethodInvocation mi) {
|
||||||
|
Object target = mi.getThis();
|
||||||
|
Class<?> targetClass = (target != null) ? AopUtils.getTargetClass(target) : null;
|
||||||
|
return new MethodAuthorizationContext(mi, targetClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Authentication getAuthentication() {
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
if (authentication == null) {
|
||||||
|
throw new AuthenticationCredentialsNotFoundException(
|
||||||
|
"An Authentication object was not found in the SecurityContext");
|
||||||
|
}
|
||||||
|
return authentication;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* 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.access.method;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.springframework.aop.MethodMatcher;
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
|
import org.springframework.security.authorization.AuthorizationManager;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link AuthorizationMethodAfterAdvice} which can determine if an
|
||||||
|
* {@link Authentication} has access to the {@link T} object using an
|
||||||
|
* {@link AuthorizationManager} if a {@link MethodMatcher} matches.
|
||||||
|
*
|
||||||
|
* @param <T> the type of object that the authorization check is being done one.
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
* @since 5.5
|
||||||
|
*/
|
||||||
|
public final class AuthorizationManagerMethodAfterAdvice<T> implements AuthorizationMethodAfterAdvice<T> {
|
||||||
|
|
||||||
|
private final MethodMatcher methodMatcher;
|
||||||
|
|
||||||
|
private final AuthorizationManager<T> authorizationManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
* @param methodMatcher the {@link MethodMatcher} to use
|
||||||
|
* @param authorizationManager the {@link AuthorizationManager} to use
|
||||||
|
*/
|
||||||
|
public AuthorizationManagerMethodAfterAdvice(MethodMatcher methodMatcher,
|
||||||
|
AuthorizationManager<T> authorizationManager) {
|
||||||
|
Assert.notNull(methodMatcher, "methodMatcher cannot be null");
|
||||||
|
Assert.notNull(authorizationManager, "authorizationManager cannot be null");
|
||||||
|
this.methodMatcher = methodMatcher;
|
||||||
|
this.authorizationManager = authorizationManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if an {@link Authentication} has access to the {@link T} object using
|
||||||
|
* the {@link AuthorizationManager}.
|
||||||
|
* @param authentication the {@link Supplier} of the {@link Authentication} to check
|
||||||
|
* @param object the {@link T} object to check
|
||||||
|
* @throws AccessDeniedException if access is not granted
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Object after(Supplier<Authentication> authentication, T object, Object returnedObject) {
|
||||||
|
this.authorizationManager.verify(authentication, object);
|
||||||
|
return returnedObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodMatcher getMethodMatcher() {
|
||||||
|
return this.methodMatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* 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.access.method;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.springframework.aop.MethodMatcher;
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
|
import org.springframework.security.authorization.AuthorizationManager;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link AuthorizationMethodBeforeAdvice} which can determine if an
|
||||||
|
* {@link Authentication} has access to the {@link T} object using an
|
||||||
|
* {@link AuthorizationManager} if a {@link MethodMatcher} matches.
|
||||||
|
*
|
||||||
|
* @param <T> the type of object that the authorization check is being done one.
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
* @since 5.5
|
||||||
|
*/
|
||||||
|
public final class AuthorizationManagerMethodBeforeAdvice<T> implements AuthorizationMethodBeforeAdvice<T> {
|
||||||
|
|
||||||
|
private final MethodMatcher methodMatcher;
|
||||||
|
|
||||||
|
private final AuthorizationManager<T> authorizationManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
* @param methodMatcher the {@link MethodMatcher} to use
|
||||||
|
* @param authorizationManager the {@link AuthorizationManager} to use
|
||||||
|
*/
|
||||||
|
public AuthorizationManagerMethodBeforeAdvice(MethodMatcher methodMatcher,
|
||||||
|
AuthorizationManager<T> authorizationManager) {
|
||||||
|
Assert.notNull(methodMatcher, "methodMatcher cannot be null");
|
||||||
|
Assert.notNull(authorizationManager, "authorizationManager cannot be null");
|
||||||
|
this.methodMatcher = methodMatcher;
|
||||||
|
this.authorizationManager = authorizationManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if an {@link Authentication} has access to the {@link T} object using
|
||||||
|
* the {@link AuthorizationManager}.
|
||||||
|
* @param authentication the {@link Supplier} of the {@link Authentication} to check
|
||||||
|
* @param object the {@link T} object to check
|
||||||
|
* @throws AccessDeniedException if access is not granted
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void before(Supplier<Authentication> authentication, T object) {
|
||||||
|
this.authorizationManager.verify(authentication, object);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodMatcher getMethodMatcher() {
|
||||||
|
return this.methodMatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* 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.access.method;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
|
|
||||||
|
import org.springframework.aop.ClassFilter;
|
||||||
|
import org.springframework.aop.Pointcut;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An Authorization advice that can determine if an {@link Authentication} has access to
|
||||||
|
* the returned object from the {@link MethodInvocation}. The {@link #getMethodMatcher()}
|
||||||
|
* describes when the advice applies for the method.
|
||||||
|
*
|
||||||
|
* @param <T> the type of object that the authorization check is being done one.
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
* @since 5.5
|
||||||
|
*/
|
||||||
|
public interface AuthorizationMethodAfterAdvice<T> extends Pointcut {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default {@link ClassFilter}.
|
||||||
|
* @return the {@link ClassFilter#TRUE} to use
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
default ClassFilter getClassFilter() {
|
||||||
|
return ClassFilter.TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if an {@link Authentication} has access to the returned object from the
|
||||||
|
* {@link MethodInvocation}.
|
||||||
|
* @param authentication the {@link Supplier} of the {@link Authentication} to check
|
||||||
|
* @param object the {@link T} object to check
|
||||||
|
* @param returnedObject the returned object from the {@link MethodInvocation} to
|
||||||
|
* check
|
||||||
|
* @return the <code>Object</code> that will ultimately be returned to the caller (if
|
||||||
|
* an implementation does not wish to modify the object to be returned to the caller,
|
||||||
|
* the implementation should simply return the same object it was passed by the
|
||||||
|
* <code>returnedObject</code> method argument)
|
||||||
|
*/
|
||||||
|
Object after(Supplier<Authentication> authentication, T object, Object returnedObject);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* 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.access.method;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.springframework.aop.ClassFilter;
|
||||||
|
import org.springframework.aop.Pointcut;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An advice which can determine if an {@link Authentication} has access to the {@link T}
|
||||||
|
* object. The {@link #getMethodMatcher()} describes when the advice applies for the
|
||||||
|
* method.
|
||||||
|
*
|
||||||
|
* @param <T> the type of object that the authorization check is being done one.
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
* @since 5.5
|
||||||
|
*/
|
||||||
|
public interface AuthorizationMethodBeforeAdvice<T> extends Pointcut {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default {@link ClassFilter}.
|
||||||
|
* @return the {@link ClassFilter#TRUE} to use
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
default ClassFilter getClassFilter() {
|
||||||
|
return ClassFilter.TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if an {@link Authentication} has access to the {@link T} object.
|
||||||
|
* @param authentication the {@link Supplier} of the {@link Authentication} to check
|
||||||
|
* @param object the {@link T} object to check
|
||||||
|
*/
|
||||||
|
void before(Supplier<Authentication> authentication, T object);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* 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.access.method;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import org.springframework.aop.MethodMatcher;
|
||||||
|
import org.springframework.aop.support.StaticMethodMatcher;
|
||||||
|
import org.springframework.core.log.LogMessage;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link AuthorizationMethodAfterAdvice} which delegates to specific
|
||||||
|
* {@link AuthorizationMethodAfterAdvice}s and returns the result (possibly modified) from
|
||||||
|
* the {@link MethodInvocation}.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
* @since 5.5
|
||||||
|
*/
|
||||||
|
public final class DelegatingAuthorizationMethodAfterAdvice
|
||||||
|
implements AuthorizationMethodAfterAdvice<MethodAuthorizationContext> {
|
||||||
|
|
||||||
|
private final Log logger = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
|
private final MethodMatcher methodMatcher = new StaticMethodMatcher() {
|
||||||
|
@Override
|
||||||
|
public boolean matches(Method method, Class<?> targetClass) {
|
||||||
|
for (AuthorizationMethodAfterAdvice<MethodAuthorizationContext> delegate : DelegatingAuthorizationMethodAfterAdvice.this.delegates) {
|
||||||
|
MethodMatcher methodMatcher = delegate.getMethodMatcher();
|
||||||
|
if (methodMatcher.matches(method, targetClass)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final List<AuthorizationMethodAfterAdvice<MethodAuthorizationContext>> delegates;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
* @param delegates the {@link AuthorizationMethodAfterAdvice}s to use
|
||||||
|
*/
|
||||||
|
public DelegatingAuthorizationMethodAfterAdvice(
|
||||||
|
List<AuthorizationMethodAfterAdvice<MethodAuthorizationContext>> delegates) {
|
||||||
|
this.delegates = delegates;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodMatcher getMethodMatcher() {
|
||||||
|
return this.methodMatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delegates to specific {@link AuthorizationMethodAfterAdvice}s and returns the
|
||||||
|
* <code>returnedObject</code> (possibly modified) from the method argument.
|
||||||
|
* @param authentication the {@link Supplier} of the {@link Authentication} to check
|
||||||
|
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
|
||||||
|
* @param returnedObject the returned object from the {@link MethodInvocation} to
|
||||||
|
* check
|
||||||
|
* @return the <code>returnedObject</code> (possibly modified) from the method
|
||||||
|
* argument
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Object after(Supplier<Authentication> authentication, MethodAuthorizationContext methodAuthorizationContext,
|
||||||
|
Object returnedObject) {
|
||||||
|
if (this.logger.isTraceEnabled()) {
|
||||||
|
this.logger.trace(
|
||||||
|
LogMessage.format("Post Authorizing %s from %s", returnedObject, methodAuthorizationContext));
|
||||||
|
}
|
||||||
|
Object result = returnedObject;
|
||||||
|
for (AuthorizationMethodAfterAdvice<MethodAuthorizationContext> delegate : this.delegates) {
|
||||||
|
if (this.logger.isTraceEnabled()) {
|
||||||
|
this.logger.trace(LogMessage.format("Checking authorization on %s from %s using %s", result,
|
||||||
|
methodAuthorizationContext, delegate));
|
||||||
|
}
|
||||||
|
result = delegate.after(authentication, methodAuthorizationContext, result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* 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.access.method;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import org.springframework.aop.MethodMatcher;
|
||||||
|
import org.springframework.aop.support.StaticMethodMatcher;
|
||||||
|
import org.springframework.core.log.LogMessage;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link AuthorizationMethodBeforeAdvice} which delegates to a specific
|
||||||
|
* {@link AuthorizationMethodBeforeAdvice} and grants access if all
|
||||||
|
* {@link AuthorizationMethodBeforeAdvice}s granted or abstained. Denies access only if
|
||||||
|
* one of the {@link AuthorizationMethodBeforeAdvice}s denied.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
* @since 5.5
|
||||||
|
*/
|
||||||
|
public final class DelegatingAuthorizationMethodBeforeAdvice
|
||||||
|
implements AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> {
|
||||||
|
|
||||||
|
private final Log logger = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
|
private final MethodMatcher methodMatcher = new StaticMethodMatcher() {
|
||||||
|
@Override
|
||||||
|
public boolean matches(Method method, Class<?> targetClass) {
|
||||||
|
for (AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> delegate : DelegatingAuthorizationMethodBeforeAdvice.this.delegates) {
|
||||||
|
MethodMatcher methodMatcher = delegate.getMethodMatcher();
|
||||||
|
if (methodMatcher.matches(method, targetClass)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final List<AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>> delegates;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
* @param delegates the {@link AuthorizationMethodBeforeAdvice}s to use
|
||||||
|
*/
|
||||||
|
public DelegatingAuthorizationMethodBeforeAdvice(
|
||||||
|
List<AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>> delegates) {
|
||||||
|
this.delegates = delegates;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodMatcher getMethodMatcher() {
|
||||||
|
return this.methodMatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delegates to a specific {@link AuthorizationMethodBeforeAdvice} and grants access
|
||||||
|
* if all {@link AuthorizationMethodBeforeAdvice}s granted or abstained. Denies only
|
||||||
|
* if one of the {@link AuthorizationMethodBeforeAdvice}s denied.
|
||||||
|
* @param authentication the {@link Supplier} of the {@link Authentication} to check
|
||||||
|
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void before(Supplier<Authentication> authentication, MethodAuthorizationContext methodAuthorizationContext) {
|
||||||
|
if (this.logger.isTraceEnabled()) {
|
||||||
|
this.logger.trace(LogMessage.format("Pre Authorizing %s", methodAuthorizationContext));
|
||||||
|
}
|
||||||
|
for (AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> delegate : this.delegates) {
|
||||||
|
if (this.logger.isTraceEnabled()) {
|
||||||
|
this.logger.trace(LogMessage.format("Checking authorization on %s using %s", methodAuthorizationContext,
|
||||||
|
delegate));
|
||||||
|
}
|
||||||
|
delegate.before(authentication, methodAuthorizationContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* 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.access.method;
|
||||||
|
|
||||||
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An authorization context which is holds the {@link MethodInvocation}, the target class
|
||||||
|
* and the returned object.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
* @since 5.5
|
||||||
|
*/
|
||||||
|
public final class MethodAuthorizationContext {
|
||||||
|
|
||||||
|
private final MethodInvocation methodInvocation;
|
||||||
|
|
||||||
|
private final Class<?> targetClass;
|
||||||
|
|
||||||
|
private Object returnObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
* @param methodInvocation the {@link MethodInvocation} to use
|
||||||
|
* @param targetClass the target class to use
|
||||||
|
*/
|
||||||
|
public MethodAuthorizationContext(MethodInvocation methodInvocation, Class<?> targetClass) {
|
||||||
|
this.methodInvocation = methodInvocation;
|
||||||
|
this.targetClass = targetClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link MethodInvocation}.
|
||||||
|
* @return the {@link MethodInvocation} to use
|
||||||
|
*/
|
||||||
|
public MethodInvocation getMethodInvocation() {
|
||||||
|
return this.methodInvocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the target class.
|
||||||
|
* @return the target class to use
|
||||||
|
*/
|
||||||
|
public Class<?> getTargetClass() {
|
||||||
|
return this.targetClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the returned object from the {@link MethodInvocation}.
|
||||||
|
* @return the returned object from the {@link MethodInvocation} to use
|
||||||
|
*/
|
||||||
|
public Object getReturnObject() {
|
||||||
|
return this.returnObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the returned object from the {@link MethodInvocation}.
|
||||||
|
* @param returnObject the returned object from the {@link MethodInvocation} to use
|
||||||
|
*/
|
||||||
|
public void setReturnObject(Object returnObject) {
|
||||||
|
this.returnObject = returnObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "MethodAuthorizationContext[methodInvocation=" + this.methodInvocation + ", targetClass="
|
||||||
|
+ this.targetClass + ", returnObject=" + this.returnObject + ']';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2020 the original author or authors.
|
* Copyright 2002-2021 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -75,9 +75,22 @@ public final class AuthorityAuthorizationManager<T> implements AuthorizationMana
|
|||||||
* @return the new instance
|
* @return the new instance
|
||||||
*/
|
*/
|
||||||
public static <T> AuthorityAuthorizationManager<T> hasAnyRole(String... roles) {
|
public static <T> AuthorityAuthorizationManager<T> hasAnyRole(String... roles) {
|
||||||
|
return hasAnyRole(ROLE_PREFIX, roles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of {@link AuthorityAuthorizationManager} with the provided
|
||||||
|
* authorities.
|
||||||
|
* @param rolePrefix the role prefix for <code>roles</code>
|
||||||
|
* @param roles the authorities to check for prefixed with <code>rolePrefix</code>
|
||||||
|
* @param <T> the type of object being authorized
|
||||||
|
* @return the new instance
|
||||||
|
*/
|
||||||
|
public static <T> AuthorityAuthorizationManager<T> hasAnyRole(String rolePrefix, String[] roles) {
|
||||||
|
Assert.notNull(rolePrefix, "rolePrefix cannot be null");
|
||||||
Assert.notEmpty(roles, "roles cannot be empty");
|
Assert.notEmpty(roles, "roles cannot be empty");
|
||||||
Assert.noNullElements(roles, "roles cannot contain null values");
|
Assert.noNullElements(roles, "roles cannot contain null values");
|
||||||
return hasAnyAuthority(toNamedRolesArray(roles));
|
return hasAnyAuthority(toNamedRolesArray(rolePrefix, roles));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -93,10 +106,10 @@ public final class AuthorityAuthorizationManager<T> implements AuthorizationMana
|
|||||||
return new AuthorityAuthorizationManager<>(authorities);
|
return new AuthorityAuthorizationManager<>(authorities);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String[] toNamedRolesArray(String... roles) {
|
private static String[] toNamedRolesArray(String rolePrefix, String[] roles) {
|
||||||
String[] result = new String[roles.length];
|
String[] result = new String[roles.length];
|
||||||
for (int i = 0; i < roles.length; i++) {
|
for (int i = 0; i < roles.length; i++) {
|
||||||
result[i] = ROLE_PREFIX + roles[i];
|
result[i] = rolePrefix + roles[i];
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* 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.authorization.method;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
|
|
||||||
|
import org.springframework.core.MethodClassKey;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.security.access.method.MethodAuthorizationContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract registry which provides an {@link ExpressionAttribute} for the
|
||||||
|
* {@link MethodInvocation}.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
* @since 5.5
|
||||||
|
*/
|
||||||
|
abstract class AbstractExpressionAttributeRegistry<T extends ExpressionAttribute> {
|
||||||
|
|
||||||
|
private final Map<MethodClassKey, T> cachedAttributes = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an {@link ExpressionAttribute} for the {@link MethodAuthorizationContext}.
|
||||||
|
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to use
|
||||||
|
* @return the {@link ExpressionAttribute} to use
|
||||||
|
*/
|
||||||
|
final T getAttribute(MethodAuthorizationContext methodAuthorizationContext) {
|
||||||
|
MethodInvocation methodInvocation = methodAuthorizationContext.getMethodInvocation();
|
||||||
|
Method method = methodInvocation.getMethod();
|
||||||
|
Class<?> targetClass = methodAuthorizationContext.getTargetClass();
|
||||||
|
return getAttribute(method, targetClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an {@link ExpressionAttribute} for the method and the target class.
|
||||||
|
* @param method the method
|
||||||
|
* @param targetClass the target class
|
||||||
|
* @return the {@link ExpressionAttribute} to use
|
||||||
|
*/
|
||||||
|
final T getAttribute(Method method, Class<?> targetClass) {
|
||||||
|
MethodClassKey cacheKey = new MethodClassKey(method, targetClass);
|
||||||
|
return this.cachedAttributes.computeIfAbsent(cacheKey, (k) -> resolveAttribute(method, targetClass));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subclasses should implement this method to provide the non-null
|
||||||
|
* {@link ExpressionAttribute} for the method and the target class.
|
||||||
|
* @param method the method
|
||||||
|
* @param targetClass the target class
|
||||||
|
* @return the non-null {@link ExpressionAttribute}
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
abstract T resolveAttribute(Method method, Class<?> targetClass);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* 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.authorization.method;
|
||||||
|
|
||||||
|
import org.springframework.expression.Expression;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link Expression} attribute.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
* @since 5.5
|
||||||
|
*/
|
||||||
|
class ExpressionAttribute {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an empty attribute with null {@link Expression}.
|
||||||
|
*/
|
||||||
|
static final ExpressionAttribute NULL_ATTRIBUTE = new ExpressionAttribute(null);
|
||||||
|
|
||||||
|
private final Expression expression;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
* @param expression the {@link Expression} to use
|
||||||
|
*/
|
||||||
|
ExpressionAttribute(Expression expression) {
|
||||||
|
this.expression = expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link Expression}.
|
||||||
|
* @return the {@link Expression} to use
|
||||||
|
*/
|
||||||
|
Expression getExpression() {
|
||||||
|
return this.expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* 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.authorization.method;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
|
import reactor.util.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.springframework.aop.support.AopUtils;
|
||||||
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
|
import org.springframework.expression.EvaluationContext;
|
||||||
|
import org.springframework.expression.Expression;
|
||||||
|
import org.springframework.security.access.expression.ExpressionUtils;
|
||||||
|
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.method.MethodAuthorizationContext;
|
||||||
|
import org.springframework.security.access.prepost.PostAuthorize;
|
||||||
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
|
import org.springframework.security.authorization.AuthorizationManager;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link AuthorizationManager} which can determine if an {@link Authentication} has
|
||||||
|
* access to the {@link MethodInvocation} by evaluating an expression from the
|
||||||
|
* {@link PostAuthorize} annotation.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
* @since 5.5
|
||||||
|
*/
|
||||||
|
public final class PostAuthorizeAuthorizationManager implements AuthorizationManager<MethodAuthorizationContext> {
|
||||||
|
|
||||||
|
private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
|
||||||
|
|
||||||
|
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link MethodSecurityExpressionHandler}.
|
||||||
|
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
|
||||||
|
*/
|
||||||
|
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
|
||||||
|
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
|
||||||
|
this.expressionHandler = expressionHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if an {@link Authentication} has access to the {@link MethodInvocation}
|
||||||
|
* by evaluating an expression from the {@link PostAuthorize} annotation.
|
||||||
|
* @param authentication the {@link Supplier} of the {@link Authentication} to check
|
||||||
|
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
|
||||||
|
* @return an {@link AuthorizationDecision} or null if the {@link PostAuthorize}
|
||||||
|
* annotation is not present
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public AuthorizationDecision check(Supplier<Authentication> authentication,
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext) {
|
||||||
|
ExpressionAttribute attribute = this.registry.getAttribute(methodAuthorizationContext);
|
||||||
|
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(),
|
||||||
|
methodAuthorizationContext.getMethodInvocation());
|
||||||
|
this.expressionHandler.setReturnObject(methodAuthorizationContext.getReturnObject(), ctx);
|
||||||
|
boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);
|
||||||
|
return new AuthorizationDecision(granted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class PostAuthorizeExpressionAttributeRegistry
|
||||||
|
extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
|
||||||
|
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
|
||||||
|
PostAuthorize postAuthorize = findPostAuthorizeAnnotation(specificMethod);
|
||||||
|
if (postAuthorize == null) {
|
||||||
|
return ExpressionAttribute.NULL_ATTRIBUTE;
|
||||||
|
}
|
||||||
|
Expression postAuthorizeExpression = PostAuthorizeAuthorizationManager.this.expressionHandler
|
||||||
|
.getExpressionParser().parseExpression(postAuthorize.value());
|
||||||
|
return new ExpressionAttribute(postAuthorizeExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PostAuthorize findPostAuthorizeAnnotation(Method method) {
|
||||||
|
PostAuthorize postAuthorize = AnnotationUtils.findAnnotation(method, PostAuthorize.class);
|
||||||
|
return (postAuthorize != null) ? postAuthorize
|
||||||
|
: AnnotationUtils.findAnnotation(method.getDeclaringClass(), PostAuthorize.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* 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.authorization.method;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
|
|
||||||
|
import org.springframework.aop.MethodMatcher;
|
||||||
|
import org.springframework.aop.support.AopUtils;
|
||||||
|
import org.springframework.aop.support.StaticMethodMatcher;
|
||||||
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
|
import org.springframework.expression.EvaluationContext;
|
||||||
|
import org.springframework.expression.Expression;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.method.AuthorizationMethodAfterAdvice;
|
||||||
|
import org.springframework.security.access.method.MethodAuthorizationContext;
|
||||||
|
import org.springframework.security.access.prepost.PostFilter;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link AuthorizationMethodAfterAdvice} which filters a <code>returnedObject</code>
|
||||||
|
* from the {@link MethodInvocation} by evaluating an expression from the
|
||||||
|
* {@link PostFilter} annotation.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
* @since 5.5
|
||||||
|
*/
|
||||||
|
public final class PostFilterAuthorizationMethodAfterAdvice
|
||||||
|
implements AuthorizationMethodAfterAdvice<MethodAuthorizationContext> {
|
||||||
|
|
||||||
|
private final PostFilterExpressionAttributeRegistry registry = new PostFilterExpressionAttributeRegistry();
|
||||||
|
|
||||||
|
private final MethodMatcher methodMatcher = new StaticMethodMatcher() {
|
||||||
|
@Override
|
||||||
|
public boolean matches(Method method, Class<?> targetClass) {
|
||||||
|
return PostFilterAuthorizationMethodAfterAdvice.this.registry.getAttribute(method,
|
||||||
|
targetClass) != ExpressionAttribute.NULL_ATTRIBUTE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link MethodSecurityExpressionHandler}.
|
||||||
|
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
|
||||||
|
*/
|
||||||
|
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
|
||||||
|
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
|
||||||
|
this.expressionHandler = expressionHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodMatcher getMethodMatcher() {
|
||||||
|
return this.methodMatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters a <code>returnedObject</code> from the {@link MethodInvocation} by
|
||||||
|
* evaluating an expression from the {@link PostFilter} annotation.
|
||||||
|
* @param authentication the {@link Supplier} of the {@link Authentication} to check
|
||||||
|
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
|
||||||
|
* @param returnedObject the returned object from the {@link MethodInvocation} to
|
||||||
|
* check
|
||||||
|
* @return filtered <code>returnedObject</code> from the {@link MethodInvocation}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Object after(Supplier<Authentication> authentication, MethodAuthorizationContext methodAuthorizationContext,
|
||||||
|
Object returnedObject) {
|
||||||
|
if (returnedObject == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ExpressionAttribute attribute = this.registry.getAttribute(methodAuthorizationContext);
|
||||||
|
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
|
||||||
|
return returnedObject;
|
||||||
|
}
|
||||||
|
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(),
|
||||||
|
methodAuthorizationContext.getMethodInvocation());
|
||||||
|
Object result = this.expressionHandler.filter(returnedObject, attribute.getExpression(), ctx);
|
||||||
|
methodAuthorizationContext.setReturnObject(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class PostFilterExpressionAttributeRegistry
|
||||||
|
extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
|
||||||
|
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
|
||||||
|
PostFilter postFilter = findPostFilterAnnotation(specificMethod);
|
||||||
|
if (postFilter == null) {
|
||||||
|
return ExpressionAttribute.NULL_ATTRIBUTE;
|
||||||
|
}
|
||||||
|
Expression postFilterExpression = PostFilterAuthorizationMethodAfterAdvice.this.expressionHandler
|
||||||
|
.getExpressionParser().parseExpression(postFilter.value());
|
||||||
|
return new ExpressionAttribute(postFilterExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PostFilter findPostFilterAnnotation(Method method) {
|
||||||
|
PostFilter postFilter = AnnotationUtils.findAnnotation(method, PostFilter.class);
|
||||||
|
return (postFilter != null) ? postFilter
|
||||||
|
: AnnotationUtils.findAnnotation(method.getDeclaringClass(), PostFilter.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* 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.authorization.method;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
|
import reactor.util.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.springframework.aop.support.AopUtils;
|
||||||
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
|
import org.springframework.expression.EvaluationContext;
|
||||||
|
import org.springframework.expression.Expression;
|
||||||
|
import org.springframework.security.access.expression.ExpressionUtils;
|
||||||
|
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.method.MethodAuthorizationContext;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
|
import org.springframework.security.authorization.AuthorizationManager;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link AuthorizationManager} which can determine if an {@link Authentication} has
|
||||||
|
* access to the {@link MethodInvocation} by evaluating an expression from the
|
||||||
|
* {@link PreAuthorize} annotation.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
* @since 5.5
|
||||||
|
*/
|
||||||
|
public final class PreAuthorizeAuthorizationManager implements AuthorizationManager<MethodAuthorizationContext> {
|
||||||
|
|
||||||
|
private final PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();
|
||||||
|
|
||||||
|
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link MethodSecurityExpressionHandler}.
|
||||||
|
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
|
||||||
|
*/
|
||||||
|
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
|
||||||
|
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
|
||||||
|
this.expressionHandler = expressionHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if an {@link Authentication} has access to the {@link MethodInvocation}
|
||||||
|
* by evaluating an expression from the {@link PreAuthorize} annotation.
|
||||||
|
* @param authentication the {@link Supplier} of the {@link Authentication} to check
|
||||||
|
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
|
||||||
|
* @return an {@link AuthorizationDecision} or null if the {@link PreAuthorize}
|
||||||
|
* annotation is not present
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public AuthorizationDecision check(Supplier<Authentication> authentication,
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext) {
|
||||||
|
ExpressionAttribute attribute = this.registry.getAttribute(methodAuthorizationContext);
|
||||||
|
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(),
|
||||||
|
methodAuthorizationContext.getMethodInvocation());
|
||||||
|
boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);
|
||||||
|
return new AuthorizationDecision(granted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class PreAuthorizeExpressionAttributeRegistry
|
||||||
|
extends AbstractExpressionAttributeRegistry<ExpressionAttribute> {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
ExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
|
||||||
|
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
|
||||||
|
PreAuthorize preAuthorize = findPreAuthorizeAnnotation(specificMethod);
|
||||||
|
if (preAuthorize == null) {
|
||||||
|
return ExpressionAttribute.NULL_ATTRIBUTE;
|
||||||
|
}
|
||||||
|
Expression preAuthorizeExpression = PreAuthorizeAuthorizationManager.this.expressionHandler
|
||||||
|
.getExpressionParser().parseExpression(preAuthorize.value());
|
||||||
|
return new ExpressionAttribute(preAuthorizeExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PreAuthorize findPreAuthorizeAnnotation(Method method) {
|
||||||
|
PreAuthorize preAuthorize = AnnotationUtils.findAnnotation(method, PreAuthorize.class);
|
||||||
|
return (preAuthorize != null) ? preAuthorize
|
||||||
|
: AnnotationUtils.findAnnotation(method.getDeclaringClass(), PreAuthorize.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* 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.authorization.method;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
|
|
||||||
|
import org.springframework.aop.MethodMatcher;
|
||||||
|
import org.springframework.aop.support.AopUtils;
|
||||||
|
import org.springframework.aop.support.StaticMethodMatcher;
|
||||||
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
|
import org.springframework.expression.EvaluationContext;
|
||||||
|
import org.springframework.expression.Expression;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.method.AuthorizationMethodBeforeAdvice;
|
||||||
|
import org.springframework.security.access.method.MethodAuthorizationContext;
|
||||||
|
import org.springframework.security.access.prepost.PreFilter;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link AuthorizationMethodBeforeAdvice} which filters a method argument by
|
||||||
|
* evaluating an expression from the {@link PreFilter} annotation.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
* @since 5.5
|
||||||
|
*/
|
||||||
|
public final class PreFilterAuthorizationMethodBeforeAdvice
|
||||||
|
implements AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> {
|
||||||
|
|
||||||
|
private final PreFilterExpressionAttributeRegistry registry = new PreFilterExpressionAttributeRegistry();
|
||||||
|
|
||||||
|
private final MethodMatcher methodMatcher = new StaticMethodMatcher() {
|
||||||
|
@Override
|
||||||
|
public boolean matches(Method method, Class<?> targetClass) {
|
||||||
|
return PreFilterAuthorizationMethodBeforeAdvice.this.registry.getAttribute(method,
|
||||||
|
targetClass) != PreFilterExpressionAttribute.NULL_ATTRIBUTE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link MethodSecurityExpressionHandler}.
|
||||||
|
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
|
||||||
|
*/
|
||||||
|
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
|
||||||
|
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
|
||||||
|
this.expressionHandler = expressionHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodMatcher getMethodMatcher() {
|
||||||
|
return this.methodMatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters a method argument by evaluating an expression from the {@link PreFilter}
|
||||||
|
* annotation.
|
||||||
|
* @param authentication the {@link Supplier} of the {@link Authentication} to check
|
||||||
|
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void before(Supplier<Authentication> authentication, MethodAuthorizationContext methodAuthorizationContext) {
|
||||||
|
PreFilterExpressionAttribute attribute = this.registry.getAttribute(methodAuthorizationContext);
|
||||||
|
if (attribute == PreFilterExpressionAttribute.NULL_ATTRIBUTE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MethodInvocation mi = methodAuthorizationContext.getMethodInvocation();
|
||||||
|
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(), mi);
|
||||||
|
Object filterTarget = findFilterTarget(attribute.filterTarget, ctx, mi);
|
||||||
|
this.expressionHandler.filter(filterTarget, attribute.getExpression(), ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object findFilterTarget(String filterTargetName, EvaluationContext ctx, MethodInvocation methodInvocation) {
|
||||||
|
Object filterTarget;
|
||||||
|
if (StringUtils.hasText(filterTargetName)) {
|
||||||
|
filterTarget = ctx.lookupVariable(filterTargetName);
|
||||||
|
Assert.notNull(filterTarget, () -> "Filter target was null, or no argument with name '" + filterTargetName
|
||||||
|
+ "' found in method.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Object[] arguments = methodInvocation.getArguments();
|
||||||
|
Assert.state(arguments.length == 1,
|
||||||
|
"Unable to determine the method argument for filtering. Specify the filter target.");
|
||||||
|
filterTarget = arguments[0];
|
||||||
|
Assert.notNull(filterTarget,
|
||||||
|
"Filter target was null. Make sure you passing the correct value in the method argument.");
|
||||||
|
}
|
||||||
|
Assert.state(!filterTarget.getClass().isArray(),
|
||||||
|
"Pre-filtering on array types is not supported. Using a Collection will solve this problem.");
|
||||||
|
return filterTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class PreFilterExpressionAttributeRegistry
|
||||||
|
extends AbstractExpressionAttributeRegistry<PreFilterExpressionAttribute> {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
PreFilterExpressionAttribute resolveAttribute(Method method, Class<?> targetClass) {
|
||||||
|
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
|
||||||
|
PreFilter preFilter = findPreFilterAnnotation(specificMethod);
|
||||||
|
if (preFilter == null) {
|
||||||
|
return PreFilterExpressionAttribute.NULL_ATTRIBUTE;
|
||||||
|
}
|
||||||
|
Expression preFilterExpression = PreFilterAuthorizationMethodBeforeAdvice.this.expressionHandler
|
||||||
|
.getExpressionParser().parseExpression(preFilter.value());
|
||||||
|
return new PreFilterExpressionAttribute(preFilterExpression, preFilter.filterTarget());
|
||||||
|
}
|
||||||
|
|
||||||
|
private PreFilter findPreFilterAnnotation(Method method) {
|
||||||
|
PreFilter preFilter = AnnotationUtils.findAnnotation(method, PreFilter.class);
|
||||||
|
return (preFilter != null) ? preFilter
|
||||||
|
: AnnotationUtils.findAnnotation(method.getDeclaringClass(), PreFilter.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class PreFilterExpressionAttribute extends ExpressionAttribute {
|
||||||
|
|
||||||
|
private static final PreFilterExpressionAttribute NULL_ATTRIBUTE = new PreFilterExpressionAttribute(null, null);
|
||||||
|
|
||||||
|
private final String filterTarget;
|
||||||
|
|
||||||
|
private PreFilterExpressionAttribute(Expression expression, String filterTarget) {
|
||||||
|
super(expression);
|
||||||
|
this.filterTarget = filterTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,167 @@
|
|||||||
|
/*
|
||||||
|
* 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.access.annotation;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import javax.annotation.security.DenyAll;
|
||||||
|
import javax.annotation.security.PermitAll;
|
||||||
|
import javax.annotation.security.RolesAllowed;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.security.access.intercept.method.MockMethodInvocation;
|
||||||
|
import org.springframework.security.access.method.MethodAuthorizationContext;
|
||||||
|
import org.springframework.security.authentication.TestAuthentication;
|
||||||
|
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||||
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link Jsr250AuthorizationManager}.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
*/
|
||||||
|
public class Jsr250AuthorizationManagerTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rolePrefixWhenNotSetThenDefaultsToRole() {
|
||||||
|
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
|
||||||
|
assertThat(manager).extracting("rolePrefix").isEqualTo("ROLE_");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setRolePrefixWhenNullThenException() {
|
||||||
|
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
|
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> manager.setRolePrefix(null))
|
||||||
|
.withMessage("rolePrefix cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setRolePrefixWhenNotNullThenSets() {
|
||||||
|
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
|
||||||
|
manager.setRolePrefix("CUSTOM_");
|
||||||
|
assertThat(manager).extracting("rolePrefix").isEqualTo("CUSTOM_");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkDoSomethingWhenNoJsr250AnnotationsThenNullDecision() throws Exception {
|
||||||
|
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"doSomething");
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
|
||||||
|
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
|
||||||
|
methodAuthorizationContext);
|
||||||
|
assertThat(decision).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkPermitAllRolesAllowedAdminWhenRoleUserThenGrantedDecision() throws Exception {
|
||||||
|
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"permitAllRolesAllowedAdmin");
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
|
||||||
|
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
|
||||||
|
methodAuthorizationContext);
|
||||||
|
assertThat(decision).isNotNull();
|
||||||
|
assertThat(decision.isGranted()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkDenyAllRolesAllowedAdminWhenRoleAdminThenDeniedDecision() throws Exception {
|
||||||
|
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"denyAllRolesAllowedAdmin");
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
|
||||||
|
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedAdmin,
|
||||||
|
methodAuthorizationContext);
|
||||||
|
assertThat(decision).isNotNull();
|
||||||
|
assertThat(decision.isGranted()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkRolesAllowedUserOrAdminWhenRoleUserThenGrantedDecision() throws Exception {
|
||||||
|
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"rolesAllowedUserOrAdmin");
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
|
||||||
|
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
|
||||||
|
methodAuthorizationContext);
|
||||||
|
assertThat(decision).isNotNull();
|
||||||
|
assertThat(decision.isGranted()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkRolesAllowedUserOrAdminWhenRoleAdminThenGrantedDecision() throws Exception {
|
||||||
|
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"rolesAllowedUserOrAdmin");
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
|
||||||
|
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedAdmin,
|
||||||
|
methodAuthorizationContext);
|
||||||
|
assertThat(decision).isNotNull();
|
||||||
|
assertThat(decision.isGranted()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkRolesAllowedUserOrAdminWhenRoleAnonymousThenDeniedDecision() throws Exception {
|
||||||
|
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password",
|
||||||
|
"ROLE_ANONYMOUS");
|
||||||
|
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"rolesAllowedUserOrAdmin");
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
|
||||||
|
AuthorizationDecision decision = manager.check(authentication, methodAuthorizationContext);
|
||||||
|
assertThat(decision).isNotNull();
|
||||||
|
assertThat(decision.isGranted()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TestClass {
|
||||||
|
|
||||||
|
public void doSomething() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@DenyAll
|
||||||
|
@RolesAllowed("ADMIN")
|
||||||
|
public void denyAllRolesAllowedAdmin() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@PermitAll
|
||||||
|
@RolesAllowed("ADMIN")
|
||||||
|
public void permitAllRolesAllowedAdmin() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@RolesAllowed({ "USER", "ADMIN" })
|
||||||
|
public void rolesAllowedUserOrAdmin() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* 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.access.annotation;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.security.access.intercept.method.MockMethodInvocation;
|
||||||
|
import org.springframework.security.access.method.MethodAuthorizationContext;
|
||||||
|
import org.springframework.security.authentication.TestAuthentication;
|
||||||
|
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||||
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link SecuredAuthorizationManager}.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
*/
|
||||||
|
public class SecuredAuthorizationManagerTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkDoSomethingWhenNoSecuredAnnotationThenNullDecision() throws Exception {
|
||||||
|
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"doSomething");
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
|
||||||
|
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
|
||||||
|
methodAuthorizationContext);
|
||||||
|
assertThat(decision).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkSecuredUserOrAdminWhenRoleUserThenGrantedDecision() throws Exception {
|
||||||
|
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"securedUserOrAdmin");
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
|
||||||
|
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
|
||||||
|
methodAuthorizationContext);
|
||||||
|
assertThat(decision).isNotNull();
|
||||||
|
assertThat(decision.isGranted()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkSecuredUserOrAdminWhenRoleAdminThenGrantedDecision() throws Exception {
|
||||||
|
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"securedUserOrAdmin");
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
|
||||||
|
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedAdmin,
|
||||||
|
methodAuthorizationContext);
|
||||||
|
assertThat(decision).isNotNull();
|
||||||
|
assertThat(decision.isGranted()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkSecuredUserOrAdminWhenRoleAnonymousThenDeniedDecision() throws Exception {
|
||||||
|
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password",
|
||||||
|
"ROLE_ANONYMOUS");
|
||||||
|
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"securedUserOrAdmin");
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(methodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
|
||||||
|
AuthorizationDecision decision = manager.check(authentication, methodAuthorizationContext);
|
||||||
|
assertThat(decision).isNotNull();
|
||||||
|
assertThat(decision.isGranted()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TestClass {
|
||||||
|
|
||||||
|
public void doSomething() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Secured({ "ROLE_USER", "ROLE_ADMIN" })
|
||||||
|
public void securedUserOrAdmin() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* 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.access.intercept.aopalliance;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.aop.MethodMatcher;
|
||||||
|
import org.springframework.security.access.intercept.method.MockMethodInvocation;
|
||||||
|
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.authentication.AuthenticationCredentialsNotFoundException;
|
||||||
|
import org.springframework.security.authentication.TestAuthentication;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.context.SecurityContextImpl;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link AuthorizationMethodInterceptor}.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
*/
|
||||||
|
public class AuthorizationMethodInterceptorTests {
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
SecurityContextHolder.clearContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void invokeWhenAuthenticatedThenVerifyAdvicesUsage() throws Throwable {
|
||||||
|
Authentication authentication = TestAuthentication.authenticatedUser();
|
||||||
|
SecurityContextHolder.setContext(new SecurityContextImpl(authentication));
|
||||||
|
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"doSomethingString");
|
||||||
|
AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> mockBeforeAdvice = mock(
|
||||||
|
AuthorizationMethodBeforeAdvice.class);
|
||||||
|
AuthorizationMethodAfterAdvice<MethodAuthorizationContext> mockAfterAdvice = mock(
|
||||||
|
AuthorizationMethodAfterAdvice.class);
|
||||||
|
given(mockAfterAdvice.after(any(), any(MethodAuthorizationContext.class), eq(null))).willReturn("abc");
|
||||||
|
AuthorizationMethodInterceptor interceptor = new AuthorizationMethodInterceptor(mockBeforeAdvice,
|
||||||
|
mockAfterAdvice);
|
||||||
|
Object result = interceptor.invoke(mockMethodInvocation);
|
||||||
|
assertThat(result).isEqualTo("abc");
|
||||||
|
verify(mockAfterAdvice).after(any(), any(MethodAuthorizationContext.class), eq(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void invokeWhenNotAuthenticatedThenAuthenticationCredentialsNotFoundException() throws Exception {
|
||||||
|
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"doSomethingString");
|
||||||
|
AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> beforeAdvice = new AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>() {
|
||||||
|
@Override
|
||||||
|
public MethodMatcher getMethodMatcher() {
|
||||||
|
return MethodMatcher.TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void before(Supplier<Authentication> authentication,
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext) {
|
||||||
|
authentication.get();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
AuthorizationMethodAfterAdvice<MethodAuthorizationContext> mockAfterAdvice = mock(
|
||||||
|
AuthorizationMethodAfterAdvice.class);
|
||||||
|
AuthorizationMethodInterceptor interceptor = new AuthorizationMethodInterceptor(beforeAdvice, mockAfterAdvice);
|
||||||
|
assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class)
|
||||||
|
.isThrownBy(() -> interceptor.invoke(mockMethodInvocation))
|
||||||
|
.withMessage("An Authentication object was not found in the SecurityContext");
|
||||||
|
verifyNoInteractions(mockAfterAdvice);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TestClass {
|
||||||
|
|
||||||
|
public String doSomethingString() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* 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.access.method;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.aop.MethodMatcher;
|
||||||
|
import org.springframework.security.authentication.TestAuthentication;
|
||||||
|
import org.springframework.security.authorization.AuthorizationManager;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link AuthorizationManagerMethodAfterAdvice}.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
*/
|
||||||
|
public class AuthorizationManagerMethodAfterAdviceTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void instantiateWhenMethodMatcherNullThenException() {
|
||||||
|
assertThatIllegalArgumentException()
|
||||||
|
.isThrownBy(() -> new AuthorizationManagerMethodAfterAdvice<>(null, mock(AuthorizationManager.class)))
|
||||||
|
.withMessage("methodMatcher cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void instantiateWhenAuthorizationManagerNullThenException() {
|
||||||
|
assertThatIllegalArgumentException()
|
||||||
|
.isThrownBy(() -> new AuthorizationManagerMethodAfterAdvice<>(mock(MethodMatcher.class), null))
|
||||||
|
.withMessage("authorizationManager cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void beforeWhenMockAuthorizationManagerThenVerifyAndReturnedObject() {
|
||||||
|
Supplier<Authentication> authentication = TestAuthentication::authenticatedUser;
|
||||||
|
MethodInvocation mockMethodInvocation = mock(MethodInvocation.class);
|
||||||
|
Object returnedObject = new Object();
|
||||||
|
AuthorizationManager<MethodInvocation> mockAuthorizationManager = mock(AuthorizationManager.class);
|
||||||
|
AuthorizationManagerMethodAfterAdvice<MethodInvocation> advice = new AuthorizationManagerMethodAfterAdvice<>(
|
||||||
|
mock(MethodMatcher.class), mockAuthorizationManager);
|
||||||
|
Object result = advice.after(authentication, mockMethodInvocation, returnedObject);
|
||||||
|
assertThat(result).isEqualTo(returnedObject);
|
||||||
|
verify(mockAuthorizationManager).verify(authentication, mockMethodInvocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* 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.access.method;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.aop.MethodMatcher;
|
||||||
|
import org.springframework.security.authentication.TestAuthentication;
|
||||||
|
import org.springframework.security.authorization.AuthorizationManager;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link AuthorizationManagerMethodBeforeAdvice}.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
*/
|
||||||
|
public class AuthorizationManagerMethodBeforeAdviceTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void instantiateWhenMethodMatcherNullThenException() {
|
||||||
|
assertThatIllegalArgumentException()
|
||||||
|
.isThrownBy(() -> new AuthorizationManagerMethodBeforeAdvice<>(null, mock(AuthorizationManager.class)))
|
||||||
|
.withMessage("methodMatcher cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void instantiateWhenAuthorizationManagerNullThenException() {
|
||||||
|
assertThatIllegalArgumentException()
|
||||||
|
.isThrownBy(() -> new AuthorizationManagerMethodBeforeAdvice<>(mock(MethodMatcher.class), null))
|
||||||
|
.withMessage("authorizationManager cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void beforeWhenMockAuthorizationManagerThenVerify() {
|
||||||
|
Supplier<Authentication> authentication = TestAuthentication::authenticatedUser;
|
||||||
|
MethodInvocation mockMethodInvocation = mock(MethodInvocation.class);
|
||||||
|
AuthorizationManager<MethodInvocation> mockAuthorizationManager = mock(AuthorizationManager.class);
|
||||||
|
AuthorizationManagerMethodBeforeAdvice<MethodInvocation> advice = new AuthorizationManagerMethodBeforeAdvice<>(
|
||||||
|
mock(MethodMatcher.class), mockAuthorizationManager);
|
||||||
|
advice.before(authentication, mockMethodInvocation);
|
||||||
|
verify(mockAuthorizationManager).verify(authentication, mockMethodInvocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,164 @@
|
|||||||
|
/*
|
||||||
|
* 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.access.method;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.aop.MethodMatcher;
|
||||||
|
import org.springframework.aop.support.StaticMethodMatcher;
|
||||||
|
import org.springframework.security.access.intercept.method.MockMethodInvocation;
|
||||||
|
import org.springframework.security.authentication.TestAuthentication;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link DelegatingAuthorizationMethodAfterAdvice}.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
*/
|
||||||
|
public class DelegatingAuthorizationMethodAfterAdviceTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void methodMatcherWhenNoneMatchesThenNotMatches() throws Exception {
|
||||||
|
List<AuthorizationMethodAfterAdvice<MethodAuthorizationContext>> delegates = new ArrayList<>();
|
||||||
|
delegates.add(new AuthorizationMethodAfterAdvice<MethodAuthorizationContext>() {
|
||||||
|
@Override
|
||||||
|
public Object after(Supplier<Authentication> authentication, MethodAuthorizationContext object,
|
||||||
|
Object returnedObject) {
|
||||||
|
return returnedObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodMatcher getMethodMatcher() {
|
||||||
|
return new StaticMethodMatcher() {
|
||||||
|
@Override
|
||||||
|
public boolean matches(Method method, Class<?> targetClass) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
delegates.add(new AuthorizationMethodAfterAdvice<MethodAuthorizationContext>() {
|
||||||
|
@Override
|
||||||
|
public Object after(Supplier<Authentication> authentication, MethodAuthorizationContext object,
|
||||||
|
Object returnedObject) {
|
||||||
|
return returnedObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodMatcher getMethodMatcher() {
|
||||||
|
return new StaticMethodMatcher() {
|
||||||
|
@Override
|
||||||
|
public boolean matches(Method method, Class<?> targetClass) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
DelegatingAuthorizationMethodAfterAdvice advice = new DelegatingAuthorizationMethodAfterAdvice(delegates);
|
||||||
|
MethodMatcher methodMatcher = advice.getMethodMatcher();
|
||||||
|
assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomething"), TestClass.class)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void methodMatcherWhenAnyMatchesThenMatches() throws Exception {
|
||||||
|
List<AuthorizationMethodAfterAdvice<MethodAuthorizationContext>> delegates = new ArrayList<>();
|
||||||
|
delegates.add(new AuthorizationMethodAfterAdvice<MethodAuthorizationContext>() {
|
||||||
|
@Override
|
||||||
|
public Object after(Supplier<Authentication> authentication, MethodAuthorizationContext object,
|
||||||
|
Object returnedObject) {
|
||||||
|
return returnedObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodMatcher getMethodMatcher() {
|
||||||
|
return new StaticMethodMatcher() {
|
||||||
|
@Override
|
||||||
|
public boolean matches(Method method, Class<?> targetClass) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
delegates.add(new AuthorizationMethodAfterAdvice<MethodAuthorizationContext>() {
|
||||||
|
@Override
|
||||||
|
public Object after(Supplier<Authentication> authentication, MethodAuthorizationContext object,
|
||||||
|
Object returnedObject) {
|
||||||
|
return returnedObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodMatcher getMethodMatcher() {
|
||||||
|
return MethodMatcher.TRUE;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
DelegatingAuthorizationMethodAfterAdvice advice = new DelegatingAuthorizationMethodAfterAdvice(delegates);
|
||||||
|
MethodMatcher methodMatcher = advice.getMethodMatcher();
|
||||||
|
assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomething"), TestClass.class)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkWhenDelegatingAdviceModifiesReturnedObjectThenModifiedReturnedObject() throws Exception {
|
||||||
|
List<AuthorizationMethodAfterAdvice<MethodAuthorizationContext>> delegates = new ArrayList<>();
|
||||||
|
delegates.add(new AuthorizationMethodAfterAdvice<MethodAuthorizationContext>() {
|
||||||
|
@Override
|
||||||
|
public Object after(Supplier<Authentication> authentication, MethodAuthorizationContext object,
|
||||||
|
Object returnedObject) {
|
||||||
|
return returnedObject + "b";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodMatcher getMethodMatcher() {
|
||||||
|
return MethodMatcher.TRUE;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
delegates.add(new AuthorizationMethodAfterAdvice<MethodAuthorizationContext>() {
|
||||||
|
@Override
|
||||||
|
public Object after(Supplier<Authentication> authentication, MethodAuthorizationContext object,
|
||||||
|
Object returnedObject) {
|
||||||
|
return returnedObject + "c";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodMatcher getMethodMatcher() {
|
||||||
|
return MethodMatcher.TRUE;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"doSomething");
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
DelegatingAuthorizationMethodAfterAdvice advice = new DelegatingAuthorizationMethodAfterAdvice(delegates);
|
||||||
|
Object result = advice.after(TestAuthentication::authenticatedUser, methodAuthorizationContext, "a");
|
||||||
|
assertThat(result).isEqualTo("abc");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TestClass {
|
||||||
|
|
||||||
|
public String doSomething() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,168 @@
|
|||||||
|
/*
|
||||||
|
* 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.access.method;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.aop.MethodMatcher;
|
||||||
|
import org.springframework.aop.support.StaticMethodMatcher;
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
|
import org.springframework.security.access.intercept.method.MockMethodInvocation;
|
||||||
|
import org.springframework.security.authentication.TestAuthentication;
|
||||||
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link DelegatingAuthorizationMethodBeforeAdvice}.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
*/
|
||||||
|
public class DelegatingAuthorizationMethodBeforeAdviceTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void methodMatcherWhenNoneMatchesThenNotMatches() throws Exception {
|
||||||
|
List<AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>> delegates = new ArrayList<>();
|
||||||
|
delegates.add(new AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>() {
|
||||||
|
@Override
|
||||||
|
public MethodMatcher getMethodMatcher() {
|
||||||
|
return new StaticMethodMatcher() {
|
||||||
|
@Override
|
||||||
|
public boolean matches(Method method, Class<?> targetClass) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void before(Supplier<Authentication> authentication, MethodAuthorizationContext object) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
delegates.add(new AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>() {
|
||||||
|
@Override
|
||||||
|
public MethodMatcher getMethodMatcher() {
|
||||||
|
return new StaticMethodMatcher() {
|
||||||
|
@Override
|
||||||
|
public boolean matches(Method method, Class<?> targetClass) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void before(Supplier<Authentication> authentication, MethodAuthorizationContext object) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
DelegatingAuthorizationMethodBeforeAdvice advice = new DelegatingAuthorizationMethodBeforeAdvice(delegates);
|
||||||
|
MethodMatcher methodMatcher = advice.getMethodMatcher();
|
||||||
|
assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomething"), TestClass.class)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void methodMatcherWhenAnyMatchesThenMatches() throws Exception {
|
||||||
|
List<AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>> delegates = new ArrayList<>();
|
||||||
|
delegates.add(new AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>() {
|
||||||
|
@Override
|
||||||
|
public MethodMatcher getMethodMatcher() {
|
||||||
|
return new StaticMethodMatcher() {
|
||||||
|
@Override
|
||||||
|
public boolean matches(Method method, Class<?> targetClass) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void before(Supplier<Authentication> authentication, MethodAuthorizationContext object) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
delegates.add(new AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>() {
|
||||||
|
@Override
|
||||||
|
public MethodMatcher getMethodMatcher() {
|
||||||
|
return MethodMatcher.TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void before(Supplier<Authentication> authentication, MethodAuthorizationContext object) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
DelegatingAuthorizationMethodBeforeAdvice advice = new DelegatingAuthorizationMethodBeforeAdvice(delegates);
|
||||||
|
MethodMatcher methodMatcher = advice.getMethodMatcher();
|
||||||
|
assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomething"), TestClass.class)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkWhenAllGrantsOrAbstainsThenPasses() throws Exception {
|
||||||
|
List<AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>> delegates = new ArrayList<>();
|
||||||
|
delegates.add(new AuthorizationManagerMethodBeforeAdvice<>(MethodMatcher.TRUE, (a, o) -> null));
|
||||||
|
delegates.add(new AuthorizationManagerMethodBeforeAdvice<>(MethodMatcher.TRUE,
|
||||||
|
(a, o) -> new AuthorizationDecision(true)));
|
||||||
|
delegates.add(new AuthorizationManagerMethodBeforeAdvice<>(MethodMatcher.TRUE, (a, o) -> null));
|
||||||
|
DelegatingAuthorizationMethodBeforeAdvice advice = new DelegatingAuthorizationMethodBeforeAdvice(delegates);
|
||||||
|
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"doSomething");
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkWhenAnyDeniesThenAccessDeniedException() throws Exception {
|
||||||
|
List<AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>> delegates = new ArrayList<>();
|
||||||
|
delegates.add(new AuthorizationManagerMethodBeforeAdvice<>(MethodMatcher.TRUE, (a, o) -> null));
|
||||||
|
delegates.add(new AuthorizationManagerMethodBeforeAdvice<>(MethodMatcher.TRUE,
|
||||||
|
(a, o) -> new AuthorizationDecision(true)));
|
||||||
|
delegates.add(new AuthorizationManagerMethodBeforeAdvice<>(MethodMatcher.TRUE,
|
||||||
|
(a, o) -> new AuthorizationDecision(false)));
|
||||||
|
DelegatingAuthorizationMethodBeforeAdvice advice = new DelegatingAuthorizationMethodBeforeAdvice(delegates);
|
||||||
|
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"doSomething");
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
assertThatExceptionOfType(AccessDeniedException.class)
|
||||||
|
.isThrownBy(() -> advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext))
|
||||||
|
.withMessage("Access Denied");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkWhenDelegatesEmptyThenPasses() throws Exception {
|
||||||
|
DelegatingAuthorizationMethodBeforeAdvice advice = new DelegatingAuthorizationMethodBeforeAdvice(
|
||||||
|
Collections.emptyList());
|
||||||
|
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"doSomething");
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TestClass {
|
||||||
|
|
||||||
|
public void doSomething() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2020 the original author or authors.
|
* Copyright 2002-2021 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -64,6 +64,13 @@ public class AuthorityAuthorizationManagerTests {
|
|||||||
.withMessage("roles cannot contain null values");
|
.withMessage("roles cannot contain null values");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void hasAnyRoleWhenCustomRolePrefixNullThenException() {
|
||||||
|
assertThatIllegalArgumentException()
|
||||||
|
.isThrownBy(() -> AuthorityAuthorizationManager.hasAnyRole(null, new String[] { "ADMIN", "USER" }))
|
||||||
|
.withMessage("rolePrefix cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void hasAnyAuthorityWhenNullThenException() {
|
public void hasAnyAuthorityWhenNullThenException() {
|
||||||
assertThatIllegalArgumentException().isThrownBy(() -> AuthorityAuthorizationManager.hasAnyAuthority(null))
|
assertThatIllegalArgumentException().isThrownBy(() -> AuthorityAuthorizationManager.hasAnyAuthority(null))
|
||||||
@ -147,6 +154,17 @@ public class AuthorityAuthorizationManagerTests {
|
|||||||
assertThat(manager.check(authentication, object).isGranted()).isFalse();
|
assertThat(manager.check(authentication, object).isGranted()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void hasAnyRoleWhenCustomRolePrefixProvidedThenUseCustomRolePrefix() {
|
||||||
|
AuthorityAuthorizationManager<Object> manager = AuthorityAuthorizationManager.hasAnyRole("CUSTOM_",
|
||||||
|
new String[] { "USER" });
|
||||||
|
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password",
|
||||||
|
"CUSTOM_USER");
|
||||||
|
Object object = new Object();
|
||||||
|
|
||||||
|
assertThat(manager.check(authentication, object).isGranted()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void hasAnyAuthorityWhenUserHasAnyAuthorityThenGrantedDecision() {
|
public void hasAnyAuthorityWhenUserHasAnyAuthorityThenGrantedDecision() {
|
||||||
AuthorityAuthorizationManager<Object> manager = AuthorityAuthorizationManager.hasAnyAuthority("ADMIN", "USER");
|
AuthorityAuthorizationManager<Object> manager = AuthorityAuthorizationManager.hasAnyAuthority("ADMIN", "USER");
|
||||||
|
@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
* 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.authorization.method;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.intercept.method.MockMethodInvocation;
|
||||||
|
import org.springframework.security.access.method.MethodAuthorizationContext;
|
||||||
|
import org.springframework.security.access.prepost.PostAuthorize;
|
||||||
|
import org.springframework.security.authentication.TestAuthentication;
|
||||||
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link PostAuthorizeAuthorizationManager}.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
*/
|
||||||
|
public class PostAuthorizeAuthorizationManagerTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setExpressionHandlerWhenNotNullThenSetsExpressionHandler() {
|
||||||
|
MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
||||||
|
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
|
||||||
|
manager.setExpressionHandler(expressionHandler);
|
||||||
|
assertThat(manager).extracting("expressionHandler").isEqualTo(expressionHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setExpressionHandlerWhenNullThenException() {
|
||||||
|
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
|
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> manager.setExpressionHandler(null))
|
||||||
|
.withMessage("expressionHandler cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkDoSomethingWhenNoPostAuthorizeAnnotationThenNullDecision() throws Exception {
|
||||||
|
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"doSomething", new Class[] {}, new Object[] {});
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
|
||||||
|
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
|
||||||
|
methodAuthorizationContext);
|
||||||
|
assertThat(decision).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkDoSomethingStringWhenArgIsGrantThenGrantedDecision() throws Exception {
|
||||||
|
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"doSomethingString", new Class[] { String.class }, new Object[] { "grant" });
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
|
||||||
|
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
|
||||||
|
methodAuthorizationContext);
|
||||||
|
assertThat(decision).isNotNull();
|
||||||
|
assertThat(decision.isGranted()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkDoSomethingStringWhenArgIsNotGrantThenDeniedDecision() throws Exception {
|
||||||
|
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"doSomethingString", new Class[] { String.class }, new Object[] { "deny" });
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
|
||||||
|
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
|
||||||
|
methodAuthorizationContext);
|
||||||
|
assertThat(decision).isNotNull();
|
||||||
|
assertThat(decision.isGranted()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkDoSomethingListWhenReturnObjectContainsGrantThenGrantedDecision() throws Exception {
|
||||||
|
List<String> list = Arrays.asList("grant", "deny");
|
||||||
|
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"doSomethingList", new Class[] { List.class }, new Object[] { list });
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
methodAuthorizationContext.setReturnObject(list);
|
||||||
|
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
|
||||||
|
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
|
||||||
|
methodAuthorizationContext);
|
||||||
|
assertThat(decision).isNotNull();
|
||||||
|
assertThat(decision.isGranted()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkDoSomethingListWhenReturnObjectNotContainsGrantThenDeniedDecision() throws Exception {
|
||||||
|
List<String> list = Collections.singletonList("deny");
|
||||||
|
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"doSomethingList", new Class[] { List.class }, new Object[] { list });
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
methodAuthorizationContext.setReturnObject(list);
|
||||||
|
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
|
||||||
|
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
|
||||||
|
methodAuthorizationContext);
|
||||||
|
assertThat(decision).isNotNull();
|
||||||
|
assertThat(decision.isGranted()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TestClass {
|
||||||
|
|
||||||
|
public void doSomething() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostAuthorize("#s == 'grant'")
|
||||||
|
public String doSomethingString(String s) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostAuthorize("returnObject.contains('grant')")
|
||||||
|
public List<String> doSomethingList(List<String> list) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* 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.authorization.method;
|
||||||
|
|
||||||
|
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.aop.MethodMatcher;
|
||||||
|
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.intercept.method.MockMethodInvocation;
|
||||||
|
import org.springframework.security.access.method.MethodAuthorizationContext;
|
||||||
|
import org.springframework.security.access.prepost.PostFilter;
|
||||||
|
import org.springframework.security.authentication.TestAuthentication;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link PostFilterAuthorizationMethodAfterAdvice}.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
*/
|
||||||
|
public class PostFilterAuthorizationMethodAfterAdviceTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setExpressionHandlerWhenNotNullThenSetsExpressionHandler() {
|
||||||
|
MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
||||||
|
PostFilterAuthorizationMethodAfterAdvice advice = new PostFilterAuthorizationMethodAfterAdvice();
|
||||||
|
advice.setExpressionHandler(expressionHandler);
|
||||||
|
assertThat(advice).extracting("expressionHandler").isEqualTo(expressionHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setExpressionHandlerWhenNullThenException() {
|
||||||
|
PostFilterAuthorizationMethodAfterAdvice advice = new PostFilterAuthorizationMethodAfterAdvice();
|
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> advice.setExpressionHandler(null))
|
||||||
|
.withMessage("expressionHandler cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void methodMatcherWhenMethodHasNotPostFilterAnnotationThenNotMatches() throws Exception {
|
||||||
|
PostFilterAuthorizationMethodAfterAdvice advice = new PostFilterAuthorizationMethodAfterAdvice();
|
||||||
|
MethodMatcher methodMatcher = advice.getMethodMatcher();
|
||||||
|
assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomething"), TestClass.class)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void methodMatcherWhenMethodHasPostFilterAnnotationThenMatches() throws Exception {
|
||||||
|
PostFilterAuthorizationMethodAfterAdvice advice = new PostFilterAuthorizationMethodAfterAdvice();
|
||||||
|
MethodMatcher methodMatcher = advice.getMethodMatcher();
|
||||||
|
assertThat(
|
||||||
|
methodMatcher.matches(TestClass.class.getMethod("doSomethingArray", String[].class), TestClass.class))
|
||||||
|
.isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void afterWhenArrayNotNullThenFilteredArray() throws Exception {
|
||||||
|
String[] array = { "john", "bob" };
|
||||||
|
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"doSomethingArray", new Class[] { String[].class }, new Object[] { array });
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
PostFilterAuthorizationMethodAfterAdvice advice = new PostFilterAuthorizationMethodAfterAdvice();
|
||||||
|
Object result = advice.after(TestAuthentication::authenticatedUser, methodAuthorizationContext, array);
|
||||||
|
assertThat(result).asInstanceOf(InstanceOfAssertFactories.array(String[].class)).containsOnly("john");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TestClass {
|
||||||
|
|
||||||
|
public void doSomething() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostFilter("filterObject == 'john'")
|
||||||
|
public String[] doSomethingArray(String[] array) {
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* 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.authorization.method;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.intercept.method.MockMethodInvocation;
|
||||||
|
import org.springframework.security.access.method.MethodAuthorizationContext;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.authentication.TestAuthentication;
|
||||||
|
import org.springframework.security.authorization.AuthorizationDecision;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link PreAuthorizeAuthorizationManager}.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
*/
|
||||||
|
public class PreAuthorizeAuthorizationManagerTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setExpressionHandlerWhenNotNullThenSetsExpressionHandler() {
|
||||||
|
MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
||||||
|
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
|
||||||
|
manager.setExpressionHandler(expressionHandler);
|
||||||
|
assertThat(manager).extracting("expressionHandler").isEqualTo(expressionHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setExpressionHandlerWhenNullThenException() {
|
||||||
|
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
|
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> manager.setExpressionHandler(null))
|
||||||
|
.withMessage("expressionHandler cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkDoSomethingWhenNoPostAuthorizeAnnotationThenNullDecision() throws Exception {
|
||||||
|
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"doSomething", new Class[] {}, new Object[] {});
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
|
||||||
|
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
|
||||||
|
methodAuthorizationContext);
|
||||||
|
assertThat(decision).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkDoSomethingStringWhenArgIsGrantThenGrantedDecision() throws Exception {
|
||||||
|
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"doSomethingString", new Class[] { String.class }, new Object[] { "grant" });
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
|
||||||
|
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
|
||||||
|
methodAuthorizationContext);
|
||||||
|
assertThat(decision).isNotNull();
|
||||||
|
assertThat(decision.isGranted()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkDoSomethingStringWhenArgIsNotGrantThenDeniedDecision() throws Exception {
|
||||||
|
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"doSomethingString", new Class[] { String.class }, new Object[] { "deny" });
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
|
||||||
|
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
|
||||||
|
methodAuthorizationContext);
|
||||||
|
assertThat(decision).isNotNull();
|
||||||
|
assertThat(decision.isGranted()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TestClass {
|
||||||
|
|
||||||
|
public void doSomething() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("#s == 'grant'")
|
||||||
|
public String doSomethingString(String s) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,200 @@
|
|||||||
|
/*
|
||||||
|
* 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.authorization.method;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.aop.MethodMatcher;
|
||||||
|
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.intercept.method.MockMethodInvocation;
|
||||||
|
import org.springframework.security.access.method.MethodAuthorizationContext;
|
||||||
|
import org.springframework.security.access.prepost.PreFilter;
|
||||||
|
import org.springframework.security.authentication.TestAuthentication;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link PreFilterAuthorizationMethodBeforeAdvice}.
|
||||||
|
*
|
||||||
|
* @author Evgeniy Cheban
|
||||||
|
*/
|
||||||
|
public class PreFilterAuthorizationMethodBeforeAdviceTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setExpressionHandlerWhenNotNullThenSetsExpressionHandler() {
|
||||||
|
MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
||||||
|
PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
|
||||||
|
advice.setExpressionHandler(expressionHandler);
|
||||||
|
assertThat(advice).extracting("expressionHandler").isEqualTo(expressionHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setExpressionHandlerWhenNullThenException() {
|
||||||
|
PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
|
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> advice.setExpressionHandler(null))
|
||||||
|
.withMessage("expressionHandler cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void methodMatcherWhenMethodHasNotPreFilterAnnotationThenNotMatches() throws Exception {
|
||||||
|
PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
|
||||||
|
MethodMatcher methodMatcher = advice.getMethodMatcher();
|
||||||
|
assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomething"), TestClass.class)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void methodMatcherWhenMethodHasPreFilterAnnotationThenMatches() throws Exception {
|
||||||
|
PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
|
||||||
|
MethodMatcher methodMatcher = advice.getMethodMatcher();
|
||||||
|
assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomethingListFilterTargetMatch", List.class),
|
||||||
|
TestClass.class)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findFilterTargetWhenNameProvidedAndNotMatchThenException() throws Exception {
|
||||||
|
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"doSomethingListFilterTargetNotMatch", new Class[] { List.class }, new Object[] { new ArrayList<>() });
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
|
||||||
|
assertThatIllegalArgumentException()
|
||||||
|
.isThrownBy(() -> advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext))
|
||||||
|
.withMessage(
|
||||||
|
"Filter target was null, or no argument with name 'filterTargetNotMatch' found in method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findFilterTargetWhenNameProvidedAndMatchAndNullThenException() throws Exception {
|
||||||
|
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"doSomethingListFilterTargetMatch", new Class[] { List.class }, new Object[] { null });
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
|
||||||
|
assertThatIllegalArgumentException()
|
||||||
|
.isThrownBy(() -> advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext))
|
||||||
|
.withMessage("Filter target was null, or no argument with name 'list' found in method.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findFilterTargetWhenNameProvidedAndMatchAndNotNullThenFiltersList() throws Exception {
|
||||||
|
List<String> list = new ArrayList<>();
|
||||||
|
list.add("john");
|
||||||
|
list.add("bob");
|
||||||
|
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"doSomethingListFilterTargetMatch", new Class[] { List.class }, new Object[] { list });
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
|
||||||
|
advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext);
|
||||||
|
assertThat(list).hasSize(1);
|
||||||
|
assertThat(list.get(0)).isEqualTo("john");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findFilterTargetWhenNameNotProvidedAndSingleArgListNullThenException() throws Exception {
|
||||||
|
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"doSomethingListFilterTargetNotProvided", new Class[] { List.class }, new Object[] { null });
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
|
||||||
|
assertThatIllegalArgumentException()
|
||||||
|
.isThrownBy(() -> advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext))
|
||||||
|
.withMessage("Filter target was null. Make sure you passing the correct value in the method argument.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findFilterTargetWhenNameNotProvidedAndSingleArgListThenFiltersList() throws Exception {
|
||||||
|
List<String> list = new ArrayList<>();
|
||||||
|
list.add("john");
|
||||||
|
list.add("bob");
|
||||||
|
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"doSomethingListFilterTargetNotProvided", new Class[] { List.class }, new Object[] { list });
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
|
||||||
|
advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext);
|
||||||
|
assertThat(list).hasSize(1);
|
||||||
|
assertThat(list.get(0)).isEqualTo("john");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findFilterTargetWhenNameNotProvidedAndSingleArgArrayThenException() throws Exception {
|
||||||
|
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"doSomethingArrayFilterTargetNotProvided", new Class[] { String[].class },
|
||||||
|
new Object[] { new String[] {} });
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
|
||||||
|
assertThatIllegalStateException()
|
||||||
|
.isThrownBy(() -> advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext))
|
||||||
|
.withMessage(
|
||||||
|
"Pre-filtering on array types is not supported. Using a Collection will solve this problem.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findFilterTargetWhenNameNotProvidedAndNotSingleArgThenException() throws Exception {
|
||||||
|
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||||
|
"doSomethingTwoArgsFilterTargetNotProvided", new Class[] { String.class, List.class },
|
||||||
|
new Object[] { "", new ArrayList<>() });
|
||||||
|
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
|
||||||
|
TestClass.class);
|
||||||
|
PreFilterAuthorizationMethodBeforeAdvice advice = new PreFilterAuthorizationMethodBeforeAdvice();
|
||||||
|
assertThatIllegalStateException()
|
||||||
|
.isThrownBy(() -> advice.before(TestAuthentication::authenticatedUser, methodAuthorizationContext))
|
||||||
|
.withMessage("Unable to determine the method argument for filtering. Specify the filter target.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TestClass {
|
||||||
|
|
||||||
|
public void doSomething() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreFilter(value = "filterObject == 'john'", filterTarget = "filterTargetNotMatch")
|
||||||
|
public List<String> doSomethingListFilterTargetNotMatch(List<String> list) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreFilter(value = "filterObject == 'john'", filterTarget = "list")
|
||||||
|
public List<String> doSomethingListFilterTargetMatch(List<String> list) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreFilter("filterObject == 'john'")
|
||||||
|
public List<String> doSomethingListFilterTargetNotProvided(List<String> list) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreFilter("filterObject == 'john'")
|
||||||
|
public String[] doSomethingArrayFilterTargetNotProvided(String[] array) {
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreFilter("filterObject == 'john'")
|
||||||
|
public List<String> doSomethingTwoArgsFilterTargetNotProvided(String s, List<String> list) {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user