NamespaceGlobalMethodSecurity groovy->java

Note that the `WhenUsingAspectJ` tests are still simply verifying structure instead of behavior. This is because the project appearsto be misconfigured in some way such that AspectJ advice isn't getting woven in at runtime. The original Groovy tests also only verified structure and they may be that way for a similar reason.

Either way, I will open up a ticket so we can review why that is the case and if there is a good fix.

Issue: gh-4939
This commit is contained in:
Josh Cummings 2018-03-03 00:05:40 -07:00 committed by Rob Winch
parent c91ca0584c
commit 3121f9c000
5 changed files with 637 additions and 536 deletions

View File

@ -1,92 +0,0 @@
/*
* Copyright 2002-2013 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
*
* http://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 static org.assertj.core.api.Assertions.assertThat
import static org.junit.Assert.fail
import java.io.Serializable;
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Configuration
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.method.configuration.NamespaceGlobalMethodSecurityTests.BaseMethodConfig;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder
/**
*
* @author Rob Winch
*/
public class NamespaceGlobalMethodSecurityExpressionHandlerTests extends BaseSpringSpec {
def setup() {
SecurityContextHolder.getContext().setAuthentication(
new TestingAuthenticationToken("user", "password","ROLE_USER"))
}
def "global-method-security/expression-handler @PreAuthorize"() {
setup:
context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomAccessDecisionManagerConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
when:
service.hasPermission("granted")
then:
noExceptionThrown()
when:
service.hasPermission("denied")
then:
thrown(AccessDeniedException)
}
def "global-method-security/expression-handler @PostAuthorize"() {
setup:
context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomAccessDecisionManagerConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
when:
service.postHasPermission("granted")
then:
noExceptionThrown()
when:
service.postHasPermission("denied")
then:
thrown(AccessDeniedException)
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
public static class CustomAccessDecisionManagerConfig extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler()
expressionHandler.permissionEvaluator = new PermissionEvaluator() {
boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
"granted" == targetDomainObject
}
boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
throw new UnsupportedOperationException()
}
}
return expressionHandler
}
}
}

View File

