Polish AuthorizationManager Method Security
- Removed consolidated pointcut advisor in favor of each interceptor being an advisor. This allows Spring AOP to do more of the heavy lifting of selecting the set of interceptors that applies - Created new method context for after interceptors instead of modifying existing one - Added documentation - Added XML support - Added AuthorizationInterceptorsOrder to simplify interceptor ordering - Adjusted annotation lookup to comply with JSR-250 spec - Adjusted annotation lookup to exhaustively search for duplicate annotations - Separated into three @Configuration classes, one for each set of authorization annotations Issue gh-9289
This commit is contained in:
parent
84e2e80915
commit
67e5c05a47
|
@ -87,6 +87,8 @@ public abstract class Elements {
|
|||
|
||||
public static final String GLOBAL_METHOD_SECURITY = "global-method-security";
|
||||
|
||||
public static final String METHOD_SECURITY = "method-security";
|
||||
|
||||
public static final String PASSWORD_ENCODER = "password-encoder";
|
||||
|
||||
public static final String PORT_MAPPINGS = "port-mappings";
|
||||
|
|
|
@ -44,6 +44,7 @@ import org.springframework.security.config.ldap.LdapServerBeanDefinitionParser;
|
|||
import org.springframework.security.config.ldap.LdapUserServiceBeanDefinitionParser;
|
||||
import org.springframework.security.config.method.GlobalMethodSecurityBeanDefinitionParser;
|
||||
import org.springframework.security.config.method.InterceptMethodsBeanDefinitionDecorator;
|
||||
import org.springframework.security.config.method.MethodSecurityBeanDefinitionParser;
|
||||
import org.springframework.security.config.method.MethodSecurityMetadataSourceBeanDefinitionParser;
|
||||
import org.springframework.security.config.oauth2.client.ClientRegistrationsBeanDefinitionParser;
|
||||
import org.springframework.security.config.websocket.WebSocketMessageBrokerSecurityBeanDefinitionParser;
|
||||
|
@ -169,6 +170,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
|
|||
this.parsers.put(Elements.JDBC_USER_SERVICE, new JdbcUserServiceBeanDefinitionParser());
|
||||
this.parsers.put(Elements.AUTHENTICATION_PROVIDER, new AuthenticationProviderBeanDefinitionParser());
|
||||
this.parsers.put(Elements.GLOBAL_METHOD_SECURITY, new GlobalMethodSecurityBeanDefinitionParser());
|
||||
this.parsers.put(Elements.METHOD_SECURITY, new MethodSecurityBeanDefinitionParser());
|
||||
this.parsers.put(Elements.AUTHENTICATION_MANAGER, new AuthenticationManagerBeanDefinitionParser());
|
||||
this.parsers.put(Elements.METHOD_SECURITY_METADATA_SOURCE,
|
||||
new MethodSecurityMetadataSourceBeanDefinitionParser());
|
||||
|
|
|
@ -25,13 +25,17 @@ 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;
|
||||
import org.springframework.security.access.prepost.PostAuthorize;
|
||||
import org.springframework.security.access.prepost.PostFilter;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.access.prepost.PreFilter;
|
||||
|
||||
/**
|
||||
* Enables Spring Security Method Security.
|
||||
* @author Evgeniy Cheban
|
||||
* @since 5.5
|
||||
* @author Josh Cummings
|
||||
* @since 5.6
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
|
@ -40,6 +44,14 @@ import org.springframework.security.access.annotation.Secured;
|
|||
@Configuration
|
||||
public @interface EnableMethodSecurity {
|
||||
|
||||
/**
|
||||
* Determines if Spring Security's {@link PreAuthorize}, {@link PostAuthorize},
|
||||
* {@link PreFilter}, and {@link PostFilter} annotations should be enabled. Default is
|
||||
* true.
|
||||
* @return true if pre/post annotations should be enabled false otherwise
|
||||
*/
|
||||
boolean prePostEnabled() default true;
|
||||
|
||||
/**
|
||||
* Determines if Spring Security's {@link Secured} annotation should be enabled.
|
||||
* Default is false.
|
||||
|
@ -76,12 +88,4 @@ public @interface EnableMethodSecurity {
|
|||
*/
|
||||
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,54 @@
|
|||
/*
|
||||
* 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 org.springframework.aop.Advisor;
|
||||
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.Role;
|
||||
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
|
||||
import org.springframework.security.authorization.method.Jsr250AuthorizationManager;
|
||||
import org.springframework.security.config.core.GrantedAuthorityDefaults;
|
||||
|
||||
/**
|
||||
* {@link Configuration} for enabling JSR-250 Spring Security Method Security.
|
||||
*
|
||||
* @author Evgeniy Cheban
|
||||
* @author Josh Cummings
|
||||
* @see EnableMethodSecurity
|
||||
* @since 5.6
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
final class Jsr250MethodSecurityConfiguration {
|
||||
|
||||
private final Jsr250AuthorizationManager jsr250AuthorizationManager = new Jsr250AuthorizationManager();
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
Advisor jsr250AuthorizationMethodInterceptor() {
|
||||
return AuthorizationManagerBeforeMethodInterceptor.jsr250(this.jsr250AuthorizationManager);
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
void setGrantedAuthorityDefaults(GrantedAuthorityDefaults grantedAuthorityDefaults) {
|
||||
this.jsr250AuthorizationManager.setRolePrefix(grantedAuthorityDefaults.getRolePrefix());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,252 +0,0 @@
|
|||
/*
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -17,34 +17,59 @@
|
|||
package org.springframework.security.config.annotation.method.configuration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.context.annotation.AdviceMode;
|
||||
import org.springframework.context.annotation.AdviceModeImportSelector;
|
||||
import org.springframework.context.annotation.AutoProxyRegistrar;
|
||||
import org.springframework.context.annotation.ImportSelector;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
* Dynamically determines which imports to include using the {@link EnableMethodSecurity}
|
||||
* annotation.
|
||||
*
|
||||
* @author Evgeniy Cheban
|
||||
* @since 5.5
|
||||
* @author Josh Cummings
|
||||
* @since 5.6
|
||||
*/
|
||||
final class MethodSecuritySelector extends AdviceModeImportSelector<EnableMethodSecurity> {
|
||||
final class MethodSecuritySelector implements ImportSelector {
|
||||
|
||||
private final ImportSelector autoProxy = new AutoProxyRegistrarSelector();
|
||||
|
||||
@Override
|
||||
protected String[] selectImports(AdviceMode adviceMode) {
|
||||
if (adviceMode == AdviceMode.PROXY) {
|
||||
return getProxyImports();
|
||||
public String[] selectImports(@NonNull AnnotationMetadata importMetadata) {
|
||||
if (!importMetadata.hasAnnotation(EnableMethodSecurity.class.getName())) {
|
||||
return new String[0];
|
||||
}
|
||||
throw new IllegalStateException("AdviceMode '" + adviceMode + "' is not supported");
|
||||
EnableMethodSecurity annotation = importMetadata.getAnnotations().get(EnableMethodSecurity.class).synthesize();
|
||||
List<String> imports = new ArrayList<>(Arrays.asList(this.autoProxy.selectImports(importMetadata)));
|
||||
if (annotation.prePostEnabled()) {
|
||||
imports.add(PrePostMethodSecurityConfiguration.class.getName());
|
||||
}
|
||||
if (annotation.securedEnabled()) {
|
||||
imports.add(SecuredMethodSecurityConfiguration.class.getName());
|
||||
}
|
||||
if (annotation.jsr250Enabled()) {
|
||||
imports.add(Jsr250MethodSecurityConfiguration.class.getName());
|
||||
}
|
||||
return imports.toArray(new String[0]);
|
||||
}
|
||||
|
||||
private String[] getProxyImports() {
|
||||
List<String> result = new ArrayList<>();
|
||||
result.add(AutoProxyRegistrar.class.getName());
|
||||
result.add(MethodSecurityConfiguration.class.getName());
|
||||
return result.toArray(new String[0]);
|
||||
private static final class AutoProxyRegistrarSelector extends AdviceModeImportSelector<EnableMethodSecurity> {
|
||||
|
||||
private static final String[] IMPORTS = new String[] { AutoProxyRegistrar.class.getName() };
|
||||
|
||||
@Override
|
||||
protected String[] selectImports(@NonNull AdviceMode adviceMode) {
|
||||
if (adviceMode == AdviceMode.PROXY) {
|
||||
return IMPORTS;
|
||||
}
|
||||
throw new IllegalStateException("AdviceMode '" + adviceMode + "' is not supported");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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 org.springframework.aop.Advisor;
|
||||
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.Role;
|
||||
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
||||
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||
import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor;
|
||||
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
|
||||
import org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager;
|
||||
import org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor;
|
||||
import org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager;
|
||||
import org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor;
|
||||
import org.springframework.security.config.core.GrantedAuthorityDefaults;
|
||||
|
||||
/**
|
||||
* Base {@link Configuration} for enabling Spring Security Method Security.
|
||||
*
|
||||
* @author Evgeniy Cheban
|
||||
* @author Josh Cummings
|
||||
* @see EnableMethodSecurity
|
||||
* @since 5.6
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
final class PrePostMethodSecurityConfiguration {
|
||||
|
||||
private final PreFilterAuthorizationMethodInterceptor preFilterAuthorizationMethodInterceptor = new PreFilterAuthorizationMethodInterceptor();
|
||||
|
||||
private final PreAuthorizeAuthorizationManager preAuthorizeAuthorizationManager = new PreAuthorizeAuthorizationManager();
|
||||
|
||||
private final PostAuthorizeAuthorizationManager postAuthorizeAuthorizationManager = new PostAuthorizeAuthorizationManager();
|
||||
|
||||
private final PostFilterAuthorizationMethodInterceptor postFilterAuthorizationMethodInterceptor = new PostFilterAuthorizationMethodInterceptor();
|
||||
|
||||
private boolean customMethodSecurityExpressionHandler = false;
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
Advisor preFilterAuthorizationMethodInterceptor() {
|
||||
return this.preFilterAuthorizationMethodInterceptor;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
Advisor preAuthorizeAuthorizationMethodInterceptor() {
|
||||
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(this.preAuthorizeAuthorizationManager);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
Advisor postAuthorizeAuthorizationMethodInterceptor() {
|
||||
return AuthorizationManagerAfterMethodInterceptor.postAuthorize(this.postAuthorizeAuthorizationManager);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
Advisor postFilterAuthorizationMethodInterceptor() {
|
||||
return this.postFilterAuthorizationMethodInterceptor;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
void setMethodSecurityExpressionHandler(MethodSecurityExpressionHandler methodSecurityExpressionHandler) {
|
||||
this.customMethodSecurityExpressionHandler = true;
|
||||
this.preFilterAuthorizationMethodInterceptor.setExpressionHandler(methodSecurityExpressionHandler);
|
||||
this.preAuthorizeAuthorizationManager.setExpressionHandler(methodSecurityExpressionHandler);
|
||||
this.postAuthorizeAuthorizationManager.setExpressionHandler(methodSecurityExpressionHandler);
|
||||
this.postFilterAuthorizationMethodInterceptor.setExpressionHandler(methodSecurityExpressionHandler);
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
void setGrantedAuthorityDefaults(GrantedAuthorityDefaults grantedAuthorityDefaults) {
|
||||
if (this.customMethodSecurityExpressionHandler) {
|
||||
return;
|
||||
}
|
||||
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
||||
expressionHandler.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix());
|
||||
this.preFilterAuthorizationMethodInterceptor.setExpressionHandler(expressionHandler);
|
||||
this.preAuthorizeAuthorizationManager.setExpressionHandler(expressionHandler);
|
||||
this.postAuthorizeAuthorizationManager.setExpressionHandler(expressionHandler);
|
||||
this.postFilterAuthorizationMethodInterceptor.setExpressionHandler(expressionHandler);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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 org.springframework.aop.Advisor;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Role;
|
||||
import org.springframework.security.access.annotation.Secured;
|
||||
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
|
||||
|
||||
/**
|
||||
* {@link Configuration} for enabling {@link Secured} Spring Security Method Security.
|
||||
*
|
||||
* @author Evgeniy Cheban
|
||||
* @author Josh Cummings
|
||||
* @see EnableMethodSecurity
|
||||
* @since 5.6
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
final class SecuredMethodSecurityConfiguration {
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
Advisor securedAuthorizationMethodInterceptor() {
|
||||
return AuthorizationManagerBeforeMethodInterceptor.secured();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
* Copyright 2002-2018 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.method;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.aop.config.AopNamespaceUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
||||
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||
import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor;
|
||||
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
|
||||
import org.springframework.security.authorization.method.Jsr250AuthorizationManager;
|
||||
import org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager;
|
||||
import org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor;
|
||||
import org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager;
|
||||
import org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor;
|
||||
import org.springframework.security.config.Elements;
|
||||
import org.springframework.security.config.core.GrantedAuthorityDefaults;
|
||||
import org.springframework.util.xml.DomUtils;
|
||||
|
||||
/**
|
||||
* Processes the top-level "method-security" element.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 5.6
|
||||
*/
|
||||
public class MethodSecurityBeanDefinitionParser implements BeanDefinitionParser {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private static final String ATT_USE_JSR250 = "jsr250-enabled";
|
||||
|
||||
private static final String ATT_USE_SECURED = "secured-enabled";
|
||||
|
||||
private static final String ATT_USE_PREPOST = "pre-post-enabled";
|
||||
|
||||
private static final String ATT_REF = "ref";
|
||||
|
||||
@Override
|
||||
public BeanDefinition parse(Element element, ParserContext pc) {
|
||||
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(),
|
||||
pc.extractSource(element));
|
||||
pc.pushContainingComponent(compositeDef);
|
||||
boolean prePostAnnotationsEnabled = !element.hasAttribute(ATT_USE_PREPOST)
|
||||
|| "true".equals(element.getAttribute(ATT_USE_PREPOST));
|
||||
if (prePostAnnotationsEnabled) {
|
||||
BeanDefinitionBuilder preFilterInterceptor = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(PreFilterAuthorizationMethodInterceptor.class)
|
||||
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
BeanDefinitionBuilder preAuthorizeInterceptor = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(PreAuthorizeAuthorizationMethodInterceptor.class)
|
||||
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
BeanDefinitionBuilder postAuthorizeInterceptor = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(PostAuthorizeAuthorizationMethodInterceptor.class)
|
||||
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
BeanDefinitionBuilder postFilterInterceptor = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(PostFilterAuthorizationMethodInterceptor.class)
|
||||
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
Element expressionHandlerElt = DomUtils.getChildElementByTagName(element, Elements.EXPRESSION_HANDLER);
|
||||
if (expressionHandlerElt != null) {
|
||||
String expressionHandlerRef = expressionHandlerElt.getAttribute(ATT_REF);
|
||||
preFilterInterceptor.addPropertyReference("expressionHandler", expressionHandlerRef);
|
||||
preAuthorizeInterceptor.addPropertyReference("expressionHandler", expressionHandlerRef);
|
||||
postAuthorizeInterceptor.addPropertyReference("expressionHandler", expressionHandlerRef);
|
||||
postFilterInterceptor.addPropertyReference("expressionHandler", expressionHandlerRef);
|
||||
}
|
||||
else {
|
||||
BeanDefinition expressionHandler = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(MethodSecurityExpressionHandlerBean.class).getBeanDefinition();
|
||||
preFilterInterceptor.addPropertyValue("expressionHandler", expressionHandler);
|
||||
preAuthorizeInterceptor.addPropertyValue("expressionHandler", expressionHandler);
|
||||
postAuthorizeInterceptor.addPropertyValue("expressionHandler", expressionHandler);
|
||||
postFilterInterceptor.addPropertyValue("expressionHandler", expressionHandler);
|
||||
}
|
||||
pc.getRegistry().registerBeanDefinition("preFilterAuthorizationMethodInterceptor",
|
||||
preFilterInterceptor.getBeanDefinition());
|
||||
pc.getRegistry().registerBeanDefinition("preAuthorizeAuthorizationMethodInterceptor",
|
||||
preAuthorizeInterceptor.getBeanDefinition());
|
||||
pc.getRegistry().registerBeanDefinition("postAuthorizeAuthorizationMethodInterceptor",
|
||||
postAuthorizeInterceptor.getBeanDefinition());
|
||||
pc.getRegistry().registerBeanDefinition("postFilterAuthorizationMethodInterceptor",
|
||||
postFilterInterceptor.getBeanDefinition());
|
||||
}
|
||||
boolean securedEnabled = "true".equals(element.getAttribute(ATT_USE_SECURED));
|
||||
if (securedEnabled) {
|
||||
BeanDefinitionBuilder securedInterceptor = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(AuthorizationManagerBeforeMethodInterceptor.class)
|
||||
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).setFactoryMethod("secured");
|
||||
pc.getRegistry().registerBeanDefinition("securedAuthorizationMethodInterceptor",
|
||||
securedInterceptor.getBeanDefinition());
|
||||
}
|
||||
boolean jsr250Enabled = "true".equals(element.getAttribute(ATT_USE_JSR250));
|
||||
if (jsr250Enabled) {
|
||||
BeanDefinitionBuilder jsr250Interceptor = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(Jsr250AuthorizationMethodInterceptor.class)
|
||||
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
||||
pc.getRegistry().registerBeanDefinition("jsr250AuthorizationMethodInterceptor",
|
||||
jsr250Interceptor.getBeanDefinition());
|
||||
}
|
||||
AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(pc, element);
|
||||
pc.popAndRegisterContainingComponent();
|
||||
return null;
|
||||
}
|
||||
|
||||
public static final class MethodSecurityExpressionHandlerBean
|
||||
implements FactoryBean<MethodSecurityExpressionHandler>, ApplicationContextAware {
|
||||
|
||||
private final DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
||||
|
||||
@Override
|
||||
public MethodSecurityExpressionHandler getObject() {
|
||||
return this.expressionHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return MethodSecurityExpressionHandler.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
String[] grantedAuthorityDefaultsBeanNames = applicationContext
|
||||
.getBeanNamesForType(GrantedAuthorityDefaults.class);
|
||||
if (grantedAuthorityDefaultsBeanNames.length == 1) {
|
||||
GrantedAuthorityDefaults grantedAuthorityDefaults = applicationContext
|
||||
.getBean(grantedAuthorityDefaultsBeanNames[0], GrantedAuthorityDefaults.class);
|
||||
this.expressionHandler.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final class Jsr250AuthorizationMethodInterceptor
|
||||
implements FactoryBean<AuthorizationManagerBeforeMethodInterceptor>, ApplicationContextAware {
|
||||
|
||||
private final Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
|
||||
|
||||
@Override
|
||||
public AuthorizationManagerBeforeMethodInterceptor getObject() {
|
||||
return AuthorizationManagerBeforeMethodInterceptor.jsr250(this.manager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return AuthorizationManagerBeforeMethodInterceptor.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
String[] grantedAuthorityDefaultsBeanNames = applicationContext
|
||||
.getBeanNamesForType(GrantedAuthorityDefaults.class);
|
||||
if (grantedAuthorityDefaultsBeanNames.length == 1) {
|
||||
GrantedAuthorityDefaults grantedAuthorityDefaults = applicationContext
|
||||
.getBean(grantedAuthorityDefaultsBeanNames[0], GrantedAuthorityDefaults.class);
|
||||
this.manager.setRolePrefix(grantedAuthorityDefaults.getRolePrefix());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final class PreAuthorizeAuthorizationMethodInterceptor
|
||||
implements FactoryBean<AuthorizationManagerBeforeMethodInterceptor> {
|
||||
|
||||
private final PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
|
||||
|
||||
@Override
|
||||
public AuthorizationManagerBeforeMethodInterceptor getObject() {
|
||||
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(this.manager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return AuthorizationManagerBeforeMethodInterceptor.class;
|
||||
}
|
||||
|
||||
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
|
||||
this.manager.setExpressionHandler(expressionHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final class PostAuthorizeAuthorizationMethodInterceptor
|
||||
implements FactoryBean<AuthorizationManagerAfterMethodInterceptor> {
|
||||
|
||||
private final PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
|
||||
|
||||
@Override
|
||||
public AuthorizationManagerAfterMethodInterceptor getObject() {
|
||||
return AuthorizationManagerAfterMethodInterceptor.postAuthorize(this.manager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return AuthorizationManagerAfterMethodInterceptor.class;
|
||||
}
|
||||
|
||||
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
|
||||
this.manager.setExpressionHandler(expressionHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -196,6 +196,22 @@ msmds.attlist &= id?
|
|||
|
||||
msmds.attlist &= use-expressions?
|
||||
|
||||
method-security =
|
||||
## Provides method security for all beans registered in the Spring application context. Specifically, beans will be scanned for matches with Spring Security annotations. Where there is a match, the beans will automatically be proxied and security authorization applied to the methods accordingly. Interceptors are invoked in the order specified in AuthorizationInterceptorsOrder. Use can create your own interceptors using Spring AOP.
|
||||
element method-security {method-security.attlist, expression-handler?}
|
||||
method-security.attlist &=
|
||||
## Specifies whether the use of Spring Security's pre and post invocation annotations (@PreFilter, @PreAuthorize, @PostFilter, @PostAuthorize) should be enabled for this application context. Defaults to "true".
|
||||
attribute pre-post-enabled {xsd:boolean}?
|
||||
method-security.attlist &=
|
||||
## Specifies whether the use of Spring Security's @Secured annotations should be enabled for this application context. Defaults to "false".
|
||||
attribute secured-enabled {xsd:boolean}?
|
||||
method-security.attlist &=
|
||||
## Specifies whether JSR-250 style attributes are to be used (for example "RolesAllowed"). This will require the javax.annotation.security classes on the classpath. Defaults to "false".
|
||||
attribute jsr250-enabled {xsd:boolean}?
|
||||
method-security.attlist &=
|
||||
## If true, class-based proxying will be used instead of interface-based proxying.
|
||||
attribute proxy-target-class {xsd:boolean}?
|
||||
|
||||
global-method-security =
|
||||
## Provides method security for all beans registered in the Spring application context. Specifically, beans will be scanned for matches with the ordered list of "protect-pointcut" sub-elements, Spring Security annotations and/or. Where there is a match, the beans will automatically be proxied and security authorization applied to the methods accordingly. If you use and enable all four sources of method security metadata (ie "protect-pointcut" declarations, expression annotations, @Secured and also JSR250 security annotations), the metadata sources will be queried in that order. In practical terms, this enables you to use XML to override method security metadata expressed in annotations. If using annotations, the order of precedence is EL-based (@PreAuthorize etc.), @Secured and finally JSR-250.
|
||||
element global-method-security {global-method-security.attlist, (pre-post-annotation-handling | expression-handler)?, protect-pointcut*, after-invocation-provider*}
|
||||
|
|
|
@ -595,6 +595,63 @@
|
|||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:attributeGroup>
|
||||
<xs:element name="method-security">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Provides method security for all beans registered in the Spring application context.
|
||||
Specifically, beans will be scanned for matches with Spring Security annotations. Where
|
||||
there is a match, the beans will automatically be proxied and security authorization
|
||||
applied to the methods accordingly. Interceptors are invoked in the order specified in
|
||||
AuthorizationInterceptorsOrder. Use can create your own interceptors using Spring AOP.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element minOccurs="0" name="expression-handler">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Defines the SecurityExpressionHandler instance which will be used if expression-based
|
||||
access-control is enabled. A default implementation (with no ACL support) will be used if
|
||||
not supplied.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
<xs:complexType>
|
||||
<xs:attributeGroup ref="security:ref"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
<xs:attributeGroup ref="security:method-security.attlist"/>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:attributeGroup name="method-security.attlist">
|
||||
<xs:attribute name="pre-post-enabled" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Specifies whether the use of Spring Security's pre and post invocation annotations
|
||||
(@PreFilter, @PreAuthorize, @PostFilter, @PostAuthorize) should be enabled for this
|
||||
application context. Defaults to "true".
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="secured-enabled" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Specifies whether the use of Spring Security's @Secured annotations should be enabled for
|
||||
this application context. Defaults to "false".
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="jsr250-enabled" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Specifies whether JSR-250 style attributes are to be used (for example "RolesAllowed").
|
||||
This will require the javax.annotation.security classes on the classpath. Defaults to
|
||||
"false".
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="proxy-target-class" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>If true, class-based proxying will be used instead of interface-based proxying.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:attributeGroup>
|
||||
<xs:element name="global-method-security">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Provides method security for all beans registered in the Spring application context.
|
||||
|
|
|
@ -16,12 +16,16 @@
|
|||
|
||||
package org.springframework.security.config.annotation.method.configuration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.security.DenyAll;
|
||||
import javax.annotation.security.PermitAll;
|
||||
|
||||
import org.springframework.security.access.annotation.Secured;
|
||||
import org.springframework.security.access.prepost.PostAuthorize;
|
||||
import org.springframework.security.access.prepost.PostFilter;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.access.prepost.PreFilter;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.parameters.P;
|
||||
|
||||
|
@ -69,4 +73,15 @@ public interface MethodSecurityService {
|
|||
@PostAuthorize("#o?.contains('grant')")
|
||||
String postAnnotation(@P("o") String object);
|
||||
|
||||
@PreFilter("filterObject.length > 3")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@Secured("ROLE_USER")
|
||||
@PostFilter("filterObject.length > 5")
|
||||
@PostAuthorize("returnObject.size == 2")
|
||||
List<String> manyAnnotations(List<String> array);
|
||||
|
||||
@RequireUserRole
|
||||
@RequireAdminRole
|
||||
void repeatedAnnotations();
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package org.springframework.security.config.annotation.method.configuration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
|
@ -86,4 +88,13 @@ public class MethodSecurityServiceImpl implements MethodSecurityService {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> manyAnnotations(List<String> object) {
|
||||
return object;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void repeatedAnnotations() {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,31 +18,39 @@ package org.springframework.security.config.annotation.method.configuration;
|
|||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.aop.MethodMatcher;
|
||||
import org.springframework.aop.Advisor;
|
||||
import org.springframework.aop.support.DefaultPointcutAdvisor;
|
||||
import org.springframework.aop.support.JdkRegexpMethodPointcut;
|
||||
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.Role;
|
||||
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
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.BusinessServiceImpl;
|
||||
import org.springframework.security.access.annotation.ExpressionProtectedBusinessServiceImpl;
|
||||
import org.springframework.security.access.annotation.Jsr250BusinessServiceImpl;
|
||||
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.authorization.method.AuthorizationInterceptorsOrder;
|
||||
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
|
||||
import org.springframework.security.config.core.GrantedAuthorityDefaults;
|
||||
import org.springframework.security.config.test.SpringTestRule;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
|
||||
import org.springframework.security.test.context.support.WithAnonymousUser;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
|
@ -52,13 +60,14 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Tests for {@link MethodSecurityConfiguration}.
|
||||
* Tests for {@link PrePostMethodSecurityConfiguration}.
|
||||
*
|
||||
* @author Evgeniy Cheban
|
||||
* @author Josh Cummings
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SecurityTestExecutionListeners
|
||||
public class MethodSecurityConfigurationTests {
|
||||
public class PrePostMethodSecurityConfigurationTests {
|
||||
|
||||
@Rule
|
||||
public final SpringTestRule spring = new SpringTestRule();
|
||||
|
@ -103,7 +112,7 @@ public class MethodSecurityConfigurationTests {
|
|||
@WithMockUser
|
||||
@Test
|
||||
public void securedWhenRoleUserThenAccessDeniedException() {
|
||||
this.spring.register(MethodSecurityServiceConfig.class).autowire();
|
||||
this.spring.register(MethodSecurityServiceEnabledConfig.class).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::secured)
|
||||
.withMessage("Access Denied");
|
||||
}
|
||||
|
@ -119,7 +128,7 @@ public class MethodSecurityConfigurationTests {
|
|||
@WithMockUser(roles = "ADMIN")
|
||||
@Test
|
||||
public void securedUserWhenRoleAdminThenAccessDeniedException() {
|
||||
this.spring.register(MethodSecurityServiceConfig.class).autowire();
|
||||
this.spring.register(MethodSecurityServiceEnabledConfig.class).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::securedUser)
|
||||
.withMessage("Access Denied");
|
||||
}
|
||||
|
@ -147,6 +156,13 @@ public class MethodSecurityConfigurationTests {
|
|||
this.methodSecurityService.preAuthorizeAdmin();
|
||||
}
|
||||
|
||||
@WithMockUser(authorities = "PREFIX_ADMIN")
|
||||
@Test
|
||||
public void preAuthorizeAdminWhenRoleAdminAndCustomPrefixThenPasses() {
|
||||
this.spring.register(CustomGrantedAuthorityDefaultsConfig.class, MethodSecurityServiceConfig.class).autowire();
|
||||
this.methodSecurityService.preAuthorizeAdmin();
|
||||
}
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void postHasPermissionWhenParameterIsNotGrantThenAccessDeniedException() {
|
||||
|
@ -244,7 +260,7 @@ public class MethodSecurityConfigurationTests {
|
|||
@WithMockUser(roles = "ADMIN")
|
||||
@Test
|
||||
public void jsr250WhenRoleAdminThenAccessDeniedException() {
|
||||
this.spring.register(MethodSecurityServiceConfig.class).autowire();
|
||||
this.spring.register(MethodSecurityServiceEnabledConfig.class).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::jsr250)
|
||||
.withMessage("Access Denied");
|
||||
}
|
||||
|
@ -252,7 +268,7 @@ public class MethodSecurityConfigurationTests {
|
|||
@WithAnonymousUser
|
||||
@Test
|
||||
public void jsr250PermitAllWhenRoleAnonymousThenPasses() {
|
||||
this.spring.register(MethodSecurityServiceConfig.class).autowire();
|
||||
this.spring.register(MethodSecurityServiceEnabledConfig.class).autowire();
|
||||
String result = this.methodSecurityService.jsr250PermitAll();
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
|
@ -272,7 +288,70 @@ public class MethodSecurityConfigurationTests {
|
|||
this.businessService.rolesAllowedUser();
|
||||
}
|
||||
|
||||
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
|
||||
@WithMockUser(roles = { "ADMIN", "USER" })
|
||||
@Test
|
||||
public void manyAnnotationsWhenMeetsConditionsThenReturnsFilteredList() throws Exception {
|
||||
List<String> names = Arrays.asList("harold", "jonathan", "pete", "bo");
|
||||
this.spring.register(MethodSecurityServiceEnabledConfig.class).autowire();
|
||||
List<String> filtered = this.methodSecurityService.manyAnnotations(new ArrayList<>(names));
|
||||
assertThat(filtered).hasSize(2);
|
||||
assertThat(filtered).containsExactly("harold", "jonathan");
|
||||
}
|
||||
|
||||
// gh-4003
|
||||
// gh-4103
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void manyAnnotationsWhenUserThenFails() {
|
||||
List<String> names = Arrays.asList("harold", "jonathan", "pete", "bo");
|
||||
this.spring.register(MethodSecurityServiceEnabledConfig.class).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class)
|
||||
.isThrownBy(() -> this.methodSecurityService.manyAnnotations(new ArrayList<>(names)));
|
||||
}
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void manyAnnotationsWhenShortListThenFails() {
|
||||
List<String> names = Arrays.asList("harold", "jonathan", "pete");
|
||||
this.spring.register(MethodSecurityServiceEnabledConfig.class).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class)
|
||||
.isThrownBy(() -> this.methodSecurityService.manyAnnotations(new ArrayList<>(names)));
|
||||
}
|
||||
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
@Test
|
||||
public void manyAnnotationsWhenAdminThenFails() {
|
||||
List<String> names = Arrays.asList("harold", "jonathan", "pete", "bo");
|
||||
this.spring.register(MethodSecurityServiceEnabledConfig.class).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class)
|
||||
.isThrownBy(() -> this.methodSecurityService.manyAnnotations(new ArrayList<>(names)));
|
||||
}
|
||||
|
||||
// gh-3183
|
||||
@Test
|
||||
public void repeatedAnnotationsWhenPresentThenFails() {
|
||||
this.spring.register(MethodSecurityServiceConfig.class).autowire();
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> this.methodSecurityService.repeatedAnnotations());
|
||||
}
|
||||
|
||||
// gh-3183
|
||||
@Test
|
||||
public void repeatedJsr250AnnotationsWhenPresentThenFails() {
|
||||
this.spring.register(Jsr250Config.class).autowire();
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> this.businessService.repeatedAnnotations());
|
||||
}
|
||||
|
||||
// gh-3183
|
||||
@Test
|
||||
public void repeatedSecuredAnnotationsWhenPresentThenFails() {
|
||||
this.spring.register(SecuredConfig.class).autowire();
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> this.businessService.repeatedAnnotations());
|
||||
}
|
||||
|
||||
@EnableMethodSecurity
|
||||
static class MethodSecurityServiceConfig {
|
||||
|
||||
@Bean
|
||||
|
@ -292,6 +371,36 @@ public class MethodSecurityConfigurationTests {
|
|||
|
||||
}
|
||||
|
||||
@EnableMethodSecurity(prePostEnabled = false, securedEnabled = true)
|
||||
static class SecuredConfig {
|
||||
|
||||
@Bean
|
||||
BusinessService businessService() {
|
||||
return new BusinessServiceImpl<>();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableMethodSecurity(prePostEnabled = false, jsr250Enabled = true)
|
||||
static class Jsr250Config {
|
||||
|
||||
@Bean
|
||||
BusinessService businessService() {
|
||||
return new Jsr250BusinessServiceImpl();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
|
||||
static class MethodSecurityServiceEnabledConfig {
|
||||
|
||||
@Bean
|
||||
MethodSecurityService methodSecurityService() {
|
||||
return new MethodSecurityServiceImpl();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableMethodSecurity
|
||||
static class CustomPermissionEvaluatorConfig {
|
||||
|
||||
|
@ -316,16 +425,27 @@ public class MethodSecurityConfigurationTests {
|
|||
|
||||
}
|
||||
|
||||
@EnableMethodSecurity
|
||||
static class CustomGrantedAuthorityDefaultsConfig {
|
||||
|
||||
@Bean
|
||||
GrantedAuthorityDefaults grantedAuthorityDefaults() {
|
||||
return new GrantedAuthorityDefaults("PREFIX_");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableMethodSecurity
|
||||
static class CustomAuthorizationManagerBeforeAdviceConfig {
|
||||
|
||||
@Bean
|
||||
AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> customBeforeAdvice() {
|
||||
JdkRegexpMethodPointcut methodMatcher = new JdkRegexpMethodPointcut();
|
||||
methodMatcher.setPattern(".*MethodSecurityServiceImpl.*securedUser");
|
||||
AuthorizationManager<MethodAuthorizationContext> authorizationManager = (a,
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
Advisor customBeforeAdvice() {
|
||||
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
|
||||
pointcut.setPattern(".*MethodSecurityServiceImpl.*securedUser");
|
||||
AuthorizationManager<MethodInvocation> authorizationManager = (a,
|
||||
o) -> new AuthorizationDecision("bob".equals(a.get().getName()));
|
||||
return new AuthorizationManagerMethodBeforeAdvice<>(methodMatcher, authorizationManager);
|
||||
return new AuthorizationManagerBeforeMethodInterceptor(pointcut, authorizationManager);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -334,25 +454,20 @@ public class MethodSecurityConfigurationTests {
|
|||
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() + "'");
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
Advisor customAfterAdvice() {
|
||||
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
|
||||
pointcut.setPattern(".*MethodSecurityServiceImpl.*securedUser");
|
||||
MethodInterceptor interceptor = (mi) -> {
|
||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
if ("bob".equals(auth.getName())) {
|
||||
return "granted";
|
||||
}
|
||||
throw new AccessDeniedException("Access Denied for User '" + auth.getName() + "'");
|
||||
};
|
||||
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(pointcut, interceptor);
|
||||
advisor.setOrder(AuthorizationInterceptorsOrder.POST_FILTER.getOrder() + 1);
|
||||
return advisor;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
public @interface RequireAdminRole {
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@PreAuthorize("hasRole('USER')")
|
||||
public @interface RequireUserRole {
|
||||
|
||||
}
|
|
@ -52,7 +52,6 @@ public class XsdDocumentedTests {
|
|||
"nsa-authentication",
|
||||
"nsa-websocket-security",
|
||||
"nsa-ldap",
|
||||
"nsa-method-security",
|
||||
"nsa-web",
|
||||
// deprecated and for removal
|
||||
"nsa-frame-options-strategy",
|
||||
|
|
|
@ -0,0 +1,385 @@
|
|||
/*
|
||||
* Copyright 2002-2017 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.method;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.access.PermissionEvaluator;
|
||||
import org.springframework.security.access.annotation.BusinessService;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.authorization.AuthorizationDecision;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.config.annotation.method.configuration.MethodSecurityService;
|
||||
import org.springframework.security.config.test.SpringTestRule;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Josh Cummings
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SecurityTestExecutionListeners
|
||||
public class MethodSecurityBeanDefinitionParserTests {
|
||||
|
||||
private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests";
|
||||
|
||||
private final UsernamePasswordAuthenticationToken bob = new UsernamePasswordAuthenticationToken("bob",
|
||||
"bobspassword");
|
||||
|
||||
@Autowired(required = false)
|
||||
MethodSecurityService methodSecurityService;
|
||||
|
||||
@Autowired(required = false)
|
||||
BusinessService businessService;
|
||||
|
||||
@Rule
|
||||
public final SpringTestRule spring = new SpringTestRule();
|
||||
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
@Test
|
||||
public void preAuthorizeWhenRoleAdminThenAccessDeniedException() {
|
||||
this.spring.configLocations(xml("MethodSecurityService")).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::preAuthorize)
|
||||
.withMessage("Access Denied");
|
||||
}
|
||||
|
||||
@WithAnonymousUser
|
||||
@Test
|
||||
public void preAuthorizePermitAllWhenRoleAnonymousThenPasses() {
|
||||
this.spring.configLocations(xml("MethodSecurityService")).autowire();
|
||||
String result = this.methodSecurityService.preAuthorizePermitAll();
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
|
||||
@WithAnonymousUser
|
||||
@Test
|
||||
public void preAuthorizeNotAnonymousWhenRoleAnonymousThenAccessDeniedException() {
|
||||
this.spring.configLocations(xml("MethodSecurityService")).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class)
|
||||
.isThrownBy(this.methodSecurityService::preAuthorizeNotAnonymous).withMessage("Access Denied");
|
||||
}
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void preAuthorizeNotAnonymousWhenRoleUserThenPasses() {
|
||||
this.spring.configLocations(xml("MethodSecurityService")).autowire();
|
||||
this.methodSecurityService.preAuthorizeNotAnonymous();
|
||||
}
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void securedWhenRoleUserThenAccessDeniedException() {
|
||||
this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::secured)
|
||||
.withMessage("Access Denied");
|
||||
}
|
||||
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
@Test
|
||||
public void securedWhenRoleAdminThenPasses() {
|
||||
this.spring.configLocations(xml("MethodSecurityService")).autowire();
|
||||
String result = this.methodSecurityService.secured();
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
@Test
|
||||
public void securedUserWhenRoleAdminThenAccessDeniedException() {
|
||||
this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::securedUser)
|
||||
.withMessage("Access Denied");
|
||||
}
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void securedUserWhenRoleUserThenPasses() {
|
||||
this.spring.configLocations(xml("MethodSecurityService")).autowire();
|
||||
String result = this.methodSecurityService.securedUser();
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void preAuthorizeAdminWhenRoleUserThenAccessDeniedException() {
|
||||
this.spring.configLocations(xml("MethodSecurityService")).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::preAuthorizeAdmin)
|
||||
.withMessage("Access Denied");
|
||||
}
|
||||
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
@Test
|
||||
public void preAuthorizeAdminWhenRoleAdminThenPasses() {
|
||||
this.spring.configLocations(xml("MethodSecurityService")).autowire();
|
||||
this.methodSecurityService.preAuthorizeAdmin();
|
||||
}
|
||||
|
||||
@WithMockUser(authorities = "PREFIX_ADMIN")
|
||||
@Test
|
||||
public void preAuthorizeAdminWhenRoleAdminAndCustomPrefixThenPasses() {
|
||||
this.spring.configLocations(xml("CustomGrantedAuthorityDefaults")).autowire();
|
||||
this.methodSecurityService.preAuthorizeAdmin();
|
||||
}
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void postHasPermissionWhenParameterIsNotGrantThenAccessDeniedException() {
|
||||
this.spring.configLocations(xml("CustomPermissionEvaluator")).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class)
|
||||
.isThrownBy(() -> this.methodSecurityService.postHasPermission("deny")).withMessage("Access Denied");
|
||||
}
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void postHasPermissionWhenParameterIsGrantThenPasses() {
|
||||
this.spring.configLocations(xml("CustomPermissionEvaluator")).autowire();
|
||||
String result = this.methodSecurityService.postHasPermission("grant");
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void postAnnotationWhenParameterIsNotGrantThenAccessDeniedException() {
|
||||
this.spring.configLocations(xml("MethodSecurityService")).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class)
|
||||
.isThrownBy(() -> this.methodSecurityService.postAnnotation("deny")).withMessage("Access Denied");
|
||||
}
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void postAnnotationWhenParameterIsGrantThenPasses() {
|
||||
this.spring.configLocations(xml("MethodSecurityService")).autowire();
|
||||
String result = this.methodSecurityService.postAnnotation("grant");
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
|
||||
@WithMockUser("bob")
|
||||
@Test
|
||||
public void methodReturningAListWhenPrePostFiltersConfiguredThenFiltersList() {
|
||||
this.spring.configLocations(xml("BusinessService")).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.configLocations(xml("BusinessService")).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.configLocations(xml("CustomAuthorizationManagerBeforeAdvice")).autowire();
|
||||
String result = this.methodSecurityService.securedUser();
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
|
||||
@WithMockUser("joe")
|
||||
@Test
|
||||
public void securedUserWhenCustomBeforeAdviceConfiguredAndNameNotBobThenAccessDeniedException() {
|
||||
this.spring.configLocations(xml("CustomAuthorizationManagerBeforeAdvice")).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::securedUser)
|
||||
.withMessage("Access Denied");
|
||||
}
|
||||
|
||||
@WithMockUser("bob")
|
||||
@Test
|
||||
public void securedUserWhenCustomAfterAdviceConfiguredAndNameBobThenGranted() {
|
||||
this.spring.configLocations(xml("CustomAuthorizationManagerAfterAdvice")).autowire();
|
||||
String result = this.methodSecurityService.securedUser();
|
||||
assertThat(result).isEqualTo("granted");
|
||||
}
|
||||
|
||||
@WithMockUser("joe")
|
||||
@Test
|
||||
public void securedUserWhenCustomAfterAdviceConfiguredAndNameNotBobThenAccessDeniedException() {
|
||||
this.spring.configLocations(xml("CustomAuthorizationManagerAfterAdvice")).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::securedUser)
|
||||
.withMessage("Access Denied for User 'joe'");
|
||||
}
|
||||
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
@Test
|
||||
public void jsr250WhenRoleAdminThenAccessDeniedException() {
|
||||
this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::jsr250)
|
||||
.withMessage("Access Denied");
|
||||
}
|
||||
|
||||
@WithAnonymousUser
|
||||
@Test
|
||||
public void jsr250PermitAllWhenRoleAnonymousThenPasses() {
|
||||
this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire();
|
||||
String result = this.methodSecurityService.jsr250PermitAll();
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
@Test
|
||||
public void rolesAllowedUserWhenRoleAdminThenAccessDeniedException() {
|
||||
this.spring.configLocations(xml("BusinessService")).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.businessService::rolesAllowedUser)
|
||||
.withMessage("Access Denied");
|
||||
}
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void rolesAllowedUserWhenRoleUserThenPasses() {
|
||||
this.spring.configLocations(xml("BusinessService")).autowire();
|
||||
this.businessService.rolesAllowedUser();
|
||||
}
|
||||
|
||||
@WithMockUser(roles = { "ADMIN", "USER" })
|
||||
@Test
|
||||
public void manyAnnotationsWhenMeetsConditionsThenReturnsFilteredList() throws Exception {
|
||||
List<String> names = Arrays.asList("harold", "jonathan", "pete", "bo");
|
||||
this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire();
|
||||
List<String> filtered = this.methodSecurityService.manyAnnotations(new ArrayList<>(names));
|
||||
assertThat(filtered).hasSize(2);
|
||||
assertThat(filtered).containsExactly("harold", "jonathan");
|
||||
}
|
||||
|
||||
// gh-4003
|
||||
// gh-4103
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void manyAnnotationsWhenUserThenFails() {
|
||||
List<String> names = Arrays.asList("harold", "jonathan", "pete", "bo");
|
||||
this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class)
|
||||
.isThrownBy(() -> this.methodSecurityService.manyAnnotations(new ArrayList<>(names)));
|
||||
}
|
||||
|
||||
@WithMockUser
|
||||
@Test
|
||||
public void manyAnnotationsWhenShortListThenFails() {
|
||||
List<String> names = Arrays.asList("harold", "jonathan", "pete");
|
||||
this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class)
|
||||
.isThrownBy(() -> this.methodSecurityService.manyAnnotations(new ArrayList<>(names)));
|
||||
}
|
||||
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
@Test
|
||||
public void manyAnnotationsWhenAdminThenFails() {
|
||||
List<String> names = Arrays.asList("harold", "jonathan", "pete", "bo");
|
||||
this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire();
|
||||
assertThatExceptionOfType(AccessDeniedException.class)
|
||||
.isThrownBy(() -> this.methodSecurityService.manyAnnotations(new ArrayList<>(names)));
|
||||
}
|
||||
|
||||
// gh-3183
|
||||
@Test
|
||||
public void repeatedAnnotationsWhenPresentThenFails() {
|
||||
this.spring.configLocations(xml("MethodSecurityService")).autowire();
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> this.methodSecurityService.repeatedAnnotations());
|
||||
}
|
||||
|
||||
// gh-3183
|
||||
@Test
|
||||
public void repeatedJsr250AnnotationsWhenPresentThenFails() {
|
||||
this.spring.configLocations(xml("Jsr250")).autowire();
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> this.businessService.repeatedAnnotations());
|
||||
}
|
||||
|
||||
// gh-3183
|
||||
@Test
|
||||
public void repeatedSecuredAnnotationsWhenPresentThenFails() {
|
||||
this.spring.configLocations(xml("Secured")).autowire();
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> this.businessService.repeatedAnnotations());
|
||||
}
|
||||
|
||||
private static String xml(String configName) {
|
||||
return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml";
|
||||
}
|
||||
|
||||
static class MyPermissionEvaluator implements 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class MyAuthorizationManager implements AuthorizationManager<MethodInvocation> {
|
||||
|
||||
@Override
|
||||
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation object) {
|
||||
return new AuthorizationDecision("bob".equals(authentication.get().getName()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class MyAdvice implements MethodInterceptor {
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object invoke(@NotNull MethodInvocation invocation) {
|
||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
if ("bob".equals(auth.getName())) {
|
||||
return "granted";
|
||||
}
|
||||
throw new AccessDeniedException("Access Denied for User '" + auth.getName() + "'");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<method-security jsr250-enabled="true"/>
|
||||
|
||||
<b:bean class="org.springframework.security.access.annotation.ExpressionProtectedBusinessServiceImpl"/>
|
||||
|
||||
</b:beans>
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
|
||||
|
||||
<method-security/>
|
||||
|
||||
<b:bean id="pattern" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
|
||||
<b:property name="pattern" value=".*MethodSecurityServiceImpl.*securedUser"/>
|
||||
</b:bean>
|
||||
|
||||
<b:bean id="rule" class="org.springframework.security.config.method.MethodSecurityBeanDefinitionParserTests.MyAdvice"/>
|
||||
|
||||
<aop:config>
|
||||
<aop:advisor pointcut-ref="pattern" advice-ref="rule" order="#{T(org.springframework.security.authorization.method.AuthorizationInterceptorsOrder).POST_FILTER.getOrder() + 1}"/>
|
||||
</aop:config>
|
||||
|
||||
<b:bean id="methodSecurityService" class="org.springframework.security.config.annotation.method.configuration.MethodSecurityServiceImpl"/>
|
||||
|
||||
</b:beans>
|
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
|
||||
|
||||
<method-security/>
|
||||
|
||||
<aop:config/>
|
||||
|
||||
<b:bean id="myAdvice" class="org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor">
|
||||
<b:constructor-arg>
|
||||
<b:bean class="org.springframework.aop.support.JdkRegexpMethodPointcut">
|
||||
<b:property name="pattern" value=".*MethodSecurityServiceImpl.*securedUser"/>
|
||||
</b:bean>
|
||||
</b:constructor-arg>
|
||||
<b:constructor-arg>
|
||||
<b:bean class="org.springframework.security.config.method.MethodSecurityBeanDefinitionParserTests.MyAuthorizationManager"/>
|
||||
</b:constructor-arg>
|
||||
</b:bean>
|
||||
|
||||
<b:bean id="methodSecurityService" class="org.springframework.security.config.annotation.method.configuration.MethodSecurityServiceImpl"/>
|
||||
|
||||
</b:beans>
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<method-security/>
|
||||
|
||||
<b:bean class="org.springframework.security.config.annotation.method.configuration.MethodSecurityServiceImpl"/>
|
||||
|
||||
<b:bean id="grantedAuthorityDefaults" class="org.springframework.security.config.core.GrantedAuthorityDefaults">
|
||||
<b:constructor-arg value="PREFIX_"/>
|
||||
</b:bean>
|
||||
</b:beans>
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<method-security>
|
||||
<expression-handler ref="expressionHandler"/>
|
||||
</method-security>
|
||||
|
||||
<b:bean class="org.springframework.security.config.annotation.method.configuration.MethodSecurityServiceImpl"/>
|
||||
|
||||
<b:bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
|
||||
<b:property name="permissionEvaluator">
|
||||
<b:bean class="org.springframework.security.config.method.MethodSecurityBeanDefinitionParserTests.MyPermissionEvaluator"/>
|
||||
</b:property>
|
||||
</b:bean>
|
||||
</b:beans>
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<method-security pre-post-enabled="false" jsr250-enabled="true"/>
|
||||
|
||||
<b:bean class="org.springframework.security.access.annotation.Jsr250BusinessServiceImpl"/>
|
||||
|
||||
</b:beans>
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<method-security/>
|
||||
|
||||
<b:bean class="org.springframework.security.config.annotation.method.configuration.MethodSecurityServiceImpl"/>
|
||||
|
||||
</b:beans>
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<method-security secured-enabled="true" jsr250-enabled="true"/>
|
||||
|
||||
<b:bean class="org.springframework.security.config.annotation.method.configuration.MethodSecurityServiceImpl"/>
|
||||
|
||||
</b:beans>
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<method-security pre-post-enabled="false" secured-enabled="true"/>
|
||||
|
||||
<b:bean class="org.springframework.security.access.annotation.BusinessServiceImpl"/>
|
||||
|
||||
</b:beans>
|
|
@ -1,123 +0,0 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* 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);
|
||||
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* 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);
|
||||
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
/*
|
||||
* 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 + ']';
|
||||
}
|
||||
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.access.annotation;
|
||||
package org.springframework.security.authorization.method;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
@ -22,34 +22,31 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
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}.
|
||||
* For internal use only, as this contract is likely to change
|
||||
*
|
||||
* @author Evgeniy Cheban
|
||||
* @since 5.5
|
||||
*/
|
||||
abstract class AbstractAuthorizationManagerRegistry {
|
||||
|
||||
static final AuthorizationManager<MethodAuthorizationContext> NULL_MANAGER = (a, o) -> null;
|
||||
static final AuthorizationManager<MethodInvocation> NULL_MANAGER = (a, o) -> null;
|
||||
|
||||
private final Map<MethodClassKey, AuthorizationManager<MethodAuthorizationContext>> cachedManagers = new ConcurrentHashMap<>();
|
||||
private final Map<MethodClassKey, AuthorizationManager<MethodInvocation>> cachedManagers = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Returns an {@link AuthorizationManager} for the {@link MethodAuthorizationContext}.
|
||||
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to use
|
||||
* Returns an {@link AuthorizationManager} for the {@link MethodInvocation}.
|
||||
* @param methodInvocation the {@link MethodInvocation} to use
|
||||
* @return an {@link AuthorizationManager} to use
|
||||
*/
|
||||
final AuthorizationManager<MethodAuthorizationContext> getManager(
|
||||
MethodAuthorizationContext methodAuthorizationContext) {
|
||||
MethodInvocation methodInvocation = methodAuthorizationContext.getMethodInvocation();
|
||||
final AuthorizationManager<MethodInvocation> getManager(MethodInvocation methodInvocation) {
|
||||
Method method = methodInvocation.getMethod();
|
||||
Class<?> targetClass = methodAuthorizationContext.getTargetClass();
|
||||
Object target = methodInvocation.getThis();
|
||||
Class<?> targetClass = (target != null) ? AopUtils.getTargetClass(target) : null;
|
||||
MethodClassKey cacheKey = new MethodClassKey(method, targetClass);
|
||||
return this.cachedManagers.computeIfAbsent(cacheKey, (k) -> resolveManager(method, targetClass));
|
||||
}
|
||||
|
@ -62,6 +59,6 @@ abstract class AbstractAuthorizationManagerRegistry {
|
|||
* @return the non-null {@link AuthorizationManager}
|
||||
*/
|
||||
@NonNull
|
||||
abstract AuthorizationManager<MethodAuthorizationContext> resolveManager(Method method, Class<?> targetClass);
|
||||
abstract AuthorizationManager<MethodInvocation> resolveManager(Method method, Class<?> targetClass);
|
||||
|
||||
}
|
|
@ -22,30 +22,28 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
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}.
|
||||
* For internal use only, as this contract is likely to change
|
||||
*
|
||||
* @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
|
||||
* Returns an {@link ExpressionAttribute} for the {@link MethodInvocation}.
|
||||
* @param mi the {@link MethodInvocation} 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();
|
||||
final T getAttribute(MethodInvocation mi) {
|
||||
Method method = mi.getMethod();
|
||||
Object target = mi.getThis();
|
||||
Class<?> targetClass = (target != null) ? AopUtils.getTargetClass(target) : null;
|
||||
return getAttribute(method, targetClass);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* 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.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.core.annotation.RepeatableContainers;
|
||||
|
||||
/**
|
||||
* A wrapper around {@link AnnotationUtils} that checks for, and errors on, conflicting
|
||||
* annotations. This is specifically important for Spring Security annotations which are
|
||||
* not designed to be repeatable.
|
||||
*
|
||||
* There are numerous ways that two annotations of the same type may be attached to the
|
||||
* same method. For example, a class may implement a method defined in two separate
|
||||
* interfaces. If both of those interfaces have a `@PreAuthorize` annotation, then it's
|
||||
* unclear which `@PreAuthorize` expression Spring Security should use.
|
||||
*
|
||||
* Another way is when one of Spring Security's annotations is used as a meta-annotation.
|
||||
* In that case, two custom annotations can be declared, each with their own
|
||||
* `@PreAuthorize` declaration. If both custom annotations are used on the same method,
|
||||
* then it's unclear which `@PreAuthorize` expression Spring Security should use.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
*/
|
||||
final class AuthorizationAnnotationUtils {
|
||||
|
||||
/**
|
||||
* Perform an exhaustive search on the type hierarchy of the given {@link Method} for
|
||||
* the annotation of type {@code annotationType}, including any annotations using
|
||||
* {@code annotationType} as a meta-annotation.
|
||||
*
|
||||
* If more than one is found, then throw an error.
|
||||
* @param method the method declaration to search from
|
||||
* @param annotationType the annotation type to search for
|
||||
* @return the unique instance of the annotation attributed to the method,
|
||||
* {@code null} otherwise
|
||||
* @throws AnnotationConfigurationException if more than one instance of the
|
||||
* annotation is found
|
||||
*/
|
||||
static <A extends Annotation> A findUniqueAnnotation(Method method, Class<A> annotationType) {
|
||||
MergedAnnotations mergedAnnotations = MergedAnnotations.from(method,
|
||||
MergedAnnotations.SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none());
|
||||
if (hasDuplicate(mergedAnnotations, annotationType)) {
|
||||
throw new AnnotationConfigurationException("Found more than one annotation of type " + annotationType
|
||||
+ " attributed to " + method
|
||||
+ " Please remove the duplicate annotations and publish a bean to handle your authorization logic.");
|
||||
}
|
||||
return AnnotationUtils.findAnnotation(method, annotationType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an exhaustive search on the type hierarchy of the given {@link Class} for
|
||||
* the annotation of type {@code annotationType}, including any annotations using
|
||||
* {@code annotationType} as a meta-annotation.
|
||||
*
|
||||
* If more than one is found, then throw an error.
|
||||
* @param type the type to search from
|
||||
* @param annotationType the annotation type to search for
|
||||
* @return the unique instance of the annotation attributed to the method,
|
||||
* {@code null} otherwise
|
||||
* @throws AnnotationConfigurationException if more than one instance of the
|
||||
* annotation is found
|
||||
*/
|
||||
static <A extends Annotation> A findUniqueAnnotation(Class<?> type, Class<A> annotationType) {
|
||||
MergedAnnotations mergedAnnotations = MergedAnnotations.from(type,
|
||||
MergedAnnotations.SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none());
|
||||
if (hasDuplicate(mergedAnnotations, annotationType)) {
|
||||
throw new AnnotationConfigurationException("Found more than one annotation of type " + annotationType
|
||||
+ " attributed to " + type
|
||||
+ " Please remove the duplicate annotations and publish a bean to handle your authorization logic.");
|
||||
}
|
||||
return AnnotationUtils.findAnnotation(type, annotationType);
|
||||
}
|
||||
|
||||
private static <A extends Annotation> boolean hasDuplicate(MergedAnnotations mergedAnnotations,
|
||||
Class<A> annotationType) {
|
||||
boolean alreadyFound = false;
|
||||
for (MergedAnnotation<Annotation> mergedAnnotation : mergedAnnotations) {
|
||||
if (mergedAnnotation.getType() == annotationType) {
|
||||
if (alreadyFound) {
|
||||
return true;
|
||||
}
|
||||
alreadyFound = true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private AuthorizationAnnotationUtils() {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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.aop.Advisor;
|
||||
|
||||
/**
|
||||
* Ordering of Spring Security's authorization {@link Advisor}s
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 5.6
|
||||
* @see PreAuthorizeAuthorizationManager
|
||||
* @see PostAuthorizeAuthorizationManager
|
||||
* @see SecuredAuthorizationManager
|
||||
* @see Jsr250AuthorizationManager
|
||||
*/
|
||||
public enum AuthorizationInterceptorsOrder {
|
||||
|
||||
FIRST(Integer.MIN_VALUE),
|
||||
|
||||
/**
|
||||
* {@link PreFilterAuthorizationMethodInterceptor}
|
||||
*/
|
||||
PRE_FILTER,
|
||||
|
||||
PRE_AUTHORIZE,
|
||||
|
||||
SECURED,
|
||||
|
||||
JSR250,
|
||||
|
||||
POST_AUTHORIZE,
|
||||
|
||||
/**
|
||||
* {@link PostFilterAuthorizationMethodInterceptor}
|
||||
*/
|
||||
POST_FILTER,
|
||||
|
||||
LAST(Integer.MAX_VALUE);
|
||||
|
||||
private static final int INTERVAL = 100;
|
||||
|
||||
private final int order;
|
||||
|
||||
AuthorizationInterceptorsOrder() {
|
||||
this.order = ordinal() * INTERVAL;
|
||||
}
|
||||
|
||||
AuthorizationInterceptorsOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* 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.function.Supplier;
|
||||
|
||||
import org.aopalliance.aop.Advice;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.aop.PointcutAdvisor;
|
||||
import org.springframework.aop.framework.AopInfrastructureBean;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.access.prepost.PostAuthorize;
|
||||
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link MethodInterceptor} which can determine if an {@link Authentication} has access
|
||||
* to the result of an {@link MethodInvocation} using an {@link AuthorizationManager}
|
||||
*
|
||||
* @author Evgeniy Cheban
|
||||
* @author Josh Cummings
|
||||
* @since 5.6
|
||||
*/
|
||||
public final class AuthorizationManagerAfterMethodInterceptor
|
||||
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
|
||||
|
||||
static final Supplier<Authentication> AUTHENTICATION_SUPPLIER = () -> {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication == null) {
|
||||
throw new AuthenticationCredentialsNotFoundException(
|
||||
"An Authentication object was not found in the SecurityContext");
|
||||
}
|
||||
return authentication;
|
||||
};
|
||||
|
||||
private final Pointcut pointcut;
|
||||
|
||||
private final AuthorizationManager<MethodInvocationResult> authorizationManager;
|
||||
|
||||
private int order;
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
* @param pointcut the {@link Pointcut} to use
|
||||
* @param authorizationManager the {@link AuthorizationManager} to use
|
||||
*/
|
||||
public AuthorizationManagerAfterMethodInterceptor(Pointcut pointcut,
|
||||
AuthorizationManager<MethodInvocationResult> authorizationManager) {
|
||||
Assert.notNull(pointcut, "pointcut cannot be null");
|
||||
Assert.notNull(authorizationManager, "authorizationManager cannot be null");
|
||||
this.pointcut = pointcut;
|
||||
this.authorizationManager = authorizationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an interceptor for the {@link PostAuthorize} annotation
|
||||
* @return the interceptor
|
||||
*/
|
||||
public static AuthorizationManagerAfterMethodInterceptor postAuthorize() {
|
||||
return postAuthorize(new PostAuthorizeAuthorizationManager());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an interceptor for the {@link PostAuthorize} annotation
|
||||
* @param authorizationManager the {@link PostAuthorizeAuthorizationManager} to use
|
||||
* @return the interceptor
|
||||
*/
|
||||
public static AuthorizationManagerAfterMethodInterceptor postAuthorize(
|
||||
PostAuthorizeAuthorizationManager authorizationManager) {
|
||||
AuthorizationManagerAfterMethodInterceptor interceptor = new AuthorizationManagerAfterMethodInterceptor(
|
||||
AuthorizationMethodPointcuts.forAnnotations(PostAuthorize.class), authorizationManager);
|
||||
interceptor.setOrder(500);
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an {@link Authentication} has access to the {@link MethodInvocation}
|
||||
* using the {@link AuthorizationManager}.
|
||||
* @param mi the {@link MethodInvocation} to check
|
||||
* @throws AccessDeniedException if access is not granted
|
||||
*/
|
||||
@Override
|
||||
public Object invoke(MethodInvocation mi) throws Throwable {
|
||||
Object result = mi.proceed();
|
||||
this.authorizationManager.verify(AUTHENTICATION_SUPPLIER, new MethodInvocationResult(mi, result));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Pointcut getPointcut() {
|
||||
return this.pointcut;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Advice getAdvice() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPerInstance() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
* 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.function.Supplier;
|
||||
|
||||
import javax.annotation.security.DenyAll;
|
||||
import javax.annotation.security.PermitAll;
|
||||
import javax.annotation.security.RolesAllowed;
|
||||
|
||||
import org.aopalliance.aop.Advice;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.aop.PointcutAdvisor;
|
||||
import org.springframework.aop.framework.AopInfrastructureBean;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.access.annotation.Secured;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link MethodInterceptor} which uses a {@link AuthorizationManager} to determine if
|
||||
* an {@link Authentication} may invoke the given {@link MethodInvocation}
|
||||
*
|
||||
* @author Evgeniy Cheban
|
||||
* @author Josh Cummings
|
||||
* @since 5.6
|
||||
*/
|
||||
public final class AuthorizationManagerBeforeMethodInterceptor
|
||||
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
|
||||
|
||||
static final Supplier<Authentication> AUTHENTICATION_SUPPLIER = () -> {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication == null) {
|
||||
throw new AuthenticationCredentialsNotFoundException(
|
||||
"An Authentication object was not found in the SecurityContext");
|
||||
}
|
||||
return authentication;
|
||||
};
|
||||
|
||||
private final Pointcut pointcut;
|
||||
|
||||
private final AuthorizationManager<MethodInvocation> authorizationManager;
|
||||
|
||||
private int order = AuthorizationInterceptorsOrder.FIRST.getOrder();
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
* @param pointcut the {@link Pointcut} to use
|
||||
* @param authorizationManager the {@link AuthorizationManager} to use
|
||||
*/
|
||||
public AuthorizationManagerBeforeMethodInterceptor(Pointcut pointcut,
|
||||
AuthorizationManager<MethodInvocation> authorizationManager) {
|
||||
Assert.notNull(pointcut, "pointcut cannot be null");
|
||||
Assert.notNull(authorizationManager, "authorizationManager cannot be null");
|
||||
this.pointcut = pointcut;
|
||||
this.authorizationManager = authorizationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an interceptor for the {@link PreAuthorize} annotation
|
||||
* @return the interceptor
|
||||
*/
|
||||
public static AuthorizationManagerBeforeMethodInterceptor preAuthorize() {
|
||||
return preAuthorize(new PreAuthorizeAuthorizationManager());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an interceptor for the {@link PreAuthorize} annotation
|
||||
* @param authorizationManager the {@link PreAuthorizeAuthorizationManager} to use
|
||||
* @return the interceptor
|
||||
*/
|
||||
public static AuthorizationManagerBeforeMethodInterceptor preAuthorize(
|
||||
PreAuthorizeAuthorizationManager authorizationManager) {
|
||||
AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(
|
||||
AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class), authorizationManager);
|
||||
interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder());
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an interceptor for the {@link Secured} annotation
|
||||
* @return the interceptor
|
||||
*/
|
||||
public static AuthorizationManagerBeforeMethodInterceptor secured() {
|
||||
return secured(new SecuredAuthorizationManager());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an interceptor for the {@link Secured} annotation
|
||||
* @param authorizationManager the {@link SecuredAuthorizationManager} to use
|
||||
* @return the interceptor
|
||||
*/
|
||||
public static AuthorizationManagerBeforeMethodInterceptor secured(
|
||||
SecuredAuthorizationManager authorizationManager) {
|
||||
AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(
|
||||
AuthorizationMethodPointcuts.forAnnotations(Secured.class), authorizationManager);
|
||||
interceptor.setOrder(AuthorizationInterceptorsOrder.SECURED.getOrder());
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an interceptor for the JSR-250 annotations
|
||||
* @return the interceptor
|
||||
*/
|
||||
public static AuthorizationManagerBeforeMethodInterceptor jsr250() {
|
||||
return jsr250(new Jsr250AuthorizationManager());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an interceptor for the JSR-250 annotations
|
||||
* @param authorizationManager the {@link Jsr250AuthorizationManager} to use
|
||||
* @return the interceptor
|
||||
*/
|
||||
public static AuthorizationManagerBeforeMethodInterceptor jsr250(Jsr250AuthorizationManager authorizationManager) {
|
||||
AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(
|
||||
AuthorizationMethodPointcuts.forAnnotations(RolesAllowed.class, DenyAll.class, PermitAll.class),
|
||||
authorizationManager);
|
||||
interceptor.setOrder(AuthorizationInterceptorsOrder.JSR250.getOrder());
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an {@link Authentication} has access to the {@link MethodInvocation}
|
||||
* using the configured {@link AuthorizationManager}.
|
||||
* @param mi the {@link MethodInvocation} to check
|
||||
* @throws AccessDeniedException if access is not granted
|
||||
*/
|
||||
@Override
|
||||
public Object invoke(MethodInvocation mi) throws Throwable {
|
||||
this.authorizationManager.verify(AUTHENTICATION_SUPPLIER, mi);
|
||||
return mi.proceed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Pointcut getPointcut() {
|
||||
return this.pointcut;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Advice getAdvice() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPerInstance() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.annotation.Annotation;
|
||||
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.aop.support.ComposablePointcut;
|
||||
import org.springframework.aop.support.Pointcuts;
|
||||
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
|
||||
|
||||
/**
|
||||
* @author Josh Cummings
|
||||
*/
|
||||
final class AuthorizationMethodPointcuts {
|
||||
|
||||
@SafeVarargs
|
||||
static Pointcut forAnnotations(Class<? extends Annotation>... annotations) {
|
||||
ComposablePointcut pointcut = null;
|
||||
for (Class<? extends Annotation> annotation : annotations) {
|
||||
if (pointcut == null) {
|
||||
pointcut = new ComposablePointcut(classOrMethod(annotation));
|
||||
}
|
||||
else {
|
||||
pointcut.union(classOrMethod(annotation));
|
||||
}
|
||||
}
|
||||
return pointcut;
|
||||
}
|
||||
|
||||
private static Pointcut classOrMethod(Class<? extends Annotation> annotation) {
|
||||
return Pointcuts.union(new AnnotationMatchingPointcut(null, annotation, true),
|
||||
new AnnotationMatchingPointcut(annotation, true));
|
||||
}
|
||||
|
||||
private AuthorizationMethodPointcuts() {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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.annotation.Annotation;
|
||||
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.AnnotationConfigurationException;
|
||||
import org.springframework.lang.NonNull;
|
||||
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} may
|
||||
* invoke the {@link MethodInvocation} by evaluating if the {@link Authentication}
|
||||
* contains a specified authority from the JSR-250 security annotations.
|
||||
*
|
||||
* @author Evgeniy Cheban
|
||||
* @author Josh Cummings
|
||||
* @since 5.6
|
||||
*/
|
||||
public final class Jsr250AuthorizationManager implements AuthorizationManager<MethodInvocation> {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if an {@link Authentication} has access to a method by evaluating the
|
||||
* {@link DenyAll}, {@link PermitAll}, and {@link RolesAllowed} annotations that
|
||||
* {@link MethodInvocation} specifies.
|
||||
* @param authentication the {@link Supplier} of the {@link Authentication} to check
|
||||
* @param methodInvocation the {@link MethodInvocation} to check
|
||||
* @return an {@link AuthorizationDecision} or null if the JSR-250 security
|
||||
* annotations is not present
|
||||
*/
|
||||
@Override
|
||||
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation methodInvocation) {
|
||||
AuthorizationManager<MethodInvocation> delegate = this.registry.getManager(methodInvocation);
|
||||
return delegate.check(authentication, methodInvocation);
|
||||
}
|
||||
|
||||
private final class Jsr250AuthorizationManagerRegistry extends AbstractAuthorizationManagerRegistry {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
AuthorizationManager<MethodInvocation> resolveManager(Method method, Class<?> targetClass) {
|
||||
Annotation annotation = findJsr250Annotation(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 Annotation findJsr250Annotation(Method method, Class<?> targetClass) {
|
||||
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
|
||||
Annotation annotation = findAnnotation(specificMethod);
|
||||
return (annotation != null) ? annotation : findAnnotation(specificMethod.getDeclaringClass());
|
||||
}
|
||||
|
||||
private Annotation findAnnotation(Method method) {
|
||||
Set<Annotation> annotations = new HashSet<>();
|
||||
for (Class<? extends Annotation> annotationClass : JSR250_ANNOTATIONS) {
|
||||
Annotation annotation = AuthorizationAnnotationUtils.findUniqueAnnotation(method, annotationClass);
|
||||
if (annotation != null) {
|
||||
annotations.add(annotation);
|
||||
}
|
||||
}
|
||||
if (annotations.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (annotations.size() > 1) {
|
||||
throw new AnnotationConfigurationException(
|
||||
"The JSR-250 specification disallows DenyAll, PermitAll, and RolesAllowed from appearing on the same method.");
|
||||
}
|
||||
return annotations.iterator().next();
|
||||
}
|
||||
|
||||
private Annotation findAnnotation(Class<?> clazz) {
|
||||
Set<Annotation> annotations = new HashSet<>();
|
||||
for (Class<? extends Annotation> annotationClass : JSR250_ANNOTATIONS) {
|
||||
Annotation annotation = AuthorizationAnnotationUtils.findUniqueAnnotation(clazz, annotationClass);
|
||||
if (annotation != null) {
|
||||
annotations.add(annotation);
|
||||
}
|
||||
}
|
||||
if (annotations.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (annotations.size() > 1) {
|
||||
throw new AnnotationConfigurationException(
|
||||
"The JSR-250 specification disallows DenyAll, PermitAll, and RolesAllowed from appearing on the same class definition.");
|
||||
}
|
||||
return annotations.iterator().next();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A context object that contains a {@link MethodInvocation} and the result of that
|
||||
* {@link MethodInvocation}.
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 5.6
|
||||
*/
|
||||
public class MethodInvocationResult {
|
||||
|
||||
private final MethodInvocation methodInvocation;
|
||||
|
||||
private final Object result;
|
||||
|
||||
/**
|
||||
* Construct a {@link MethodInvocationResult} with the provided parameters
|
||||
* @param methodInvocation the already-invoked {@link MethodInvocation}
|
||||
* @param result the value returned from the {@link MethodInvocation}
|
||||
*/
|
||||
public MethodInvocationResult(MethodInvocation methodInvocation, Object result) {
|
||||
Assert.notNull(methodInvocation, "methodInvocation cannot be null");
|
||||
this.methodInvocation = methodInvocation;
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the already-invoked {@link MethodInvocation}
|
||||
* @return the already-invoked {@link MethodInvocation}
|
||||
*/
|
||||
public MethodInvocation getMethodInvocation() {
|
||||
return this.methodInvocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the result of the already-invoked {@link MethodInvocation}
|
||||
* @return the result
|
||||
*/
|
||||
public Object getResult() {
|
||||
return this.result;
|
||||
}
|
||||
|
||||
}
|
|
@ -23,13 +23,11 @@ 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;
|
||||
|
@ -37,21 +35,21 @@ 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.
|
||||
* An {@link AuthorizationManager} which can determine if an {@link Authentication} may
|
||||
* return the result from an invoked {@link MethodInvocation} by evaluating an expression
|
||||
* from the {@link PostAuthorize} annotation.
|
||||
*
|
||||
* @author Evgeniy Cheban
|
||||
* @since 5.5
|
||||
* @since 5.6
|
||||
*/
|
||||
public final class PostAuthorizeAuthorizationManager implements AuthorizationManager<MethodAuthorizationContext> {
|
||||
public final class PostAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocationResult> {
|
||||
|
||||
private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
|
||||
|
||||
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
||||
|
||||
/**
|
||||
* Sets the {@link MethodSecurityExpressionHandler}.
|
||||
* Use this the {@link MethodSecurityExpressionHandler}.
|
||||
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
|
||||
*/
|
||||
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
|
||||
|
@ -60,23 +58,23 @@ public final class PostAuthorizeAuthorizationManager implements AuthorizationMan
|
|||
}
|
||||
|
||||
/**
|
||||
* Determines if an {@link Authentication} has access to the {@link MethodInvocation}
|
||||
* by evaluating an expression from the {@link PostAuthorize} annotation.
|
||||
* Determine if an {@link Authentication} has access to the returned object by
|
||||
* evaluating the {@link PostAuthorize} annotation that the {@link MethodInvocation}
|
||||
* specifies.
|
||||
* @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
|
||||
* @param mi the {@link MethodInvocationResult} to check
|
||||
* @return an {@link AuthorizationDecision} or {@code null} if the
|
||||
* {@link PostAuthorize} annotation is not present
|
||||
*/
|
||||
@Override
|
||||
public AuthorizationDecision check(Supplier<Authentication> authentication,
|
||||
MethodAuthorizationContext methodAuthorizationContext) {
|
||||
ExpressionAttribute attribute = this.registry.getAttribute(methodAuthorizationContext);
|
||||
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocationResult mi) {
|
||||
ExpressionAttribute attribute = this.registry.getAttribute(mi.getMethodInvocation());
|
||||
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
|
||||
return null;
|
||||
}
|
||||
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(),
|
||||
methodAuthorizationContext.getMethodInvocation());
|
||||
this.expressionHandler.setReturnObject(methodAuthorizationContext.getReturnObject(), ctx);
|
||||
mi.getMethodInvocation());
|
||||
this.expressionHandler.setReturnObject(mi.getResult(), ctx);
|
||||
boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);
|
||||
return new AuthorizationDecision(granted);
|
||||
}
|
||||
|
@ -98,9 +96,10 @@ public final class PostAuthorizeAuthorizationManager implements AuthorizationMan
|
|||
}
|
||||
|
||||
private PostAuthorize findPostAuthorizeAnnotation(Method method) {
|
||||
PostAuthorize postAuthorize = AnnotationUtils.findAnnotation(method, PostAuthorize.class);
|
||||
return (postAuthorize != null) ? postAuthorize
|
||||
: AnnotationUtils.findAnnotation(method.getDeclaringClass(), PostAuthorize.class);
|
||||
PostAuthorize postAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method,
|
||||
PostAuthorize.class);
|
||||
return (postAuthorize != null) ? postAuthorize : AuthorizationAnnotationUtils
|
||||
.findUniqueAnnotation(method.getDeclaringClass(), PostAuthorize.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,48 +19,65 @@ package org.springframework.security.authorization.method;
|
|||
import java.lang.reflect.Method;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.aopalliance.aop.Advice;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.aop.MethodMatcher;
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.aop.PointcutAdvisor;
|
||||
import org.springframework.aop.framework.AopInfrastructureBean;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.aop.support.StaticMethodMatcher;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.Ordered;
|
||||
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.authentication.AuthenticationCredentialsNotFoundException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
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.
|
||||
* A {@link MethodInterceptor} which filters a {@code returnedObject} from the
|
||||
* {@link MethodInvocation} by evaluating an expression from the {@link PostFilter}
|
||||
* annotation.
|
||||
*
|
||||
* @author Evgeniy Cheban
|
||||
* @since 5.5
|
||||
* @author Josh Cummings
|
||||
* @since 5.6
|
||||
*/
|
||||
public final class PostFilterAuthorizationMethodAfterAdvice
|
||||
implements AuthorizationMethodAfterAdvice<MethodAuthorizationContext> {
|
||||
public final class PostFilterAuthorizationMethodInterceptor
|
||||
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
|
||||
|
||||
private static final Supplier<Authentication> AUTHENTICATION_SUPPLIER = () -> {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication == null) {
|
||||
throw new AuthenticationCredentialsNotFoundException(
|
||||
"An Authentication object was not found in the SecurityContext");
|
||||
}
|
||||
return authentication;
|
||||
};
|
||||
|
||||
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 int order = AuthorizationInterceptorsOrder.POST_FILTER.getOrder();
|
||||
|
||||
private final Pointcut pointcut;
|
||||
|
||||
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
||||
|
||||
/**
|
||||
* Sets the {@link MethodSecurityExpressionHandler}.
|
||||
* Creates a {@link PostFilterAuthorizationMethodInterceptor} using the provided
|
||||
* parameters
|
||||
*/
|
||||
public PostFilterAuthorizationMethodInterceptor() {
|
||||
this.pointcut = AuthorizationMethodPointcuts.forAnnotations(PostFilter.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@link MethodSecurityExpressionHandler}.
|
||||
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
|
||||
*/
|
||||
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
|
||||
|
@ -68,35 +85,51 @@ public final class PostFilterAuthorizationMethodAfterAdvice
|
|||
this.expressionHandler = expressionHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public MethodMatcher getMethodMatcher() {
|
||||
return this.methodMatcher;
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Object after(Supplier<Authentication> authentication, MethodAuthorizationContext methodAuthorizationContext,
|
||||
Object returnedObject) {
|
||||
if (returnedObject == null) {
|
||||
return null;
|
||||
}
|
||||
ExpressionAttribute attribute = this.registry.getAttribute(methodAuthorizationContext);
|
||||
public Pointcut getPointcut() {
|
||||
return this.pointcut;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Advice getAdvice() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPerInstance() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a {@code returnedObject} using the {@link PostFilter} annotation that the
|
||||
* {@link MethodInvocation} specifies.
|
||||
* @param mi the {@link MethodInvocation} to check check
|
||||
* @return filtered {@code returnedObject}
|
||||
*/
|
||||
@Override
|
||||
public Object invoke(MethodInvocation mi) throws Throwable {
|
||||
Object returnedObject = mi.proceed();
|
||||
ExpressionAttribute attribute = this.registry.getAttribute(mi);
|
||||
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;
|
||||
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(AUTHENTICATION_SUPPLIER.get(), mi);
|
||||
return this.expressionHandler.filter(returnedObject, attribute.getExpression(), ctx);
|
||||
}
|
||||
|
||||
private final class PostFilterExpressionAttributeRegistry
|
||||
|
@ -110,15 +143,15 @@ public final class PostFilterAuthorizationMethodAfterAdvice
|
|||
if (postFilter == null) {
|
||||
return ExpressionAttribute.NULL_ATTRIBUTE;
|
||||
}
|
||||
Expression postFilterExpression = PostFilterAuthorizationMethodAfterAdvice.this.expressionHandler
|
||||
Expression postFilterExpression = PostFilterAuthorizationMethodInterceptor.this.expressionHandler
|
||||
.getExpressionParser().parseExpression(postFilter.value());
|
||||
return new ExpressionAttribute(postFilterExpression);
|
||||
}
|
||||
|
||||
private PostFilter findPostFilterAnnotation(Method method) {
|
||||
PostFilter postFilter = AnnotationUtils.findAnnotation(method, PostFilter.class);
|
||||
PostFilter postFilter = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PostFilter.class);
|
||||
return (postFilter != null) ? postFilter
|
||||
: AnnotationUtils.findAnnotation(method.getDeclaringClass(), PostFilter.class);
|
||||
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PostFilter.class);
|
||||
}
|
||||
|
||||
}
|
|
@ -23,13 +23,11 @@ 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;
|
||||
|
@ -37,14 +35,14 @@ 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
|
||||
* An {@link AuthorizationManager} which can determine if an {@link Authentication} may
|
||||
* invoke the {@link MethodInvocation} by evaluating an expression from the
|
||||
* {@link PreAuthorize} annotation.
|
||||
*
|
||||
* @author Evgeniy Cheban
|
||||
* @since 5.5
|
||||
* @since 5.6
|
||||
*/
|
||||
public final class PreAuthorizeAuthorizationManager implements AuthorizationManager<MethodAuthorizationContext> {
|
||||
public final class PreAuthorizeAuthorizationManager implements AuthorizationManager<MethodInvocation> {
|
||||
|
||||
private final PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry();
|
||||
|
||||
|
@ -60,22 +58,21 @@ public final class PreAuthorizeAuthorizationManager implements AuthorizationMana
|
|||
}
|
||||
|
||||
/**
|
||||
* Determines if an {@link Authentication} has access to the {@link MethodInvocation}
|
||||
* by evaluating an expression from the {@link PreAuthorize} annotation.
|
||||
* Determine if an {@link Authentication} has access to a method by evaluating an
|
||||
* expression from the {@link PreAuthorize} annotation that the
|
||||
* {@link MethodInvocation} specifies.
|
||||
* @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
|
||||
* @param mi the {@link MethodInvocation} to check
|
||||
* @return an {@link AuthorizationDecision} or {@code null} if the
|
||||
* {@link PreAuthorize} annotation is not present
|
||||
*/
|
||||
@Override
|
||||
public AuthorizationDecision check(Supplier<Authentication> authentication,
|
||||
MethodAuthorizationContext methodAuthorizationContext) {
|
||||
ExpressionAttribute attribute = this.registry.getAttribute(methodAuthorizationContext);
|
||||
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation mi) {
|
||||
ExpressionAttribute attribute = this.registry.getAttribute(mi);
|
||||
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
|
||||
return null;
|
||||
}
|
||||
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(),
|
||||
methodAuthorizationContext.getMethodInvocation());
|
||||
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(), mi);
|
||||
boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);
|
||||
return new AuthorizationDecision(granted);
|
||||
}
|
||||
|
@ -97,9 +94,9 @@ public final class PreAuthorizeAuthorizationManager implements AuthorizationMana
|
|||
}
|
||||
|
||||
private PreAuthorize findPreAuthorizeAnnotation(Method method) {
|
||||
PreAuthorize preAuthorize = AnnotationUtils.findAnnotation(method, PreAuthorize.class);
|
||||
PreAuthorize preAuthorize = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreAuthorize.class);
|
||||
return (preAuthorize != null) ? preAuthorize
|
||||
: AnnotationUtils.findAnnotation(method.getDeclaringClass(), PreAuthorize.class);
|
||||
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PreAuthorize.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,48 +19,65 @@ package org.springframework.security.authorization.method;
|
|||
import java.lang.reflect.Method;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.aopalliance.aop.Advice;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
import org.springframework.aop.MethodMatcher;
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.aop.PointcutAdvisor;
|
||||
import org.springframework.aop.framework.AopInfrastructureBean;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.aop.support.StaticMethodMatcher;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.Ordered;
|
||||
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.authentication.AuthenticationCredentialsNotFoundException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
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.
|
||||
* A {@link MethodInterceptor} which filters a method argument by evaluating an expression
|
||||
* from the {@link PreFilter} annotation.
|
||||
*
|
||||
* @author Evgeniy Cheban
|
||||
* @since 5.5
|
||||
* @author Josh Cummings
|
||||
* @since 5.6
|
||||
*/
|
||||
public final class PreFilterAuthorizationMethodBeforeAdvice
|
||||
implements AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> {
|
||||
public final class PreFilterAuthorizationMethodInterceptor
|
||||
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
|
||||
|
||||
private static final Supplier<Authentication> AUTHENTICATION_SUPPLIER = () -> {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication == null) {
|
||||
throw new AuthenticationCredentialsNotFoundException(
|
||||
"An Authentication object was not found in the SecurityContext");
|
||||
}
|
||||
return authentication;
|
||||
};
|
||||
|
||||
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 int order = AuthorizationInterceptorsOrder.PRE_FILTER.getOrder();
|
||||
|
||||
private final Pointcut pointcut;
|
||||
|
||||
private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
||||
|
||||
/**
|
||||
* Sets the {@link MethodSecurityExpressionHandler}.
|
||||
* Creates a {@link PreFilterAuthorizationMethodInterceptor} using the provided
|
||||
* parameters
|
||||
*/
|
||||
public PreFilterAuthorizationMethodInterceptor() {
|
||||
this.pointcut = AuthorizationMethodPointcuts.forAnnotations(PreFilter.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@link MethodSecurityExpressionHandler}
|
||||
* @param expressionHandler the {@link MethodSecurityExpressionHandler} to use
|
||||
*/
|
||||
public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) {
|
||||
|
@ -68,27 +85,51 @@ public final class PreFilterAuthorizationMethodBeforeAdvice
|
|||
this.expressionHandler = expressionHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public MethodMatcher getMethodMatcher() {
|
||||
return this.methodMatcher;
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void before(Supplier<Authentication> authentication, MethodAuthorizationContext methodAuthorizationContext) {
|
||||
PreFilterExpressionAttribute attribute = this.registry.getAttribute(methodAuthorizationContext);
|
||||
public Pointcut getPointcut() {
|
||||
return this.pointcut;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Advice getAdvice() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPerInstance() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the method argument specified in the {@link PreFilter} annotation that
|
||||
* {@link MethodInvocation} specifies.
|
||||
* @param mi the {@link MethodInvocation} to check
|
||||
*/
|
||||
@Override
|
||||
public Object invoke(MethodInvocation mi) throws Throwable {
|
||||
PreFilterExpressionAttribute attribute = this.registry.getAttribute(mi);
|
||||
if (attribute == PreFilterExpressionAttribute.NULL_ATTRIBUTE) {
|
||||
return;
|
||||
return mi.proceed();
|
||||
}
|
||||
MethodInvocation mi = methodAuthorizationContext.getMethodInvocation();
|
||||
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(), mi);
|
||||
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(AUTHENTICATION_SUPPLIER.get(), mi);
|
||||
Object filterTarget = findFilterTarget(attribute.filterTarget, ctx, mi);
|
||||
this.expressionHandler.filter(filterTarget, attribute.getExpression(), ctx);
|
||||
return mi.proceed();
|
||||
}
|
||||
|
||||
private Object findFilterTarget(String filterTargetName, EvaluationContext ctx, MethodInvocation methodInvocation) {
|
||||
|
@ -122,15 +163,15 @@ public final class PreFilterAuthorizationMethodBeforeAdvice
|
|||
if (preFilter == null) {
|
||||
return PreFilterExpressionAttribute.NULL_ATTRIBUTE;
|
||||
}
|
||||
Expression preFilterExpression = PreFilterAuthorizationMethodBeforeAdvice.this.expressionHandler
|
||||
Expression preFilterExpression = PreFilterAuthorizationMethodInterceptor.this.expressionHandler
|
||||
.getExpressionParser().parseExpression(preFilter.value());
|
||||
return new PreFilterExpressionAttribute(preFilterExpression, preFilter.filterTarget());
|
||||
}
|
||||
|
||||
private PreFilter findPreFilterAnnotation(Method method) {
|
||||
PreFilter preFilter = AnnotationUtils.findAnnotation(method, PreFilter.class);
|
||||
PreFilter preFilter = AuthorizationAnnotationUtils.findUniqueAnnotation(method, PreFilter.class);
|
||||
return (preFilter != null) ? preFilter
|
||||
: AnnotationUtils.findAnnotation(method.getDeclaringClass(), PreFilter.class);
|
||||
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), PreFilter.class);
|
||||
}
|
||||
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.access.annotation;
|
||||
package org.springframework.security.authorization.method;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.function.Supplier;
|
||||
|
@ -22,57 +22,53 @@ 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.access.annotation.Secured;
|
||||
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}
|
||||
* An {@link AuthorizationManager} which can determine if an {@link Authentication} may
|
||||
* invoke 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
|
||||
* @since 5.6
|
||||
*/
|
||||
public final class SecuredAuthorizationManager implements AuthorizationManager<MethodAuthorizationContext> {
|
||||
public final class SecuredAuthorizationManager implements AuthorizationManager<MethodInvocation> {
|
||||
|
||||
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.
|
||||
* Determine if an {@link Authentication} has access to a method by evaluating the
|
||||
* {@link Secured} annotation that {@link MethodInvocation} specifies.
|
||||
* @param authentication the {@link Supplier} of the {@link Authentication} to check
|
||||
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
|
||||
* @param mi the {@link MethodInvocation} 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);
|
||||
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation mi) {
|
||||
AuthorizationManager<MethodInvocation> delegate = this.registry.getManager(mi);
|
||||
return delegate.check(authentication, mi);
|
||||
}
|
||||
|
||||
private static final class SecuredAuthorizationManagerRegistry extends AbstractAuthorizationManagerRegistry {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
AuthorizationManager<MethodAuthorizationContext> resolveManager(Method method, Class<?> targetClass) {
|
||||
AuthorizationManager<MethodInvocation> 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);
|
||||
Secured secured = AuthorizationAnnotationUtils.findUniqueAnnotation(method, Secured.class);
|
||||
return (secured != null) ? secured
|
||||
: AnnotationUtils.findAnnotation(method.getDeclaringClass(), Secured.class);
|
||||
: AuthorizationAnnotationUtils.findUniqueAnnotation(method.getDeclaringClass(), Secured.class);
|
||||
}
|
||||
|
||||
}
|
|
@ -60,4 +60,10 @@ public interface BusinessService extends Serializable {
|
|||
|
||||
List<?> methodReturningAList(String userName, String extraParam);
|
||||
|
||||
@RequireAdminRole
|
||||
@RequireUserRole
|
||||
default void repeatedAnnotations() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,167 +0,0 @@
|
|||
/*
|
||||
* 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,32 @@
|
|||
/*
|
||||
* 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.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
import javax.annotation.security.RolesAllowed;
|
||||
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@RolesAllowed("ADMIN")
|
||||
@Secured("ADMIN")
|
||||
public @interface RequireAdminRole {
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
import javax.annotation.security.RolesAllowed;
|
||||
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@PreAuthorize("hasRole('USER')")
|
||||
@RolesAllowed("ADMIN")
|
||||
@Secured("USER")
|
||||
public @interface RequireUserRole {
|
||||
|
||||
}
|
|
@ -1,104 +0,0 @@
|
|||
/*
|
||||
* 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() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,164 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,168 +0,0 @@
|
|||
/*
|
||||
* 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() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -14,55 +14,56 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.access.method;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
package org.springframework.security.authorization.method;
|
||||
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.aop.MethodMatcher;
|
||||
import org.springframework.security.authentication.TestAuthentication;
|
||||
import org.springframework.aop.Pointcut;
|
||||
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.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;
|
||||
|
||||
/**
|
||||
* Tests for {@link AuthorizationManagerMethodAfterAdvice}.
|
||||
* Tests for {@link AuthorizationManagerAfterMethodInterceptor}.
|
||||
*
|
||||
* @author Evgeniy Cheban
|
||||
*/
|
||||
public class AuthorizationManagerMethodAfterAdviceTests {
|
||||
public class AuthorizationManagerAfterMethodInterceptorTests {
|
||||
|
||||
@Test
|
||||
public void instantiateWhenMethodMatcherNullThenException() {
|
||||
AuthorizationManager<MethodInvocationResult> mockAuthorizationManager = mock(AuthorizationManager.class);
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new AuthorizationManagerMethodAfterAdvice<>(null, mock(AuthorizationManager.class)))
|
||||
.withMessage("methodMatcher cannot be null");
|
||||
.isThrownBy(() -> new AuthorizationManagerAfterMethodInterceptor(null, mockAuthorizationManager))
|
||||
.withMessage("pointcut cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void instantiateWhenAuthorizationManagerNullThenException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new AuthorizationManagerMethodAfterAdvice<>(mock(MethodMatcher.class), null))
|
||||
.isThrownBy(() -> new AuthorizationManagerAfterMethodInterceptor(mock(Pointcut.class), null))
|
||||
.withMessage("authorizationManager cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void beforeWhenMockAuthorizationManagerThenVerifyAndReturnedObject() {
|
||||
Supplier<Authentication> authentication = TestAuthentication::authenticatedUser;
|
||||
public void beforeWhenMockAuthorizationManagerThenVerifyAndReturnedObject() throws Throwable {
|
||||
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);
|
||||
MethodInvocationResult result = new MethodInvocationResult(mockMethodInvocation, new Object());
|
||||
given(mockMethodInvocation.proceed()).willReturn(result.getResult());
|
||||
AuthorizationManager<MethodInvocationResult> mockAuthorizationManager = mock(AuthorizationManager.class);
|
||||
AuthorizationManagerAfterMethodInterceptor advice = new AuthorizationManagerAfterMethodInterceptor(
|
||||
Pointcut.TRUE, mockAuthorizationManager);
|
||||
Object returnedObject = advice.invoke(mockMethodInvocation);
|
||||
assertThat(returnedObject).isEqualTo(result.getResult());
|
||||
verify(mockAuthorizationManager).verify(eq(AuthorizationManagerAfterMethodInterceptor.AUTHENTICATION_SUPPLIER),
|
||||
any(MethodInvocationResult.class));
|
||||
}
|
||||
|
||||
}
|
|
@ -14,52 +14,49 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.access.method;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
package org.springframework.security.authorization.method;
|
||||
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.aop.MethodMatcher;
|
||||
import org.springframework.security.authentication.TestAuthentication;
|
||||
import org.springframework.aop.Pointcut;
|
||||
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}.
|
||||
* Tests for {@link AuthorizationManagerBeforeMethodInterceptor}.
|
||||
*
|
||||
* @author Evgeniy Cheban
|
||||
*/
|
||||
public class AuthorizationManagerMethodBeforeAdviceTests {
|
||||
public class AuthorizationManagerBeforeMethodInterceptorTests {
|
||||
|
||||
@Test
|
||||
public void instantiateWhenMethodMatcherNullThenException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new AuthorizationManagerMethodBeforeAdvice<>(null, mock(AuthorizationManager.class)))
|
||||
.withMessage("methodMatcher cannot be null");
|
||||
.isThrownBy(
|
||||
() -> new AuthorizationManagerBeforeMethodInterceptor(null, mock(AuthorizationManager.class)))
|
||||
.withMessage("pointcut cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void instantiateWhenAuthorizationManagerNullThenException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new AuthorizationManagerMethodBeforeAdvice<>(mock(MethodMatcher.class), null))
|
||||
.isThrownBy(() -> new AuthorizationManagerBeforeMethodInterceptor(mock(Pointcut.class), null))
|
||||
.withMessage("authorizationManager cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void beforeWhenMockAuthorizationManagerThenVerify() {
|
||||
Supplier<Authentication> authentication = TestAuthentication::authenticatedUser;
|
||||
public void beforeWhenMockAuthorizationManagerThenVerify() throws Throwable {
|
||||
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);
|
||||
AuthorizationManagerBeforeMethodInterceptor advice = new AuthorizationManagerBeforeMethodInterceptor(
|
||||
Pointcut.TRUE, mockAuthorizationManager);
|
||||
advice.invoke(mockMethodInvocation);
|
||||
verify(mockAuthorizationManager).verify(AuthorizationManagerBeforeMethodInterceptor.AUTHENTICATION_SUPPLIER,
|
||||
mockMethodInvocation);
|
||||
}
|
||||
|
||||
}
|
|
@ -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.authorization.method;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link AuthorizationMethodPointcuts}
|
||||
*/
|
||||
public class AuthorizationMethodPointcutsTests {
|
||||
|
||||
@Test
|
||||
public void forAnnotationsWhenAnnotationThenClassBasedAnnotationPointcut() {
|
||||
Pointcut preAuthorize = AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class);
|
||||
assertThat(AopUtils.canApply(preAuthorize, ClassController.class)).isTrue();
|
||||
assertThat(AopUtils.canApply(preAuthorize, NoController.class)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forAnnotationsWhenAnnotationThenMethodBasedAnnotationPointcut() {
|
||||
Pointcut preAuthorize = AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class);
|
||||
assertThat(AopUtils.canApply(preAuthorize, MethodController.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forAnnotationsWhenAnnotationThenClassInheritancePointcut() {
|
||||
Pointcut preAuthorize = AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class);
|
||||
assertThat(AopUtils.canApply(preAuthorize, InterfacedClassController.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forAnnotationsWhenAnnotationThenMethodInheritancePointcut() {
|
||||
Pointcut preAuthorize = AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class);
|
||||
assertThat(AopUtils.canApply(preAuthorize, InterfacedMethodController.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forAnnotationsWhenAnnotationThenAnnotationClassInheritancePointcut() {
|
||||
Pointcut preAuthorize = AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class);
|
||||
assertThat(AopUtils.canApply(preAuthorize, InterfacedAnnotationClassController.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forAnnotationsWhenAnnotationThenAnnotationMethodInheritancePointcut() {
|
||||
Pointcut preAuthorize = AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class);
|
||||
assertThat(AopUtils.canApply(preAuthorize, InterfacedAnnotationMethodController.class)).isTrue();
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('APP')")
|
||||
public static class ClassController {
|
||||
|
||||
String methodOne(String paramOne) {
|
||||
return "value";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class MethodController {
|
||||
|
||||
@PreAuthorize("hasAuthority('APP')")
|
||||
String methodOne(String paramOne) {
|
||||
return "value";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class NoController {
|
||||
|
||||
String methodOne(String paramOne) {
|
||||
return "value";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('APP')")
|
||||
public interface ClassControllerInterface {
|
||||
|
||||
String methodOne(String paramOne);
|
||||
|
||||
}
|
||||
|
||||
public static class InterfacedClassController implements ClassControllerInterface {
|
||||
|
||||
public String methodOne(String paramOne) {
|
||||
return "value";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public interface MethodControllerInterface {
|
||||
|
||||
@PreAuthorize("hasAuthority('APP')")
|
||||
String methodOne(String paramOne);
|
||||
|
||||
}
|
||||
|
||||
public static class InterfacedMethodController implements MethodControllerInterface {
|
||||
|
||||
public String methodOne(String paramOne) {
|
||||
return "value";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Target({ ElementType.METHOD, ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@PreAuthorize("hasAuthority('APP')")
|
||||
@interface MyAnnotation {
|
||||
|
||||
}
|
||||
|
||||
@MyAnnotation
|
||||
public interface ClassAnnotationControllerInterface {
|
||||
|
||||
String methodOne(String paramOne);
|
||||
|
||||
}
|
||||
|
||||
public static class InterfacedAnnotationClassController implements ClassAnnotationControllerInterface {
|
||||
|
||||
public String methodOne(String paramOne) {
|
||||
return "value";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public interface MethodAnnotationControllerInterface {
|
||||
|
||||
@MyAnnotation
|
||||
String methodOne(String paramOne);
|
||||
|
||||
}
|
||||
|
||||
public static class InterfacedAnnotationMethodController implements MethodAnnotationControllerInterface {
|
||||
|
||||
public String methodOne(String paramOne) {
|
||||
return "value";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,278 @@
|
|||
/*
|
||||
* 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.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
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.core.annotation.AnnotationConfigurationException;
|
||||
import org.springframework.security.access.intercept.method.MockMethodInvocation;
|
||||
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.assertThatExceptionOfType;
|
||||
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");
|
||||
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
|
||||
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation);
|
||||
assertThat(decision).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkPermitAllWhenRoleUserThenGrantedDecision() throws Exception {
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, "permitAll");
|
||||
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
|
||||
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation);
|
||||
assertThat(decision).isNotNull();
|
||||
assertThat(decision.isGranted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkDenyAllWhenRoleAdminThenDeniedDecision() throws Exception {
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, "denyAll");
|
||||
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
|
||||
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedAdmin, methodInvocation);
|
||||
assertThat(decision).isNotNull();
|
||||
assertThat(decision.isGranted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkRolesAllowedUserOrAdminWhenRoleUserThenGrantedDecision() throws Exception {
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
"rolesAllowedUserOrAdmin");
|
||||
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
|
||||
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation);
|
||||
assertThat(decision).isNotNull();
|
||||
assertThat(decision.isGranted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkRolesAllowedUserOrAdminWhenRoleAdminThenGrantedDecision() throws Exception {
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
"rolesAllowedUserOrAdmin");
|
||||
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
|
||||
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedAdmin, methodInvocation);
|
||||
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");
|
||||
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
|
||||
AuthorizationDecision decision = manager.check(authentication, methodInvocation);
|
||||
assertThat(decision).isNotNull();
|
||||
assertThat(decision.isGranted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkMultipleAnnotationsWhenInvokedThenAnnotationConfigurationException() throws Exception {
|
||||
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password",
|
||||
"ROLE_ANONYMOUS");
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
"multipleAnnotations");
|
||||
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> manager.check(authentication, methodInvocation));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkRequiresAdminWhenClassAnnotationsThenMethodAnnotationsTakePrecedence() throws Exception {
|
||||
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
|
||||
ClassLevelAnnotations.class, "rolesAllowedAdmin");
|
||||
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
|
||||
AuthorizationDecision decision = manager.check(authentication, methodInvocation);
|
||||
assertThat(decision.isGranted()).isFalse();
|
||||
authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN");
|
||||
decision = manager.check(authentication, methodInvocation);
|
||||
assertThat(decision.isGranted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkDeniedWhenClassAnnotationsThenMethodAnnotationsTakePrecedence() throws Exception {
|
||||
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
|
||||
ClassLevelAnnotations.class, "denyAll");
|
||||
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
|
||||
AuthorizationDecision decision = manager.check(authentication, methodInvocation);
|
||||
assertThat(decision.isGranted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkRequiresUserWhenClassAnnotationsThenApplies() throws Exception {
|
||||
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
|
||||
ClassLevelAnnotations.class, "rolesAllowedUser");
|
||||
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
|
||||
AuthorizationDecision decision = manager.check(authentication, methodInvocation);
|
||||
assertThat(decision.isGranted()).isTrue();
|
||||
authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN");
|
||||
decision = manager.check(authentication, methodInvocation);
|
||||
assertThat(decision.isGranted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkInheritedAnnotationsWhenDuplicatedThenAnnotationConfigurationException() throws Exception {
|
||||
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
"inheritedAnnotations");
|
||||
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> manager.check(authentication, methodInvocation));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationException() throws Exception {
|
||||
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
|
||||
ClassLevelAnnotations.class, "inheritedAnnotations");
|
||||
Jsr250AuthorizationManager manager = new Jsr250AuthorizationManager();
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> manager.check(authentication, methodInvocation));
|
||||
}
|
||||
|
||||
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {
|
||||
|
||||
public void doSomething() {
|
||||
|
||||
}
|
||||
|
||||
@DenyAll
|
||||
public void denyAll() {
|
||||
|
||||
}
|
||||
|
||||
@PermitAll
|
||||
public void permitAll() {
|
||||
|
||||
}
|
||||
|
||||
@RolesAllowed({ "USER", "ADMIN" })
|
||||
public void rolesAllowedUserOrAdmin() {
|
||||
|
||||
}
|
||||
|
||||
@RolesAllowed("USER")
|
||||
@DenyAll
|
||||
public void multipleAnnotations() {
|
||||
|
||||
}
|
||||
|
||||
public void inheritedAnnotations() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RolesAllowed("USER")
|
||||
public static class ClassLevelAnnotations implements InterfaceAnnotationsThree {
|
||||
|
||||
@RolesAllowed("ADMIN")
|
||||
public void rolesAllowedAdmin() {
|
||||
|
||||
}
|
||||
|
||||
@DenyAll
|
||||
public void denyAll() {
|
||||
|
||||
}
|
||||
|
||||
public void rolesAllowedUser() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@PermitAll
|
||||
public void inheritedAnnotations() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public interface InterfaceAnnotationsOne {
|
||||
|
||||
@RolesAllowed("ADMIN")
|
||||
void inheritedAnnotations();
|
||||
|
||||
}
|
||||
|
||||
public interface InterfaceAnnotationsTwo {
|
||||
|
||||
@MyRolesAllowed
|
||||
void inheritedAnnotations();
|
||||
|
||||
}
|
||||
|
||||
public interface InterfaceAnnotationsThree {
|
||||
|
||||
@DenyAll
|
||||
void inheritedAnnotations();
|
||||
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@RolesAllowed("USER")
|
||||
public @interface MyRolesAllowed {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -16,21 +16,27 @@
|
|||
|
||||
package org.springframework.security.authorization.method;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
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.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.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
|
@ -57,38 +63,32 @@ public class PostAuthorizeAuthorizationManagerTests {
|
|||
|
||||
@Test
|
||||
public void checkDoSomethingWhenNoPostAuthorizeAnnotationThenNullDecision() throws Exception {
|
||||
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
MockMethodInvocation methodInvocation = 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);
|
||||
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null);
|
||||
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, result);
|
||||
assertThat(decision).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkDoSomethingStringWhenArgIsGrantThenGrantedDecision() throws Exception {
|
||||
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
MockMethodInvocation methodInvocation = 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);
|
||||
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null);
|
||||
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, result);
|
||||
assertThat(decision).isNotNull();
|
||||
assertThat(decision.isGranted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkDoSomethingStringWhenArgIsNotGrantThenDeniedDecision() throws Exception {
|
||||
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
"doSomethingString", new Class[] { String.class }, new Object[] { "deny" });
|
||||
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
|
||||
TestClass.class);
|
||||
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null);
|
||||
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
|
||||
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
|
||||
methodAuthorizationContext);
|
||||
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, result);
|
||||
assertThat(decision).isNotNull();
|
||||
assertThat(decision.isGranted()).isFalse();
|
||||
}
|
||||
|
@ -96,14 +96,11 @@ public class PostAuthorizeAuthorizationManagerTests {
|
|||
@Test
|
||||
public void checkDoSomethingListWhenReturnObjectContainsGrantThenGrantedDecision() throws Exception {
|
||||
List<String> list = Arrays.asList("grant", "deny");
|
||||
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
"doSomethingList", new Class[] { List.class }, new Object[] { list });
|
||||
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
|
||||
TestClass.class);
|
||||
methodAuthorizationContext.setReturnObject(list);
|
||||
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, list);
|
||||
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
|
||||
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
|
||||
methodAuthorizationContext);
|
||||
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, result);
|
||||
assertThat(decision).isNotNull();
|
||||
assertThat(decision.isGranted()).isTrue();
|
||||
}
|
||||
|
@ -111,19 +108,66 @@ public class PostAuthorizeAuthorizationManagerTests {
|
|||
@Test
|
||||
public void checkDoSomethingListWhenReturnObjectNotContainsGrantThenDeniedDecision() throws Exception {
|
||||
List<String> list = Collections.singletonList("deny");
|
||||
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
"doSomethingList", new Class[] { List.class }, new Object[] { list });
|
||||
MethodAuthorizationContext methodAuthorizationContext = new MethodAuthorizationContext(mockMethodInvocation,
|
||||
TestClass.class);
|
||||
methodAuthorizationContext.setReturnObject(list);
|
||||
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, list);
|
||||
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
|
||||
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser,
|
||||
methodAuthorizationContext);
|
||||
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, result);
|
||||
assertThat(decision).isNotNull();
|
||||
assertThat(decision.isGranted()).isFalse();
|
||||
}
|
||||
|
||||
public static class TestClass {
|
||||
@Test
|
||||
public void checkRequiresAdminWhenClassAnnotationsThenMethodAnnotationsTakePrecedence() throws Exception {
|
||||
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
|
||||
ClassLevelAnnotations.class, "securedAdmin");
|
||||
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null);
|
||||
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
|
||||
AuthorizationDecision decision = manager.check(authentication, result);
|
||||
assertThat(decision.isGranted()).isFalse();
|
||||
authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN");
|
||||
decision = manager.check(authentication, result);
|
||||
assertThat(decision.isGranted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkRequiresUserWhenClassAnnotationsThenApplies() throws Exception {
|
||||
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
|
||||
ClassLevelAnnotations.class, "securedUser");
|
||||
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null);
|
||||
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
|
||||
AuthorizationDecision decision = manager.check(authentication, result);
|
||||
assertThat(decision.isGranted()).isTrue();
|
||||
authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN");
|
||||
decision = manager.check(authentication, result);
|
||||
assertThat(decision.isGranted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkInheritedAnnotationsWhenDuplicatedThenAnnotationConfigurationException() throws Exception {
|
||||
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
"inheritedAnnotations");
|
||||
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null);
|
||||
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> manager.check(authentication, result));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationException() throws Exception {
|
||||
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
|
||||
ClassLevelAnnotations.class, "inheritedAnnotations");
|
||||
MethodInvocationResult result = new MethodInvocationResult(methodInvocation, null);
|
||||
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> manager.check(authentication, result));
|
||||
}
|
||||
|
||||
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {
|
||||
|
||||
public void doSomething() {
|
||||
|
||||
|
@ -139,6 +183,58 @@ public class PostAuthorizeAuthorizationManagerTests {
|
|||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inheritedAnnotations() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@PostAuthorize("hasRole('USER')")
|
||||
public static class ClassLevelAnnotations implements InterfaceAnnotationsThree {
|
||||
|
||||
@PostAuthorize("hasRole('ADMIN')")
|
||||
public void securedAdmin() {
|
||||
|
||||
}
|
||||
|
||||
public void securedUser() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@PostAuthorize("hasRole('ADMIN')")
|
||||
public void inheritedAnnotations() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public interface InterfaceAnnotationsOne {
|
||||
|
||||
@PostAuthorize("hasRole('ADMIN')")
|
||||
void inheritedAnnotations();
|
||||
|
||||
}
|
||||
|
||||
public interface InterfaceAnnotationsTwo {
|
||||
|
||||
@PostAuthorize("hasRole('USER')")
|
||||
void inheritedAnnotations();
|
||||
|
||||
}
|
||||
|
||||
public interface InterfaceAnnotationsThree {
|
||||
|
||||
@MyPostAuthorize
|
||||
void inheritedAnnotations();
|
||||
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@PostAuthorize("hasRole('USER')")
|
||||
public @interface MyPostAuthorize {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
/*
|
||||
* 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,186 @@
|
|||
/*
|
||||
* 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.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.aop.MethodMatcher;
|
||||
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
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.prepost.PostFilter;
|
||||
import org.springframework.security.authentication.TestAuthentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Tests for {@link PostFilterAuthorizationMethodInterceptor}.
|
||||
*
|
||||
* @author Evgeniy Cheban
|
||||
*/
|
||||
public class PostFilterAuthorizationMethodInterceptorTests {
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
SecurityContextHolder.getContext().setAuthentication(TestAuthentication.authenticatedUser());
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setExpressionHandlerWhenNotNullThenSetsExpressionHandler() {
|
||||
MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
||||
PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor();
|
||||
advice.setExpressionHandler(expressionHandler);
|
||||
assertThat(advice).extracting("expressionHandler").isEqualTo(expressionHandler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setExpressionHandlerWhenNullThenException() {
|
||||
PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor();
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> advice.setExpressionHandler(null))
|
||||
.withMessage("expressionHandler cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void methodMatcherWhenMethodHasNotPostFilterAnnotationThenNotMatches() throws Exception {
|
||||
PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor();
|
||||
MethodMatcher methodMatcher = advice.getPointcut().getMethodMatcher();
|
||||
assertThat(methodMatcher.matches(NoPostFilterClass.class.getMethod("doSomething"), NoPostFilterClass.class))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void methodMatcherWhenMethodHasPostFilterAnnotationThenMatches() throws Exception {
|
||||
PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor();
|
||||
MethodMatcher methodMatcher = advice.getPointcut().getMethodMatcher();
|
||||
assertThat(
|
||||
methodMatcher.matches(TestClass.class.getMethod("doSomethingArray", String[].class), TestClass.class))
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void afterWhenArrayNotNullThenFilteredArray() throws Throwable {
|
||||
String[] array = { "john", "bob" };
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
"doSomethingArrayClassLevel", new Class[] { String[].class }, new Object[] { array }) {
|
||||
@Override
|
||||
public Object proceed() {
|
||||
return array;
|
||||
}
|
||||
};
|
||||
PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor();
|
||||
Object result = advice.invoke(methodInvocation);
|
||||
assertThat(result).asInstanceOf(InstanceOfAssertFactories.array(String[].class)).containsOnly("john");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkInheritedAnnotationsWhenDuplicatedThenAnnotationConfigurationException() throws Exception {
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
"inheritedAnnotations");
|
||||
PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor();
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> advice.invoke(methodInvocation));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationException() throws Exception {
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ConflictingAnnotations(),
|
||||
ConflictingAnnotations.class, "inheritedAnnotations");
|
||||
PostFilterAuthorizationMethodInterceptor advice = new PostFilterAuthorizationMethodInterceptor();
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> advice.invoke(methodInvocation));
|
||||
}
|
||||
|
||||
@PostFilter("filterObject == 'john'")
|
||||
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {
|
||||
|
||||
@PostFilter("filterObject == 'john'")
|
||||
public String[] doSomethingArray(String[] array) {
|
||||
return array;
|
||||
}
|
||||
|
||||
public String[] doSomethingArrayClassLevel(String[] array) {
|
||||
return array;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inheritedAnnotations() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class NoPostFilterClass {
|
||||
|
||||
public void doSomething() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ConflictingAnnotations implements InterfaceAnnotationsThree {
|
||||
|
||||
@Override
|
||||
@PostFilter("filterObject == 'jack'")
|
||||
public void inheritedAnnotations() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public interface InterfaceAnnotationsOne {
|
||||
|
||||
@PostFilter("filterObject == 'jim'")
|
||||
void inheritedAnnotations();
|
||||
|
||||
}
|
||||
|
||||
public interface InterfaceAnnotationsTwo {
|
||||
|
||||
@PostFilter("filterObject == 'jane'")
|
||||
void inheritedAnnotations();
|
||||
|
||||
}
|
||||
|
||||
public interface InterfaceAnnotationsThree {
|
||||
|
||||
@MyPostFilter
|
||||
void inheritedAnnotations();
|
||||
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@PostFilter("filterObject == 'john'")
|
||||
public @interface MyPostFilter {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -16,17 +16,24 @@
|
|||
|
||||
package org.springframework.security.authorization.method;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
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.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.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
|
||||
/**
|
||||
|
@ -53,43 +60,80 @@ public class PreAuthorizeAuthorizationManagerTests {
|
|||
|
||||
@Test
|
||||
public void checkDoSomethingWhenNoPostAuthorizeAnnotationThenNullDecision() throws Exception {
|
||||
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
MockMethodInvocation methodInvocation = 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);
|
||||
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation);
|
||||
assertThat(decision).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkDoSomethingStringWhenArgIsGrantThenGrantedDecision() throws Exception {
|
||||
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
MockMethodInvocation methodInvocation = 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);
|
||||
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation);
|
||||
assertThat(decision).isNotNull();
|
||||
assertThat(decision.isGranted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkDoSomethingStringWhenArgIsNotGrantThenDeniedDecision() throws Exception {
|
||||
MockMethodInvocation mockMethodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
MockMethodInvocation methodInvocation = 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);
|
||||
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation);
|
||||
assertThat(decision).isNotNull();
|
||||
assertThat(decision.isGranted()).isFalse();
|
||||
}
|
||||
|
||||
public static class TestClass {
|
||||
@Test
|
||||
public void checkRequiresAdminWhenClassAnnotationsThenMethodAnnotationsTakePrecedence() throws Exception {
|
||||
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
|
||||
ClassLevelAnnotations.class, "securedAdmin");
|
||||
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
|
||||
AuthorizationDecision decision = manager.check(authentication, methodInvocation);
|
||||
assertThat(decision.isGranted()).isFalse();
|
||||
authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN");
|
||||
decision = manager.check(authentication, methodInvocation);
|
||||
assertThat(decision.isGranted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkRequiresUserWhenClassAnnotationsThenApplies() throws Exception {
|
||||
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
|
||||
ClassLevelAnnotations.class, "securedUser");
|
||||
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
|
||||
AuthorizationDecision decision = manager.check(authentication, methodInvocation);
|
||||
assertThat(decision.isGranted()).isTrue();
|
||||
authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN");
|
||||
decision = manager.check(authentication, methodInvocation);
|
||||
assertThat(decision.isGranted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkInheritedAnnotationsWhenDuplicatedThenAnnotationConfigurationException() throws Exception {
|
||||
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
"inheritedAnnotations");
|
||||
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> manager.check(authentication, methodInvocation));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationException() throws Exception {
|
||||
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
|
||||
ClassLevelAnnotations.class, "inheritedAnnotations");
|
||||
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> manager.check(authentication, methodInvocation));
|
||||
}
|
||||
|
||||
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {
|
||||
|
||||
public void doSomething() {
|
||||
|
||||
|
@ -100,6 +144,58 @@ public class PreAuthorizeAuthorizationManagerTests {
|
|||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inheritedAnnotations() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@PreAuthorize("hasRole('USER')")
|
||||
public static class ClassLevelAnnotations implements InterfaceAnnotationsThree {
|
||||
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
public void securedAdmin() {
|
||||
|
||||
}
|
||||
|
||||
public void securedUser() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
public void inheritedAnnotations() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public interface InterfaceAnnotationsOne {
|
||||
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
void inheritedAnnotations();
|
||||
|
||||
}
|
||||
|
||||
public interface InterfaceAnnotationsTwo {
|
||||
|
||||
@PreAuthorize("hasRole('USER')")
|
||||
void inheritedAnnotations();
|
||||
|
||||
}
|
||||
|
||||
public interface InterfaceAnnotationsThree {
|
||||
|
||||
@MyPreAuthorize
|
||||
void inheritedAnnotations();
|
||||
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@PreAuthorize("hasRole('USER')")
|
||||
public @interface MyPreAuthorize {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,200 +0,0 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,260 @@
|
|||
/*
|
||||
* 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.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.aop.MethodMatcher;
|
||||
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
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.prepost.PreFilter;
|
||||
import org.springframework.security.authentication.TestAuthentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link PreFilterAuthorizationMethodInterceptor}.
|
||||
*
|
||||
* @author Evgeniy Cheban
|
||||
*/
|
||||
public class PreFilterAuthorizationMethodInterceptorTests {
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
SecurityContextHolder.getContext().setAuthentication(TestAuthentication.authenticatedUser());
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setExpressionHandlerWhenNotNullThenSetsExpressionHandler() {
|
||||
MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
||||
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
|
||||
advice.setExpressionHandler(expressionHandler);
|
||||
assertThat(advice).extracting("expressionHandler").isEqualTo(expressionHandler);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setExpressionHandlerWhenNullThenException() {
|
||||
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> advice.setExpressionHandler(null))
|
||||
.withMessage("expressionHandler cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void methodMatcherWhenMethodHasNotPreFilterAnnotationThenNotMatches() throws Exception {
|
||||
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
|
||||
MethodMatcher methodMatcher = advice.getPointcut().getMethodMatcher();
|
||||
assertThat(methodMatcher.matches(NoPreFilterClass.class.getMethod("doSomething"), NoPreFilterClass.class))
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void methodMatcherWhenMethodHasPreFilterAnnotationThenMatches() throws Exception {
|
||||
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
|
||||
MethodMatcher methodMatcher = advice.getPointcut().getMethodMatcher();
|
||||
assertThat(methodMatcher.matches(TestClass.class.getMethod("doSomethingListFilterTargetMatch", List.class),
|
||||
TestClass.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findFilterTargetWhenNameProvidedAndNotMatchThenException() throws Exception {
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
"doSomethingListFilterTargetNotMatch", new Class[] { List.class }, new Object[] { new ArrayList<>() });
|
||||
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> advice.invoke(methodInvocation)).withMessage(
|
||||
"Filter target was null, or no argument with name 'filterTargetNotMatch' found in method.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findFilterTargetWhenNameProvidedAndMatchAndNullThenException() throws Exception {
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
"doSomethingListFilterTargetMatch", new Class[] { List.class }, new Object[] { null });
|
||||
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> advice.invoke(methodInvocation))
|
||||
.withMessage("Filter target was null, or no argument with name 'list' found in method.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findFilterTargetWhenNameProvidedAndMatchAndNotNullThenFiltersList() throws Throwable {
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add("john");
|
||||
list.add("bob");
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
"doSomethingListFilterTargetMatch", new Class[] { List.class }, new Object[] { list });
|
||||
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
|
||||
advice.invoke(methodInvocation);
|
||||
assertThat(list).hasSize(1);
|
||||
assertThat(list.get(0)).isEqualTo("john");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findFilterTargetWhenNameNotProvidedAndSingleArgListNullThenException() throws Exception {
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
"doSomethingListFilterTargetNotProvided", new Class[] { List.class }, new Object[] { null });
|
||||
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> advice.invoke(methodInvocation))
|
||||
.withMessage("Filter target was null. Make sure you passing the correct value in the method argument.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findFilterTargetWhenNameNotProvidedAndSingleArgListThenFiltersList() throws Throwable {
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add("john");
|
||||
list.add("bob");
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
"doSomethingListFilterTargetNotProvided", new Class[] { List.class }, new Object[] { list });
|
||||
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
|
||||
advice.invoke(methodInvocation);
|
||||
assertThat(list).hasSize(1);
|
||||
assertThat(list.get(0)).isEqualTo("john");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findFilterTargetWhenNameNotProvidedAndSingleArgArrayThenException() throws Exception {
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
"doSomethingArrayFilterTargetNotProvided", new Class[] { String[].class },
|
||||
new Object[] { new String[] {} });
|
||||
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
|
||||
assertThatIllegalStateException().isThrownBy(() -> advice.invoke(methodInvocation)).withMessage(
|
||||
"Pre-filtering on array types is not supported. Using a Collection will solve this problem.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findFilterTargetWhenNameNotProvidedAndNotSingleArgThenException() throws Exception {
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
"doSomethingTwoArgsFilterTargetNotProvided", new Class[] { String.class, List.class },
|
||||
new Object[] { "", new ArrayList<>() });
|
||||
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
|
||||
assertThatIllegalStateException().isThrownBy(() -> advice.invoke(methodInvocation))
|
||||
.withMessage("Unable to determine the method argument for filtering. Specify the filter target.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkInheritedAnnotationsWhenDuplicatedThenAnnotationConfigurationException() throws Exception {
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
"inheritedAnnotations");
|
||||
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> advice.invoke(methodInvocation));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationException() throws Exception {
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ConflictingAnnotations(),
|
||||
ConflictingAnnotations.class, "inheritedAnnotations");
|
||||
PreFilterAuthorizationMethodInterceptor advice = new PreFilterAuthorizationMethodInterceptor();
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> advice.invoke(methodInvocation));
|
||||
}
|
||||
|
||||
@PreFilter("filterObject == 'john'")
|
||||
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
public List<String> doSomethingTwoArgsFilterTargetNotProvided(String s, List<String> list) {
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inheritedAnnotations() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class NoPreFilterClass {
|
||||
|
||||
public void doSomething() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ConflictingAnnotations implements InterfaceAnnotationsThree {
|
||||
|
||||
@Override
|
||||
@PreFilter("filterObject == 'jack'")
|
||||
public void inheritedAnnotations() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public interface InterfaceAnnotationsOne {
|
||||
|
||||
@PreFilter("filterObject == 'jim'")
|
||||
void inheritedAnnotations();
|
||||
|
||||
}
|
||||
|
||||
public interface InterfaceAnnotationsTwo {
|
||||
|
||||
@PreFilter("filterObject == 'jane'")
|
||||
void inheritedAnnotations();
|
||||
|
||||
}
|
||||
|
||||
public interface InterfaceAnnotationsThree {
|
||||
|
||||
@MyPreFilter
|
||||
void inheritedAnnotations();
|
||||
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@PreFilter("filterObject == 'john'")
|
||||
public @interface MyPreFilter {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* 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.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationConfigurationException;
|
||||
import org.springframework.security.access.annotation.Secured;
|
||||
import org.springframework.security.access.intercept.method.MockMethodInvocation;
|
||||
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.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* 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");
|
||||
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
|
||||
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation);
|
||||
assertThat(decision).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkSecuredUserOrAdminWhenRoleUserThenGrantedDecision() throws Exception {
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
"securedUserOrAdmin");
|
||||
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
|
||||
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedUser, methodInvocation);
|
||||
assertThat(decision).isNotNull();
|
||||
assertThat(decision.isGranted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkSecuredUserOrAdminWhenRoleAdminThenGrantedDecision() throws Exception {
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
"securedUserOrAdmin");
|
||||
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
|
||||
AuthorizationDecision decision = manager.check(TestAuthentication::authenticatedAdmin, methodInvocation);
|
||||
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");
|
||||
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
|
||||
AuthorizationDecision decision = manager.check(authentication, methodInvocation);
|
||||
assertThat(decision).isNotNull();
|
||||
assertThat(decision.isGranted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkRequiresAdminWhenClassAnnotationsThenMethodAnnotationsTakePrecedence() throws Exception {
|
||||
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
|
||||
ClassLevelAnnotations.class, "securedAdmin");
|
||||
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
|
||||
AuthorizationDecision decision = manager.check(authentication, methodInvocation);
|
||||
assertThat(decision.isGranted()).isFalse();
|
||||
authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN");
|
||||
decision = manager.check(authentication, methodInvocation);
|
||||
assertThat(decision.isGranted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkRequiresUserWhenClassAnnotationsThenApplies() throws Exception {
|
||||
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
|
||||
ClassLevelAnnotations.class, "securedUser");
|
||||
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
|
||||
AuthorizationDecision decision = manager.check(authentication, methodInvocation);
|
||||
assertThat(decision.isGranted()).isTrue();
|
||||
authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_ADMIN");
|
||||
decision = manager.check(authentication, methodInvocation);
|
||||
assertThat(decision.isGranted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkInheritedAnnotationsWhenDuplicatedThenAnnotationConfigurationException() throws Exception {
|
||||
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class,
|
||||
"inheritedAnnotations");
|
||||
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> manager.check(authentication, methodInvocation));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkInheritedAnnotationsWhenConflictingThenAnnotationConfigurationException() throws Exception {
|
||||
Supplier<Authentication> authentication = () -> new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
MockMethodInvocation methodInvocation = new MockMethodInvocation(new ClassLevelAnnotations(),
|
||||
ClassLevelAnnotations.class, "inheritedAnnotations");
|
||||
SecuredAuthorizationManager manager = new SecuredAuthorizationManager();
|
||||
assertThatExceptionOfType(AnnotationConfigurationException.class)
|
||||
.isThrownBy(() -> manager.check(authentication, methodInvocation));
|
||||
}
|
||||
|
||||
public static class TestClass implements InterfaceAnnotationsOne, InterfaceAnnotationsTwo {
|
||||
|
||||
public void doSomething() {
|
||||
|
||||
}
|
||||
|
||||
@Secured({ "ROLE_USER", "ROLE_ADMIN" })
|
||||
public void securedUserOrAdmin() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inheritedAnnotations() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Secured("ROLE_USER")
|
||||
public static class ClassLevelAnnotations implements InterfaceAnnotationsThree {
|
||||
|
||||
@Secured("ROLE_ADMIN")
|
||||
public void securedAdmin() {
|
||||
|
||||
}
|
||||
|
||||
public void securedUser() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@Secured("ROLE_ADMIN")
|
||||
public void inheritedAnnotations() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public interface InterfaceAnnotationsOne {
|
||||
|
||||
@Secured("ROLE_ADMIN")
|
||||
void inheritedAnnotations();
|
||||
|
||||
}
|
||||
|
||||
public interface InterfaceAnnotationsTwo {
|
||||
|
||||
@Secured("ROLE_USER")
|
||||
void inheritedAnnotations();
|
||||
|
||||
}
|
||||
|
||||
public interface InterfaceAnnotationsThree {
|
||||
|
||||
@MySecured
|
||||
void inheritedAnnotations();
|
||||
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Secured("ROLE_USER")
|
||||
public @interface MySecured {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,51 +1,11 @@
|
|||
[[new]]
|
||||
== What's New in Spring Security 5.5
|
||||
== What's New in Spring Security 5.6
|
||||
|
||||
Spring Security 5.5 provides a number of new features.
|
||||
Spring Security 5.6 provides a number of new features.
|
||||
Below are the highlights of the release.
|
||||
|
||||
[[whats-new-servlet]]
|
||||
=== Servlet
|
||||
* OAuth 2.0 Client
|
||||
|
||||
** Added support for https://github.com/spring-projects/spring-security/pull/9520[Jwt Client Authentication] `private_key_jwt` and `client_secret_jwt`
|
||||
** Added https://github.com/spring-projects/spring-security/pull/9535[Jwt Bearer Authorization Grant] support
|
||||
** Added https://github.com/spring-projects/spring-security/pull/8765[R2DBC implementation] of `ReactiveOAuth2AuthorizedClientService`
|
||||
|
||||
* OAuth 2.0 Resource Server
|
||||
|
||||
** Enhanced JWT decoders https://github.com/spring-projects/spring-security/issues/7160[derivation of signature algorithms]
|
||||
** Improved https://github.com/spring-projects/spring-security/issues/9100[content negotation]
|
||||
** Improved https://github.com/spring-projects/spring-security/issues/9186[multi-tenancy support]
|
||||
|
||||
* SAML 2.0 Service Provider
|
||||
|
||||
** Added https://github.com/spring-projects/spring-security/issues/9095[OpenSAML 4 support]
|
||||
** Enhanced SAML 2.0 https://github.com/spring-projects/spring-security/issues/9131[Assertion] https://github.com/spring-projects/spring-security/issues/9044[decryption]
|
||||
** Added https://github.com/spring-projects/spring-security/issues/9028[file-based configuration] for asserting party metadata
|
||||
** Improved https://github.com/spring-projects/spring-security/issues/9486[`RelyingPartyRegistration` resolution] support
|
||||
** Enhanced relying party metadata https://github.com/spring-projects/spring-security/issues/9317[support]
|
||||
** Added support for https://github.com/spring-projects/spring-security/issues/9177[AP-specified signing methods]
|
||||
|
||||
* Configuration
|
||||
|
||||
** Introduced https://github.com/spring-projects/spring-security/issues/9205[DispatcherType request matcher]
|
||||
** Introduced https://github.com/spring-projects/spring-security/issues/8900[`AuthorizationManager`] for filter security
|
||||
|
||||
* Kotlin DSL
|
||||
|
||||
** Added https://github.com/spring-projects/spring-security/issues/9319[rememberMe support]
|
||||
|
||||
[[whats-new-webflux]]
|
||||
=== WebFlux
|
||||
|
||||
** Added https://github.com/spring-projects/spring-security/issues/8143[Kotlin coroutine support] for `EnableReactiveMethodSecurity`
|
||||
|
||||
[[whats-new-build]]
|
||||
=== Build
|
||||
|
||||
The build has been modernized in a number of different ways.
|
||||
Here are a couple of highlights:
|
||||
|
||||
* All sample applications have been moved to https://github.com/spring-projects/spring-security-samples[a separate project]
|
||||
* The full build now https://github.com/spring-projects/spring-security/issues/9419[works with JDK 11]
|
||||
** Introduced https://github.com/spring-projects/spring-security/pull/9630[`AuthorizationManager`] for method security
|
||||
|
|
|
@ -759,6 +759,7 @@ A default implementation (with no ACL support) will be used if not supplied.
|
|||
|
||||
* <<nsa-global-method-security,global-method-security>>
|
||||
* <<nsa-http,http>>
|
||||
* <<nsa-method-security,method-security>>
|
||||
* <<nsa-websocket-message-broker,websocket-message-broker>>
|
||||
|
||||
|
||||
|
@ -2374,9 +2375,41 @@ If omitted, the namespace will generate a random value, preventing its accidenta
|
|||
Cannot be empty.
|
||||
|
||||
|
||||
[[nsa-method-security]]
|
||||
|
||||
=== Method Security
|
||||
|
||||
[[nsa-method-security]]
|
||||
==== <method-security>
|
||||
This element is the primary means of adding support for securing methods on Spring Security beans.
|
||||
Methods can be secured by the use of annotations (defined at the interface or class level) or by defining a set of pointcuts.
|
||||
|
||||
[[nsa-method-security-attributes]]
|
||||
===== <method-security> attributes
|
||||
|
||||
[[nsa-method-security-pre-post-enabled]]
|
||||
* **pre-post-enabled**
|
||||
Enables Spring Security's pre and post invocation annotations (@PreFilter, @PreAuthorize, @PostFilter, @PostAuthorize) for this application context.
|
||||
Defaults to "true".
|
||||
|
||||
[[nsa-method-security-secured-enabled]]
|
||||
* **secured-enabled**
|
||||
Enables Spring Security's @Secured annotation for this application context.
|
||||
Defaults to "false".
|
||||
|
||||
[[nsa-method-security-jsr250-enabled]]
|
||||
* **jsr250-enabled**
|
||||
Enables JSR-250 authorization annotations (@RolesAllowed, @PermitAll, @DenyAll) for this application context.
|
||||
Defaults to "false".
|
||||
|
||||
[[nsa-method-security-proxy-target-class]]
|
||||
* **proxy-target-class**
|
||||
If true, class based proxying will be used instead of interface based proxying.
|
||||
Defaults to "false".
|
||||
|
||||
[[nsa-method-security-children]]
|
||||
===== Child Elements of <method-security>
|
||||
|
||||
* <<nsa-expression-handler,expression-handler>>
|
||||
|
||||
[[nsa-global-method-security]]
|
||||
==== <global-method-security>
|
||||
|
|
|
@ -6,6 +6,598 @@ It provides support for JSR-250 annotation security as well as the framework's o
|
|||
From 3.0 you can also make use of new <<el-access,expression-based annotations>>.
|
||||
You can apply security to a single bean, using the `intercept-methods` element to decorate the bean declaration, or you can secure multiple beans across the entire service layer using the AspectJ style pointcuts.
|
||||
|
||||
=== EnableMethodSecurity
|
||||
|
||||
In Spring Security 5.6, we can enable annotation-based security using the `@EnableMethodSecurity` annotation on any `@Configuration` instance.
|
||||
|
||||
This improves upon `@EnableGlobalMethodSecurity` in a number of ways. `@EnableMethodSecurity`:
|
||||
|
||||
1. Uses the simplified `AuthorizationManager` API instead of metadata sources, config attributes, decision managers, and voters.
|
||||
This simplifies reuse and customization.
|
||||
2. Favors direct bean-based configuration, instead of requiring extending `GlobalMethodSecurityConfiguration` to customize beans
|
||||
3. Is built using native Spring AOP, removing abstractions and allowing you to use Spring AOP building blocks to customize
|
||||
4. Checks for conflicting annotations to ensure an unambiguous security configuration
|
||||
5. Complies with JSR-250
|
||||
6. Enables `@PreAuthorize`, `@PostAuthorize`, `@PreFilter`, and `@PostFilter` by default
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
For earlier versions, please read about similar support with <<jc-enable-global-method-security, @EnableGlobalMethodSecurity>>.
|
||||
====
|
||||
|
||||
For example, the following would enable Spring Security's `@PreAuthorize` annotation:
|
||||
|
||||
.Method Security Configuration
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@EnableMethodSecurity
|
||||
public class MethodSecurityConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@EnableMethodSecurity
|
||||
class MethodSecurityConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<sec:method-security/>
|
||||
----
|
||||
====
|
||||
|
||||
Adding an annotation to a method (on a class or interface) would then limit the access to that method accordingly.
|
||||
Spring Security's native annotation support defines a set of attributes for the method.
|
||||
These will be passed to the `DefaultAuthorizationMethodInterceptorChain` for it to make the actual decision:
|
||||
|
||||
.Method Security Annotation Usage
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public interface BankService {
|
||||
@PreAuthorize("hasRole('USER')")
|
||||
Account readAccount(Long id);
|
||||
|
||||
@PreAuthorize("hasRole('USER')")
|
||||
List<Account> findAccounts();
|
||||
|
||||
@PreAuthorize("hasRole('TELLER')")
|
||||
Account post(Account account, Double amount);
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
interface BankService {
|
||||
@PreAuthorize("hasRole('USER')")
|
||||
fun readAccount(id : Long) : Account
|
||||
|
||||
@PreAuthorize("hasRole('USER')")
|
||||
fun findAccounts() : List<Account>
|
||||
|
||||
@PreAuthorize("hasRole('TELLER')")
|
||||
fun post(account : Account, amount : Double) : Account
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
You can enable support for Spring Security's `@Secured` annotation using:
|
||||
|
||||
.@Secured Configuration
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@EnableMethodSecurity(securedEnabled = true)
|
||||
public class MethodSecurityConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@EnableMethodSecurity(securedEnabled = true)
|
||||
class MethodSecurityConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<sec:method-security secured-enabled="true"/>
|
||||
----
|
||||
====
|
||||
|
||||
or JSR-250 using:
|
||||
|
||||
.JSR-250 Configuration
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@EnableMethodSecurity(jsr250Enabled = true)
|
||||
public class MethodSecurityConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@EnableMethodSecurity(jsr250Enabled = true)
|
||||
class MethodSecurityConfig {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<sec:method-security jsr250-enabled="true"/>
|
||||
----
|
||||
====
|
||||
|
||||
==== Customizing Authorization
|
||||
|
||||
Spring Security's `@PreAuthorize`, `@PostAuthorize`, `@PreFilter`, and `@PostFilter` ship with rich expression-based support.
|
||||
|
||||
[[jc-method-security-custom-expression-handler]]
|
||||
If you need to customize the way that expressions are handled, you can expose a custom `MethodSecurityExpressionHandler`, like so:
|
||||
|
||||
.Custom MethodSecurityExpressionHandler
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
static MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
|
||||
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
|
||||
handler.setTrustResolver(myCustomTrustResolver);
|
||||
return handler;
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
companion object {
|
||||
@Bean
|
||||
fun methodSecurityExpressionHandler() : MethodSecurityExpressionHandler {
|
||||
val handler = DefaultMethodSecurityExpressionHandler();
|
||||
handler.setTrustResolver(myCustomTrustResolver);
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<sec:method-security>
|
||||
<sec:expression-handler ref="myExpressionHandler"/>
|
||||
</sec:method-security>
|
||||
|
||||
<bean id="myExpressionHandler"
|
||||
class="org.springframework.security.messaging.access.expression.DefaultMessageSecurityExpressionHandler">
|
||||
<property name="trustResolver" ref="myCustomTrustResolver"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
|
||||
[TIP]
|
||||
====
|
||||
We expose `MethodSecurityExpressionHandler` using a `static` method to ensure that Spring publishes it before it initializes Spring Security's method security `@Configuration` classes
|
||||
====
|
||||
|
||||
Also, for role-based authorization, Spring Security adds a default `ROLE_` prefix, which is uses when evaluating expressions like `hasRole`.
|
||||
|
||||
[[jc-method-security-custom-granted-authority-defaults]]
|
||||
You can configure the authorization rules to use a different prefix by exposing a `GrantedAuthorityDefaults` bean, like so:
|
||||
|
||||
.Custom MethodSecurityExpressionHandler
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
|
||||
return new GrantedAuthorityDefaults("MYPREFIX_");
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
companion object {
|
||||
@Bean
|
||||
fun grantedAuthorityDefaults() : GrantedAuthorityDefaults {
|
||||
return GrantedAuthorityDefaults("MYPREFIX_");
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<sec:method-security/>
|
||||
|
||||
<bean id="grantedAuthorityDefaults" class="org.springframework.security.config.core.GrantedAuthorityDefaults">
|
||||
<constructor-arg value="MYPREFIX_"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
|
||||
[TIP]
|
||||
====
|
||||
We expose `GrantedAuthorityDefaults` using a `static` method to ensure that Spring publishes it before it initializes Spring Security's method security `@Configuration` classes
|
||||
====
|
||||
|
||||
[[jc-method-security-custom-authorization-manager]]
|
||||
==== Custom Authorization Managers
|
||||
|
||||
Method authorization is a combination of before- and after-method authorization.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Before-method authorization is performed before the method is invoked.
|
||||
If that authorization denies access, the method is not invoked, and an `AccessDeniedException` is thrown
|
||||
After-method authorization is performed after the method is invoked, but before the method returns to the caller.
|
||||
If that authorization denies access, the value is not returned, and an `AccessDeniedException` is thrown
|
||||
====
|
||||
|
||||
To recreate what adding `@EnableMethodSecurity` does by default, you would publish the following configuration:
|
||||
|
||||
.Full Pre-post Method Security Configuration
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@EnableMethodSecurity(prePostEnabled = false)
|
||||
class MethodSecurityConfig {
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
Advisor preFilterAuthorizationMethodInterceptor() {
|
||||
return new PreFilterAuthorizationMethodInterceptor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
Advisor preAuthorizeAuthorizationMethodInterceptor() {
|
||||
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
Advisor postAuthorizeAuthorizationMethodInterceptor() {
|
||||
return AuthorizationManagerAfterMethodInterceptor.postAuthorize();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
Advisor postFilterAuthorizationMethodInterceptor() {
|
||||
return new PostFilterAuthorizationMethodInterceptor();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@EnableMethodSecurity(prePostEnabled = false)
|
||||
class MethodSecurityConfig {
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
fun preFilterAuthorizationMethodInterceptor() : Advisor {
|
||||
return PreFilterAuthorizationMethodInterceptor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
fun preAuthorizeAuthorizationMethodInterceptor() : Advisor {
|
||||
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
fun postAuthorizeAuthorizationMethodInterceptor() : Advisor {
|
||||
return AuthorizationManagerAfterMethodInterceptor.postAuthorize();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
fun postFilterAuthorizationMethodInterceptor() : Advisor {
|
||||
return PostFilterAuthorizationMethodInterceptor();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<sec:method-security pre-post-enabled="false"/>
|
||||
|
||||
<aop:config/>
|
||||
|
||||
<bean id="preFilterAuthorizationMethodInterceptor"
|
||||
class="org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor"/>
|
||||
<bean id="preAuthorizeAuthorizationMethodInterceptor"
|
||||
class="org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor"
|
||||
factory-method="preAuthorize"/>
|
||||
<bean id="postAuthorizeAuthorizationMethodInterceptor"
|
||||
class="org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor"
|
||||
factory-method="postAuthorize"/>
|
||||
<bean id="postFilterAuthorizationMethodInterceptor"
|
||||
class="org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor"/>
|
||||
----
|
||||
====
|
||||
|
||||
Notice that Spring Security's method security is built using Spring AOP.
|
||||
So, interceptors are invoked based on the order specified.
|
||||
This can be customized by calling `setOrder` on the interceptor instances like so:
|
||||
|
||||
.Publish Custom Advisor
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
Advisor postFilterAuthorizationMethodInterceptor() {
|
||||
PostFilterAuthorizationMethodInterceptor interceptor = new PostFilterAuthorizationMethodInterceptor();
|
||||
interceptor.setOrder(AuthorizationInterceptorOrders.POST_AUTHORIZE.getOrder() - 1);
|
||||
return interceptor;
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
fun postFilterAuthorizationMethodInterceptor() : Advisor {
|
||||
val interceptor = PostFilterAuthorizationMethodInterceptor();
|
||||
interceptor.setOrder(AuthorizationInterceptorOrders.POST_AUTHORIZE.getOrder() - 1);
|
||||
return interceptor;
|
||||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<bean id="postFilterAuthorizationMethodInterceptor"
|
||||
class="org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor">
|
||||
<property name="order"
|
||||
value="#{T(org.springframework.security.authorization.method.AuthorizationInterceptorsOrder).POST_AUTHORIZE.getOrder() -1}"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
|
||||
You may want to only support `@PreAuthorize` in your application, in which case you can do the following:
|
||||
|
||||
|
||||
.Only @PreAuthorize Configuration
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@EnableMethodSecurity(prePostEnabled = false)
|
||||
class MethodSecurityConfig {
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
Advisor preAuthorize() {
|
||||
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@EnableMethodSecurity(prePostEnabled = false)
|
||||
class MethodSecurityConfig {
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
fun preAuthorize() : Advisor {
|
||||
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize()
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<sec:method-security pre-post-enabled="false"/>
|
||||
|
||||
<aop:config/>
|
||||
|
||||
<bean id="preAuthorizeAuthorizationMethodInterceptor"
|
||||
class="org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor"
|
||||
factory-method="preAuthorize"/>
|
||||
----
|
||||
====
|
||||
|
||||
Or, you may have a custom before-method `AuthorizationManager` that you want to add to the list.
|
||||
|
||||
In this case, you will need to tell Spring Security both the `AuthorizationManager` and to which methods and classes your authorization manager applies.
|
||||
|
||||
Thus, you can configure Spring Security to invoke your `AuthorizationManager` in between `@PreAuthorize` and `@PostAuthorize` like so:
|
||||
|
||||
.Custom Before Advisor
|
||||
====
|
||||
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@EnableMethodSecurity
|
||||
class MethodSecurityConfig {
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public Advisor customAuthorize() {
|
||||
JdkRegexpMethodPointcut pattern = new JdkRegexpMethodPointcut();
|
||||
pattern.setPattern("org.mycompany.myapp.service.*");
|
||||
AuthorizationManager<MethodInvocation> rule = AuthorityAuthorizationManager.isAuthenticated();
|
||||
AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(pattern, rule);
|
||||
interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1);
|
||||
return interceptor;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@EnableMethodSecurity
|
||||
class MethodSecurityConfig {
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
fun customAuthorize() : Advisor {
|
||||
val pattern = JdkRegexpMethodPointcut();
|
||||
pattern.setPattern("org.mycompany.myapp.service.*");
|
||||
val rule = AuthorityAuthorizationManager.isAuthenticated();
|
||||
val interceptor = AuthorizationManagerBeforeMethodInterceptor(pattern, rule);
|
||||
interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1);
|
||||
return interceptor;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<sec:method-security/>
|
||||
|
||||
<aop:config/>
|
||||
|
||||
<bean id="customAuthorize"
|
||||
class="org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor">
|
||||
<constructor-arg>
|
||||
<bean class="org.springframework.aop.support.JdkRegexpMethodPointcut">
|
||||
<property name="pattern" value="org.mycompany.myapp.service.*"/>
|
||||
</bean>
|
||||
</constructor-arg>
|
||||
<constructor-arg>
|
||||
<bean class="org.springframework.security.authorization.AuthorityAuthorizationManager"
|
||||
factory-method="isAuthenticated"/>
|
||||
</constructor-arg>
|
||||
<property name="order"
|
||||
value="#{T(org.springframework.security.authorization.method.AuthorizationInterceptorsOrder).PRE_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1}"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
|
||||
[TIP]
|
||||
====
|
||||
You can place your interceptor in between Spring Security method interceptors using the order constants specified in `AuthorizationInterceptorsOrder`.
|
||||
====
|
||||
|
||||
The same can be done for after-method authorization.
|
||||
After-method authorization is generally concerned with analysing the return value to verify access.
|
||||
|
||||
For example, you might have a method that confirms that the account requested actually belongs to the logged-in user like so:
|
||||
|
||||
.@PostAuthorize example
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
public interface BankService {
|
||||
|
||||
@PreAuthorize("hasRole('USER')")
|
||||
@PostAuthorize("returnObject.owner == authentication.name")
|
||||
Account readAccount(Long id);
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
interface BankService {
|
||||
|
||||
@PreAuthorize("hasRole('USER')")
|
||||
@PostAuthorize("returnObject.owner == authentication.name")
|
||||
fun readAccount(id : Long) : Account
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
You can supply your own `AuthorizationMethodInterceptor` to customize how access to the return value is evaluated.
|
||||
|
||||
For example, if you have your own custom annotation, you can configure it like so:
|
||||
|
||||
|
||||
.Custom After Advisor
|
||||
====
|
||||
.Java
|
||||
[source,java,role="primary"]
|
||||
----
|
||||
@EnableMethodSecurity
|
||||
class MethodSecurityConfig {
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public Advisor customAuthorize(AuthorizationManager<MethodInvocationResult> rules) {
|
||||
AnnotationMethodMatcher pattern = new AnnotationMethodMatcher(MySecurityAnnotation.class);
|
||||
AuthorizationManagerAfterMethodInterceptor interceptor = new AuthorizationManagerAfterMethodInterceptor(pattern, rules);
|
||||
interceptor.setOrder(AuthorizationInterceptorsOrder.POST_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1);
|
||||
return interceptor;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
.Kotlin
|
||||
[source,kotlin,role="secondary"]
|
||||
----
|
||||
@EnableMethodSecurity
|
||||
class MethodSecurityConfig {
|
||||
@Bean
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
fun customAuthorize(rules : AuthorizationManager<MethodInvocationResult>) : Advisor {
|
||||
val pattern = AnnotationMethodMatcher(MySecurityAnnotation::class.java);
|
||||
val interceptor = AuthorizationManagerAfterMethodInterceptor(pattern, rules);
|
||||
interceptor.setOrder(AuthorizationInterceptorsOrder.POST_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1);
|
||||
return interceptor;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
.Xml
|
||||
[source,xml,role="secondary"]
|
||||
----
|
||||
<sec:method-security/>
|
||||
|
||||
<aop:config/>
|
||||
|
||||
<bean id="customAuthorize"
|
||||
class="org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor">
|
||||
<constructor-arg>
|
||||
<bean class="org.springframework.aop.support.annotation.AnnotationMethodMatcher">
|
||||
<constructor-arg value="#{T(org.mycompany.MySecurityAnnotation)}"/>
|
||||
</bean>
|
||||
</constructor-arg>
|
||||
<constructor-arg>
|
||||
<bean class="org.springframework.security.authorization.AuthorityAuthorizationManager"
|
||||
factory-method="isAuthenticated"/>
|
||||
</constructor-arg>
|
||||
<property name="order"
|
||||
value="#{T(org.springframework.security.authorization.method.AuthorizationInterceptorsOrder).PRE_AUTHORIZE_ADVISOR_ORDER.getOrder() + 1}"/>
|
||||
</bean>
|
||||
----
|
||||
====
|
||||
|
||||
and it will be invoked after the `@PostAuthorize` interceptor.
|
||||
|
||||
[[jc-enable-global-method-security]]
|
||||
=== EnableGlobalMethodSecurity
|
||||
|
||||
We can enable annotation-based security using the `@EnableGlobalMethodSecurity` annotation on any `@Configuration` instance.
|
||||
|
|
Loading…
Reference in New Issue