2
0
Эх сурвалжийг харах

Configure permissionEvaluator and roleHierarchy by default

Implementations of AbstractSecurityExpressionHandler (such as the very commonly used DefaultWebSecurityExpressionHandler) get PermissionEvaluator and RoleHierarchy from the application context (if the application context is provided, and exactly one of such a bean exists in it). This approach matches that used in GlobalMethodSecurityConfiguration, making everything in Spring Security work the same way (including WebSecurity).

Issue gh-4077
Craig Andrews 8 жил өмнө
parent
commit
bf075a2cae

+ 119 - 0
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationsTests.groovy

@@ -25,6 +25,9 @@ import org.springframework.beans.factory.config.BeanPostProcessor
 import org.springframework.context.ApplicationListener
 import org.springframework.context.annotation.Bean
 import org.springframework.security.access.AccessDecisionManager;
+import org.springframework.security.access.PermissionEvaluator
+import org.springframework.security.access.hierarchicalroles.RoleHierarchy
+import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl
 import org.springframework.security.access.event.AuthorizedEvent
 import org.springframework.security.access.vote.AffirmativeBased
 import org.springframework.security.authentication.RememberMeAuthenticationToken
@@ -35,6 +38,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
 import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurerConfigs.CustomExpressionRootConfig
+import org.springframework.security.core.Authentication
 import org.springframework.security.core.authority.AuthorityUtils
 import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
 
@@ -574,4 +578,119 @@ public class ExpressionUrlAuthorizationConfigurerTests extends BaseSpringSpec {
 		}
 
 	}
+
+	def "permissionEvaluator autowired"() {
+		setup:
+			loadConfig(PermissionEvaluatorConfig)
+		when: "invoke hasPermission expression that allows access"
+			super.setup()
+			login()
+			request.servletPath = "/allow/1"
+			springSecurityFilterChain.doFilter(request, response, chain)
+		then: "permissionEvaluator with id and type works - allows access"
+			response.status == HttpServletResponse.SC_OK
+		when: "invoke hasPermission expression that denies access"
+			super.setup()
+			login()
+			request.servletPath = "/deny/1"
+			springSecurityFilterChain.doFilter(request, response, chain)
+		then: "permissionEvaluator with id and type works - denies access"
+			response.status == HttpServletResponse.SC_FORBIDDEN
+		when: "invoke hasPermission expression that allows access"
+			super.setup()
+			login()
+			request.servletPath = "/allowObject/1"
+			springSecurityFilterChain.doFilter(request, response, chain)
+		then: "permissionEvaluator with object works - allows access"
+			response.status == HttpServletResponse.SC_OK
+		when: "invoke hasPermission expression that denies access"
+			super.setup()
+			login()
+			request.servletPath = "/denyObject/1"
+			springSecurityFilterChain.doFilter(request, response, chain)
+		then: "permissionEvaluator with object works - denies access"
+			response.status == HttpServletResponse.SC_FORBIDDEN
+	}
+
+	@EnableWebSecurity
+	static class PermissionEvaluatorConfig extends WebSecurityConfigurerAdapter {
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			http
+				.authorizeRequests()
+					.antMatchers("/allow/**").access("hasPermission('ID', 'TYPE', 'PERMISSION')")
+					.antMatchers("/allowObject/**").access("hasPermission('TESTOBJ', 'PERMISSION')")
+					.antMatchers("/deny/**").access("hasPermission('ID', 'TYPE', 'NO PERMISSION')")
+					.antMatchers("/denyObject/**").access("hasPermission('TESTOBJ', 'NO PERMISSION')")
+					.anyRequest().permitAll();
+		}
+
+		@Override
+		protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+			auth
+				.inMemoryAuthentication()
+					.withUser("user").password("password").roles("USER")
+		}
+
+		@Bean
+		public PermissionEvaluator permissionEvaluator(){
+			return new PermissionEvaluator(){
+				@Override
+				public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
+					return "TESTOBJ".equals(targetDomainObject) && "PERMISSION".equals(permission);
+				}
+				@Override
+				public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType,
+						Object permission) {
+					return "ID".equals(targetId) && "TYPE".equals(targetType) && "PERMISSION".equals(permission);
+				}
+			};
+		}
+
+	}
+
+	@EnableWebSecurity
+	static class RoleHierarchyConfig extends WebSecurityConfigurerAdapter {
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			http
+				.authorizeRequests()
+					.antMatchers("/allow/**").access("hasRole('XXX')")
+					.antMatchers("/deny/**").access("hasRole('NOPE')")
+					.anyRequest().permitAll();
+		}
+
+		@Override
+		protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+			auth
+				.inMemoryAuthentication()
+					.withUser("user").password("password").roles("USER")
+		}
+
+		@Bean
+		public RoleHierarchy roleHierarchy(){
+			return new RoleHierarchyImpl("USER > XXX");
+		}
+
+	}
+
+	def "roleHierarchy autowired"() {
+		setup:
+			loadConfig(PermissionEvaluatorConfig)
+		when: "invoke roleHierarchy expression that allows access"
+			super.setup()
+			login()
+			request.servletPath = "/allow/1"
+			springSecurityFilterChain.doFilter(request, response, chain)
+		then: "permissionEvaluator with id and type works - allows access"
+			response.status == HttpServletResponse.SC_OK
+		when: "invoke roleHierarchy expression that denies access"
+			super.setup()
+			login()
+			request.servletPath = "/deny/1"
+			springSecurityFilterChain.doFilter(request, response, chain)
+		then: "permissionEvaluator with id and type works - denies access"
+			response.status == HttpServletResponse.SC_FORBIDDEN
+	}
+
 }