@ -1,444 +0,0 @@
/*
* Copyright 2002-2013 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
*
* http://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.security.access.annotation.Jsr250MethodSecurityMetadataSource
import org.springframework.security.access.intercept.aspectj.AspectJMethodSecurityInterceptor
import static org.assertj.core.api.Assertions.assertThat
import static org.junit.Assert.fail
import java.lang.reflect.Method
import org.springframework.security.access.intercept.aspectj.aspect.AnnotationSecurityAspect
import org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
import org.springframework.beans.factory.BeanCreationException
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.context.annotation.AdviceMode
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Import
import org.springframework.core.Ordered
import org.springframework.security.access.AccessDecisionManager
import org.springframework.security.access.AccessDeniedException
import org.springframework.security.access.ConfigAttribute
import org.springframework.security.access.SecurityConfig
import org.springframework.security.access.intercept.AfterInvocationManager
import org.springframework.security.access.intercept.RunAsManager
import org.springframework.security.access.intercept.RunAsManagerImpl
import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor
import org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor
import org.springframework.security.access.method.AbstractMethodSecurityMetadataSource
import org.springframework.security.access.method.MethodSecurityMetadataSource
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.TestingAuthenticationToken
import org.springframework.security.config.annotation.BaseSpringSpec
import org.springframework.security.config.annotation.authentication.BaseAuthenticationConfig;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration
import org.springframework.security.core.Authentication
import org.springframework.security.core.context.SecurityContextHolder
/**
*
* @author Rob Winch
*/
public class NamespaceGlobalMethodSecurityTests extends BaseSpringSpec {
def setup() {
SecurityContextHolder.getContext().setAuthentication(
new TestingAuthenticationToken("user", "password","ROLE_USER"))
}
// --- access-decision-manager-ref ---
def "custom AccessDecisionManager can be used"() {
setup: "Create an instance with an AccessDecisionManager that always denies access"
context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomAccessDecisionManagerConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
when:
service.preAuthorize()
then:
thrown(AccessDeniedException)
when:
service.secured()
then:
thrown(AccessDeniedException)
}
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public static class CustomAccessDecisionManagerConfig extends GlobalMethodSecurityConfiguration {
@Override
protected AccessDecisionManager accessDecisionManager() {
return new DenyAllAccessDecisionManager()
}
public static class DenyAllAccessDecisionManager implements AccessDecisionManager {
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) {
throw new AccessDeniedException("Always Denied")
}
public boolean supports(ConfigAttribute attribute) {
return true
}
public boolean supports(Class<?> clazz) {
return true
}
}
}
// --- authentication-manager-ref ---
def "custom AuthenticationManager can be used"() {
when:
context = new AnnotationConfigApplicationContext(CustomAuthenticationConfig)
MethodSecurityInterceptor interceptor = context.getBean(MethodSecurityInterceptor)
interceptor.authenticationManager.authenticate(SecurityContextHolder.context.authentication)
then:
thrown(UnsupportedOperationException)
}
@EnableGlobalMethodSecurity
public static class CustomAuthenticationConfig extends GlobalMethodSecurityConfiguration {
@Override
protected AuthenticationManager authenticationManager() {
return new AuthenticationManager() {
Authentication authenticate(Authentication authentication) {
throw new UnsupportedOperationException()
}
}
}
}
// --- jsr250-annotations ---
def "enable jsr250"() {
when:
context = new AnnotationConfigApplicationContext(Jsr250Config)
MethodSecurityService service = context.getBean(MethodSecurityService)
then: "@Secured and @PreAuthorize are ignored"
service.secured() == null
service.preAuthorize() == null
when: "@DenyAll method invoked"
service.jsr250()
then: "access is denied"
thrown(AccessDeniedException)
when: "@PermitAll method invoked"
String jsr250PermitAll = service.jsr250PermitAll()
then: "access is allowed"
jsr250PermitAll == null
}
@EnableGlobalMethodSecurity(jsr250Enabled = true)
@Configuration
public static class Jsr250Config extends BaseMethodConfig {
}
// --- metadata-source-ref ---
def "custom MethodSecurityMetadataSource can be used with higher priority than other sources"() {
setup:
context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomMethodSecurityMetadataSourceConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
when:
service.preAuthorize()
then:
thrown(AccessDeniedException)
when:
service.secured()
then:
thrown(AccessDeniedException)
when:
service.jsr250()
then:
thrown(AccessDeniedException)
}
@EnableGlobalMethodSecurity
public static class CustomMethodSecurityMetadataSourceConfig extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return new AbstractMethodSecurityMetadataSource() {
public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
// require ROLE_NOBODY for any method on MethodSecurityService class
return MethodSecurityService.isAssignableFrom(targetClass) ? [new SecurityConfig("ROLE_NOBODY")] : []
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null
}
}
}
}
// --- mode ---
def "aspectj mode works"() {
when:
context = new AnnotationConfigApplicationContext(AspectJModeConfig)
then:
context.getBean(AnnotationSecurityAspect)
context.getBean(AspectJMethodSecurityInterceptor)
}
@EnableGlobalMethodSecurity(mode = AdviceMode.ASPECTJ, proxyTargetClass = true)
public static class AspectJModeConfig extends BaseMethodConfig {
}
def "aspectj mode works extending GlobalMethodSecurityConfiguration"() {
when:
context = new AnnotationConfigApplicationContext(BaseMethodConfig,AspectJModeExtendsGMSCConfig)
then:
context.getBean(AnnotationSecurityAspect)
context.getBean(AspectJMethodSecurityInterceptor)
}
@EnableGlobalMethodSecurity(mode = AdviceMode.ASPECTJ)
public static class AspectJModeExtendsGMSCConfig extends GlobalMethodSecurityConfiguration {
}
// --- order ---
def order() {
when:
context = new AnnotationConfigApplicationContext(CustomOrderConfig)
MethodSecurityMetadataSourceAdvisor advisor = context.getBean(MethodSecurityMetadataSourceAdvisor)
then:
advisor.order == 135
}
@EnableGlobalMethodSecurity(order = 135)
public static class CustomOrderConfig extends BaseMethodConfig {
}
def "order is defaulted to Ordered.LOWEST_PRECEDENCE when using @EnableGlobalMethodSecurity"() {
when:
context = new AnnotationConfigApplicationContext(DefaultOrderConfig)
MethodSecurityMetadataSourceAdvisor advisor = context.getBean(MethodSecurityMetadataSourceAdvisor)
then:
advisor.order == Ordered.LOWEST_PRECEDENCE
}
@EnableGlobalMethodSecurity
public static class DefaultOrderConfig extends BaseMethodConfig {
}
def "order is defaulted to Ordered.LOWEST_PRECEDENCE when extending GlobalMethodSecurityConfiguration"() {
when:
context = new AnnotationConfigApplicationContext(BaseMethodConfig,DefaultOrderExtendsMethodSecurityConfig)
MethodSecurityMetadataSourceAdvisor advisor = context.getBean(MethodSecurityMetadataSourceAdvisor)
then:
advisor.order == Ordered.LOWEST_PRECEDENCE
}
@EnableGlobalMethodSecurity
public static class DefaultOrderExtendsMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
}
// --- pre-post-annotations ---
def preAuthorize() {
when:
context = new AnnotationConfigApplicationContext(PreAuthorizeConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
then:
service.secured() == null
service.jsr250() == null
when:
service.preAuthorize()
then:
thrown(AccessDeniedException)
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public static class PreAuthorizeConfig extends BaseMethodConfig {
}
def "prePostEnabled extends GlobalMethodSecurityConfiguration"() {
when:
context = new AnnotationConfigApplicationContext(BaseMethodConfig,PreAuthorizeExtendsGMSCConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
then:
service.secured() == null
service.jsr250() == null
when:
service.preAuthorize()
then:
thrown(AccessDeniedException)
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public static class PreAuthorizeExtendsGMSCConfig extends GlobalMethodSecurityConfiguration {
}
// --- proxy-target-class ---
def "proxying classes works"() {
when:
context = new AnnotationConfigApplicationContext(ProxyTargetClass)
MethodSecurityServiceImpl service = context.getBean(MethodSecurityServiceImpl)
then:
noExceptionThrown()
}
@EnableGlobalMethodSecurity(proxyTargetClass = true)
@Configuration
public static class ProxyTargetClass extends BaseMethodConfig {
}
def "proxying interfaces works"() {
when:
context = new AnnotationConfigApplicationContext(PreAuthorizeConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
then: "we get an instance of the interface"
noExceptionThrown()
when: "try to cast to the class"
MethodSecurityServiceImpl serviceImpl = service
then: "we get a class cast exception"
thrown(ClassCastException)
}
// --- run-as-manager-ref ---
def "custom RunAsManager"() {
when:
context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomRunAsManagerConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
then:
service.runAs().authorities.find { it.authority == "ROLE_RUN_AS_SUPER"}
}
@EnableGlobalMethodSecurity(securedEnabled = true)
public static class CustomRunAsManagerConfig extends GlobalMethodSecurityConfiguration {
@Override
protected RunAsManager runAsManager() {
RunAsManagerImpl runAsManager = new RunAsManagerImpl()
runAsManager.setKey("some key")
return runAsManager
}
}
// --- secured-annotation ---
def "secured enabled"() {
setup:
context = new AnnotationConfigApplicationContext(SecuredConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
when:
service.secured()
then:
thrown(AccessDeniedException)
and: "service with ROLE_USER allowed"
service.securedUser() == null
and:
service.preAuthorize() == null
service.jsr250() == null
}
@EnableGlobalMethodSecurity(securedEnabled = true)
@Configuration
public static class SecuredConfig extends BaseMethodConfig {
}
// --- after-invocation-provider
def "custom AfterInvocationManager"() {
setup:
context = new AnnotationConfigApplicationContext(BaseMethodConfig,CustomAfterInvocationManagerConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
when:
service.preAuthorizePermitAll()
then:
AccessDeniedException e = thrown()
e.message == "custom AfterInvocationManager"
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
public static class CustomAfterInvocationManagerConfig extends GlobalMethodSecurityConfiguration {
@Override
protected AfterInvocationManager afterInvocationManager() {
return new AfterInvocationManagerStub()
}
public static class AfterInvocationManagerStub implements AfterInvocationManager {
Object decide(Authentication authentication, Object object, Collection<ConfigAttribute> attributes,
Object returnedObject) throws AccessDeniedException {
throw new AccessDeniedException("custom AfterInvocationManager")
}
boolean supports(ConfigAttribute attribute) {
return true
}
boolean supports(Class<?> clazz) {
return true
}
}
}
// --- misc ---
def "good error message when no Enable annotation"() {
when:
context = new AnnotationConfigApplicationContext(ExtendsNoEnableAnntotationConfig)
MethodSecurityInterceptor interceptor = context.getBean(MethodSecurityInterceptor)
interceptor.authenticationManager.authenticate(SecurityContextHolder.context.authentication)
then:
BeanCreationException e = thrown()
e.message.contains(EnableGlobalMethodSecurity.class.getName() + " is required")
}
@Configuration
public static class ExtendsNoEnableAnntotationConfig extends GlobalMethodSecurityConfiguration {
@Override
protected AuthenticationManager authenticationManager() {
return new AuthenticationManager() {
Authentication authenticate(Authentication authentication) {
throw new UnsupportedOperationException()
}
}
}
}
def "import subclass of GlobalMethodSecurityConfiguration"() {
when:
context = new AnnotationConfigApplicationContext(ImportSubclassGMSCConfig)
MethodSecurityService service = context.getBean(MethodSecurityService)
then:
service.secured() == null
service.jsr250() == null
when:
service.preAuthorize()
then:
thrown(AccessDeniedException)
}
@Configuration
@Import(PreAuthorizeExtendsGMSCConfig)
public static class ImportSubclassGMSCConfig extends BaseMethodConfig {
}
@Configuration
public static class BaseMethodConfig extends BaseAuthenticationConfig {
@Bean
public MethodSecurityService methodSecurityService() {
return new MethodSecurityServiceImpl()
}
}
}

View File

@ -0,0 +1,28 @@
/*
* 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
*
* http://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.context.annotation.Bean;
/**
* @author Josh Cummings
*/
public class MethodSecurityServiceConfig {
@Bean
MethodSecurityService service() {
return new MethodSecurityServiceImpl();
}
}

View File

@ -0,0 +1,95 @@
/*
* 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
*
* http://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.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.core.Authentication;
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.io.Serializable;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
*
* @author Rob Winch
* @author Josh Cummings
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SecurityTestExecutionListeners
public class NamespaceGlobalMethodSecurityExpressionHandlerTests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Autowired(required = false)
private MethodSecurityService service;
@Test
@WithMockUser
public void methodSecurityWhenUsingCustomPermissionEvaluatorThenPreAuthorizesAccordingly() {
this.spring.register(CustomAccessDecisionManagerConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThatCode(() -> this.service.hasPermission("granted"))
.doesNotThrowAnyException();
assertThatThrownBy(() -> this.service.hasPermission("denied"))
.isInstanceOf(AccessDeniedException.class);
}
@Test
@WithMockUser
public void methodSecurityWhenUsingCustomPermissionEvaluatorThenPostAuthorizesAccordingly() {
this.spring.register(CustomAccessDecisionManagerConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThatCode(() -> this.service.postHasPermission("granted"))
.doesNotThrowAnyException();
assertThatThrownBy(() -> this.service.postHasPermission("denied"))
.isInstanceOf(AccessDeniedException.class);
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
public static class CustomAccessDecisionManagerConfig extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(new PermissionEvaluator() {
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
return "granted".equals(targetDomainObject);
}
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
throw new UnsupportedOperationException();
}
});
return expressionHandler;
}
}
}

View File

@ -0,0 +1,514 @@
/*
* 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
*
* http://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.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.*;
import org.springframework.core.Ordered;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.access.intercept.AfterInvocationManager;
import org.springframework.security.access.intercept.RunAsManager;
import org.springframework.security.access.intercept.RunAsManagerImpl;
import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor;
import org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor;
import org.springframework.security.access.intercept.aspectj.AspectJMethodSecurityInterceptor;
import org.springframework.security.access.method.AbstractMethodSecurityMetadataSource;
import org.springframework.security.access.method.MethodSecurityMetadataSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.test.SpringTestRule;
import org.springframework.security.core.Authentication;
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import static org.assertj.core.api.Assertions.*;
/**
*
* @author Rob Winch
* @author Josh Cummings
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SecurityTestExecutionListeners
public class NamespaceGlobalMethodSecurityTests {
@Rule
public final SpringTestRule spring = new SpringTestRule();
@Autowired(required = false)
private MethodSecurityService service;
// --- access-decision-manager-ref ---
@Test
@WithMockUser
public void methodSecurityWhenCustomAccessDecisionManagerThenAuthorizes() {
this.spring.register(CustomAccessDecisionManagerConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThatThrownBy(() -> this.service.preAuthorize())
.isInstanceOf(AccessDeniedException.class);
assertThatThrownBy(() -> this.service.secured())
.isInstanceOf(AccessDeniedException.class);
}
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public static class CustomAccessDecisionManagerConfig extends GlobalMethodSecurityConfiguration {
@Override
protected AccessDecisionManager accessDecisionManager() {
return new DenyAllAccessDecisionManager();
}
public static class DenyAllAccessDecisionManager implements AccessDecisionManager {
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) {
throw new AccessDeniedException("Always Denied");
}
public boolean supports(ConfigAttribute attribute) {
return true;
}
public boolean supports(Class<?> clazz) {
return true;
}
}
}
// --- after-invocation-provider
@Test
@WithMockUser
public void methodSecurityWhenCustomAfterInvocationManagerThenAuthorizes() {
this.spring.register(CustomAfterInvocationManagerConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThatThrownBy(() -> this.service.preAuthorizePermitAll())
.isInstanceOf(AccessDeniedException.class);
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
public static class CustomAfterInvocationManagerConfig
extends GlobalMethodSecurityConfiguration {
@Override
protected AfterInvocationManager afterInvocationManager() {
return new AfterInvocationManagerStub();
}
public static class AfterInvocationManagerStub implements AfterInvocationManager {
public Object decide(Authentication authentication,
Object object,
Collection<ConfigAttribute> attributes,
Object returnedObject) throws AccessDeniedException {
throw new AccessDeniedException("custom AfterInvocationManager");
}
public boolean supports(ConfigAttribute attribute) {
return true;
}
public boolean supports(Class<?> clazz) {
return true;
}
}
}
// --- authentication-manager-ref ---
@Test
@WithMockUser
public void methodSecurityWhenCustomAuthenticationManagerThenAuthorizes() {
this.spring.register(CustomAuthenticationConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThatThrownBy(() -> this.service.preAuthorize())
.isInstanceOf(UnsupportedOperationException.class);
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
public static class CustomAuthenticationConfig extends GlobalMethodSecurityConfiguration {
@Override
public MethodInterceptor methodSecurityInterceptor() throws Exception {
MethodInterceptor interceptor = super.methodSecurityInterceptor();
((MethodSecurityInterceptor) interceptor).setAlwaysReauthenticate(true);
return interceptor;
}
@Override
protected AuthenticationManager authenticationManager() {
return (authentication) -> {
throw new UnsupportedOperationException();
};
}
}
// --- jsr250-annotations ---
@Test
@WithMockUser
public void methodSecurityWhenJsr250EnabledThenAuthorizes() {
this.spring.register(Jsr250Config.class, MethodSecurityServiceConfig.class).autowire();
assertThatCode(() -> this.service.preAuthorize())
.doesNotThrowAnyException();
assertThatCode(() -> this.service.secured())
.doesNotThrowAnyException();
assertThatThrownBy(() -> this.service.jsr250())
.isInstanceOf(AccessDeniedException.class);
assertThatCode(() -> this.service.jsr250PermitAll())
.doesNotThrowAnyException();
}
@EnableGlobalMethodSecurity(jsr250Enabled = true)
@Configuration
public static class Jsr250Config {
}
// --- metadata-source-ref ---
@Test
@WithMockUser
public void methodSecurityWhenCustomMethodSecurityMetadataSourceThenAuthorizes() {
this.spring.register(CustomMethodSecurityMetadataSourceConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThatThrownBy(() -> this.service.preAuthorize())
.isInstanceOf(AccessDeniedException.class);
assertThatThrownBy(() -> this.service.secured())
.isInstanceOf(AccessDeniedException.class);
assertThatThrownBy(() -> this.service.jsr250())
.isInstanceOf(AccessDeniedException.class);
}
@EnableGlobalMethodSecurity
public static class CustomMethodSecurityMetadataSourceConfig extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return new AbstractMethodSecurityMetadataSource() {
public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
// require ROLE_NOBODY for any method on MethodSecurityService interface
return MethodSecurityService.class.isAssignableFrom(targetClass) ?
Arrays.asList(new SecurityConfig("ROLE_NOBODY")) :
Collections.emptyList();
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
};
}
}
// --- mode ---
@Test
@WithMockUser
public void contextRefreshWhenUsingAspectJThenAutowire() throws Exception {
this.spring.register(AspectJModeConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThat(this.spring.getContext().getBean(Class.forName("org.springframework.security.access.intercept.aspectj.aspect.AnnotationSecurityAspect"))).isNotNull();
assertThat(this.spring.getContext().getBean(AspectJMethodSecurityInterceptor.class)).isNotNull();
//TODO diagnose why aspectj isn't weaving method security advice around MethodSecurityServiceImpl
}
@EnableGlobalMethodSecurity(mode = AdviceMode.ASPECTJ, securedEnabled = true)
public static class AspectJModeConfig {
}
@Test
public void contextRefreshWhenUsingAspectJAndCustomGlobalMethodSecurityConfigurationThenAutowire()
throws Exception {
this.spring.register(AspectJModeExtendsGMSCConfig.class).autowire();
assertThat(this.spring.getContext().getBean(Class.forName("org.springframework.security.access.intercept.aspectj.aspect.AnnotationSecurityAspect"))).isNotNull();
assertThat(this.spring.getContext().getBean(AspectJMethodSecurityInterceptor.class)).isNotNull();
}
@EnableGlobalMethodSecurity(mode = AdviceMode.ASPECTJ)
public static class AspectJModeExtendsGMSCConfig extends GlobalMethodSecurityConfiguration {
}
// --- order ---
private static class AdvisorOrderConfig
implements ImportBeanDefinitionRegistrar {
private static class ExceptingInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) {
throw new UnsupportedOperationException("Deny All");
}
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinitionBuilder advice = BeanDefinitionBuilder
.rootBeanDefinition(ExceptingInterceptor.class);
registry.registerBeanDefinition("exceptingInterceptor",
advice.getBeanDefinition());
BeanDefinitionBuilder advisor = BeanDefinitionBuilder
.rootBeanDefinition(MethodSecurityMetadataSourceAdvisor.class);
advisor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
advisor.addConstructorArgValue("exceptingInterceptor");
advisor.addConstructorArgReference("methodSecurityMetadataSource");
advisor.addConstructorArgValue("methodSecurityMetadataSource");
advisor.addPropertyValue("order", 0);
registry.registerBeanDefinition("exceptingAdvisor",
advisor.getBeanDefinition());
}
}
@Test
@WithMockUser
public void methodSecurityWhenOrderSpecifiedThenConfigured() {
this.spring.register(CustomOrderConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThat(this.spring.getContext()
.getBean("metaDataSourceAdvisor", MethodSecurityMetadataSourceAdvisor.class)
.getOrder())
.isEqualTo(-135);
assertThatThrownBy(() -> this.service.jsr250())
.isInstanceOf(AccessDeniedException.class);
}
@EnableGlobalMethodSecurity(order = -135, jsr250Enabled = true)
@Import(AdvisorOrderConfig.class)
public static class CustomOrderConfig {
}
@Test
@WithMockUser
public void methodSecurityWhenOrderUnspecifiedThenConfiguredToLowestPrecedence() {
this.spring.register(DefaultOrderConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThat(this.spring.getContext()
.getBean("metaDataSourceAdvisor", MethodSecurityMetadataSourceAdvisor.class)
.getOrder())
.isEqualTo(Ordered.LOWEST_PRECEDENCE);
assertThatThrownBy(() -> this.service.jsr250())
.isInstanceOf(UnsupportedOperationException.class);
}
@EnableGlobalMethodSecurity(jsr250Enabled = true)
@Import(AdvisorOrderConfig.class)
public static class DefaultOrderConfig {
}
@Test
@WithMockUser
public void methodSecurityWhenOrderUnspecifiedAndCustomGlobalMethodSecurityConfigurationThenConfiguredToLowestPrecedence() {
this.spring.register(DefaultOrderExtendsMethodSecurityConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThat(this.spring.getContext()
.getBean("metaDataSourceAdvisor", MethodSecurityMetadataSourceAdvisor.class)
.getOrder())
.isEqualTo(Ordered.LOWEST_PRECEDENCE);
assertThatThrownBy(() -> this.service.jsr250())
.isInstanceOf(UnsupportedOperationException.class);
}
@EnableGlobalMethodSecurity(jsr250Enabled = true)
@Import(AdvisorOrderConfig.class)
public static class DefaultOrderExtendsMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
}
// --- pre-post-annotations ---
@Test
@WithMockUser
public void methodSecurityWhenPrePostEnabledThenPreAuthorizes() {
this.spring.register(PreAuthorizeConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThatCode(() -> this.service.secured())
.doesNotThrowAnyException();
assertThatCode(() -> this.service.jsr250())
.doesNotThrowAnyException();
assertThatThrownBy(() -> this.service.preAuthorize())
.isInstanceOf(AccessDeniedException.class);
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
public static class PreAuthorizeConfig {
}
@Test
@WithMockUser
public void methodSecurityWhenPrePostEnabledAndCustomGlobalMethodSecurityConfigurationThenPreAuthorizes() {
this.spring.register(PreAuthorizeExtendsGMSCConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThatCode(() -> this.service.secured())
.doesNotThrowAnyException();
assertThatCode(() -> this.service.jsr250())
.doesNotThrowAnyException();
assertThatThrownBy(() -> this.service.preAuthorize())
.isInstanceOf(AccessDeniedException.class);
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
public static class PreAuthorizeExtendsGMSCConfig extends GlobalMethodSecurityConfiguration {
}
// --- proxy-target-class ---
@Test
@WithMockUser
public void methodSecurityWhenProxyTargetClassThenDoesNotWireToInterface() {
this.spring.register(ProxyTargetClassConfig.class, MethodSecurityServiceConfig.class).autowire();
// make sure service was actually proxied
assertThat(this.service.getClass().getInterfaces())
.doesNotContain(MethodSecurityService.class);
assertThatThrownBy(() -> this.service.preAuthorize())
.isInstanceOf(AccessDeniedException.class);
}
@EnableGlobalMethodSecurity(proxyTargetClass = true, prePostEnabled = true)
public static class ProxyTargetClassConfig {
}
@Test
@WithMockUser
public void methodSecurityWhenDefaultProxyThenWiresToInterface() {
this.spring.register(DefaultProxyConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThat(this.service.getClass().getInterfaces())
.contains(MethodSecurityService.class);
assertThatThrownBy(() -> this.service.preAuthorize())
.isInstanceOf(AccessDeniedException.class);
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
public static class DefaultProxyConfig {
}
// --- run-as-manager-ref ---
@Test
@WithMockUser
public void methodSecurityWhenCustomRunAsManagerThenRunAsWrapsAuthentication() {
this.spring.register(CustomRunAsManagerConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThat(service.runAs().getAuthorities())
.anyMatch(authority -> "ROLE_RUN_AS_SUPER".equals(authority.getAuthority()));
}
@EnableGlobalMethodSecurity(securedEnabled = true)
public static class CustomRunAsManagerConfig extends GlobalMethodSecurityConfiguration {
@Override
protected RunAsManager runAsManager() {
RunAsManagerImpl runAsManager = new RunAsManagerImpl();
runAsManager.setKey("some key");
return runAsManager;
}
}
// --- secured-annotation ---
@Test
@WithMockUser
public void methodSecurityWhenSecuredEnabledThenSecures() {
this.spring.register(SecuredConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThatThrownBy(() -> this.service.secured())
.isInstanceOf(AccessDeniedException.class);
assertThatCode(() -> this.service.securedUser())
.doesNotThrowAnyException();
assertThatCode(() -> this.service.preAuthorize())
.doesNotThrowAnyException();
assertThatCode(() -> this.service.jsr250())
.doesNotThrowAnyException();
}
@EnableGlobalMethodSecurity(securedEnabled = true)
public static class SecuredConfig {
}
// --- unsorted ---
@Test
@WithMockUser
public void methodSecurityWhenMissingEnableAnnotationThenShowsHelpfulError() {
assertThatThrownBy(() ->
this.spring.register(ExtendsNoEnableAnntotationConfig.class).autowire())
.hasStackTraceContaining(EnableGlobalMethodSecurity.class.getName() + " is required");
}
@Configuration
public static class ExtendsNoEnableAnntotationConfig
extends GlobalMethodSecurityConfiguration {
}
@Test
@WithMockUser
public void methodSecurityWhenImportingGlobalMethodSecurityConfigurationSubclassThenAuthorizes() {
this.spring.register(ImportSubclassGMSCConfig.class, MethodSecurityServiceConfig.class).autowire();
assertThatCode(() -> this.service.secured())
.doesNotThrowAnyException();
assertThatCode(() -> this.service.jsr250())
.doesNotThrowAnyException();
assertThatThrownBy(() -> this.service.preAuthorize())
.isInstanceOf(AccessDeniedException.class);
}
@Configuration
@Import(PreAuthorizeExtendsGMSCConfig.class)
public static class ImportSubclassGMSCConfig {
}
}