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
This commit is contained in:
Craig Andrews 2016-11-01 16:35:01 -04:00 committed by Rob Winch
parent 3f58822d4d
commit 3bf6bf10de
2 changed files with 152 additions and 0 deletions

View File

@ -27,6 +27,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
@ -37,6 +40,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
@ -572,4 +576,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
}
}

View File

@ -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);
}
}