+ 33 - 0
core/src/main/java/org/springframework/security/access/expression/AbstractSecurityExpressionHandler.java

@@ -40,8 +40,12 @@ public abstract class AbstractSecurityExpressionHandler<T> implements
 		SecurityExpressionHandler<T>, ApplicationContextAware {
 	private ExpressionParser expressionParser = new SpelExpressionParser();
 	private BeanResolver br;
+	private ApplicationContext context;
 	private RoleHierarchy roleHierarchy;
 	private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator();
+	private boolean roleHierarchySet = false;
+	private boolean permissionEvaluatorSet = false;
+
 
 	public final ExpressionParser getExpressionParser() {
 		return expressionParser;
@@ -101,23 +105,52 @@ public abstract class AbstractSecurityExpressionHandler<T> implements
 	protected abstract SecurityExpressionOperations createSecurityExpressionRoot(
 			Authentication authentication, T invocation);
 
+	private boolean roleHerarchyNotSetForValidContext() {
+		return ! roleHierarchySet && context != null;
+	}
+
 	protected RoleHierarchy getRoleHierarchy() {
+		if(roleHerarchyNotSetForValidContext()) {
+			RoleHierarchy contextRoleHierarchy = getSingleBeanOrNull(RoleHierarchy.class);
+			if(contextRoleHierarchy != null){
+				roleHierarchy = contextRoleHierarchy;
+			}
+			roleHierarchySet = true;
+		}
 		return roleHierarchy;
 	}
 
 	public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
+		roleHierarchySet = true;
 		this.roleHierarchy = roleHierarchy;
 	}
 
 	protected PermissionEvaluator getPermissionEvaluator() {
+		if(! permissionEvaluatorSet && context != null) {
+			PermissionEvaluator contextPermissionEvaluator = getSingleBeanOrNull(PermissionEvaluator.class);
+			if(contextPermissionEvaluator != null){
+				permissionEvaluator = contextPermissionEvaluator;
+			}
+			permissionEvaluatorSet = true;
+		}
 		return permissionEvaluator;
 	}
 
 	public void setPermissionEvaluator(PermissionEvaluator permissionEvaluator) {
+		permissionEvaluatorSet = true;
 		this.permissionEvaluator = permissionEvaluator;
 	}
 
 	public void setApplicationContext(ApplicationContext applicationContext) {
 		br = new BeanFactoryResolver(applicationContext);
+		this.context = applicationContext;
+	}
+
+	private <T> T getSingleBeanOrNull(Class<T> type) {
+		String[] beanNamesForType = context.getBeanNamesForType(type);
+		if (beanNamesForType == null || beanNamesForType.length != 1) {
+			return null;
+		}
+		return context.getBean(beanNamesForType[0], type);
 	}
 }