Skip to content

Commit 31ecf94

Browse files
candrewsthomasdarimont
authored andcommitted
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 spring-projectsgh-4077
1 parent 1889ab0 commit 31ecf94

File tree

2 files changed

+152
-0
lines changed

2 files changed

+152
-0
lines changed

config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/ExpressionUrlAuthorizationsTests.groovy

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ import org.springframework.beans.factory.config.BeanPostProcessor
2727
import org.springframework.context.ApplicationListener
2828
import org.springframework.context.annotation.Bean
2929
import org.springframework.security.access.AccessDecisionManager;
30+
import org.springframework.security.access.PermissionEvaluator
31+
import org.springframework.security.access.hierarchicalroles.RoleHierarchy
32+
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl
3033
import org.springframework.security.access.event.AuthorizedEvent
3134
import org.springframework.security.access.vote.AffirmativeBased
3235
import org.springframework.security.authentication.RememberMeAuthenticationToken
@@ -37,6 +40,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity
3740
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
3841
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
3942
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurerConfigs.CustomExpressionRootConfig
43+
import org.springframework.security.core.Authentication
4044
import org.springframework.security.core.authority.AuthorityUtils
4145
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
4246

@@ -572,4 +576,119 @@ public class ExpressionUrlAuthorizationConfigurerTests extends BaseSpringSpec {
572576
}
573577

574578
}
579+
580+
def "permissionEvaluator autowired"() {
581+
setup:
582+
loadConfig(PermissionEvaluatorConfig)
583+
when: "invoke hasPermission expression that allows access"
584+
super.setup()
585+
login()
586+
request.servletPath = "/allow/1"
587+
springSecurityFilterChain.doFilter(request, response, chain)
588+
then: "permissionEvaluator with id and type works - allows access"
589+
response.status == HttpServletResponse.SC_OK
590+
when: "invoke hasPermission expression that denies access"
591+
super.setup()
592+
login()
593+
request.servletPath = "/deny/1"
594+
springSecurityFilterChain.doFilter(request, response, chain)
595+
then: "permissionEvaluator with id and type works - denies access"
596+
response.status == HttpServletResponse.SC_FORBIDDEN
597+
when: "invoke hasPermission expression that allows access"
598+
super.setup()
599+
login()
600+
request.servletPath = "/allowObject/1"
601+
springSecurityFilterChain.doFilter(request, response, chain)
602+
then: "permissionEvaluator with object works - allows access"
603+
response.status == HttpServletResponse.SC_OK
604+
when: "invoke hasPermission expression that denies access"
605+
super.setup()
606+
login()
607+
request.servletPath = "/denyObject/1"
608+
springSecurityFilterChain.doFilter(request, response, chain)
609+
then: "permissionEvaluator with object works - denies access"
610+
response.status == HttpServletResponse.SC_FORBIDDEN
611+
}
612+
613+
@EnableWebSecurity
614+
static class PermissionEvaluatorConfig extends WebSecurityConfigurerAdapter {
615+
@Override
616+
protected void configure(HttpSecurity http) throws Exception {
617+
http
618+
.authorizeRequests()
619+
.antMatchers("/allow/**").access("hasPermission('ID', 'TYPE', 'PERMISSION')")
620+
.antMatchers("/allowObject/**").access("hasPermission('TESTOBJ', 'PERMISSION')")
621+
.antMatchers("/deny/**").access("hasPermission('ID', 'TYPE', 'NO PERMISSION')")
622+
.antMatchers("/denyObject/**").access("hasPermission('TESTOBJ', 'NO PERMISSION')")
623+
.anyRequest().permitAll();
624+
}
625+
626+
@Override
627+
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
628+
auth
629+
.inMemoryAuthentication()
630+
.withUser("user").password("password").roles("USER")
631+
}
632+
633+
@Bean
634+
public PermissionEvaluator permissionEvaluator(){
635+
return new PermissionEvaluator(){
636+
@Override
637+
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
638+
return "TESTOBJ".equals(targetDomainObject) && "PERMISSION".equals(permission);
639+
}
640+
@Override
641+
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType,
642+
Object permission) {
643+
return "ID".equals(targetId) && "TYPE".equals(targetType) && "PERMISSION".equals(permission);
644+
}
645+
};
646+
}
647+
648+
}
649+
650+
@EnableWebSecurity
651+
static class RoleHierarchyConfig extends WebSecurityConfigurerAdapter {
652+
@Override
653+
protected void configure(HttpSecurity http) throws Exception {
654+
http
655+
.authorizeRequests()
656+
.antMatchers("/allow/**").access("hasRole('XXX')")
657+
.antMatchers("/deny/**").access("hasRole('NOPE')")
658+
.anyRequest().permitAll();
659+
}
660+
661+
@Override
662+
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
663+
auth
664+
.inMemoryAuthentication()
665+
.withUser("user").password("password").roles("USER")
666+
}
667+
668+
@Bean
669+
public RoleHierarchy roleHierarchy(){
670+
return new RoleHierarchyImpl("USER > XXX");
671+
}
672+
673+
}
674+
675+
def "roleHierarchy autowired"() {
676+
setup:
677+
loadConfig(PermissionEvaluatorConfig)
678+
when: "invoke roleHierarchy expression that allows access"
679+
super.setup()
680+
login()
681+
request.servletPath = "/allow/1"
682+
springSecurityFilterChain.doFilter(request, response, chain)
683+
then: "permissionEvaluator with id and type works - allows access"
684+
response.status == HttpServletResponse.SC_OK
685+
when: "invoke roleHierarchy expression that denies access"
686+
super.setup()
687+
login()
688+
request.servletPath = "/deny/1"
689+
springSecurityFilterChain.doFilter(request, response, chain)
690+
then: "permissionEvaluator with id and type works - denies access"
691+
response.status == HttpServletResponse.SC_FORBIDDEN
692+
}
693+
575694
}

core/src/main/java/org/springframework/security/access/expression/AbstractSecurityExpressionHandler.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,12 @@ public abstract class AbstractSecurityExpressionHandler<T> implements
4040
SecurityExpressionHandler<T>, ApplicationContextAware {
4141
private ExpressionParser expressionParser = new SpelExpressionParser();
4242
private BeanResolver br;
43+
private ApplicationContext context;
4344
private RoleHierarchy roleHierarchy;
4445
private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator();
46+
private boolean roleHierarchySet = false;
47+
private boolean permissionEvaluatorSet = false;
48+
4549

4650
public final ExpressionParser getExpressionParser() {
4751
return expressionParser;
@@ -101,23 +105,52 @@ protected StandardEvaluationContext createEvaluationContextInternal(
101105
protected abstract SecurityExpressionOperations createSecurityExpressionRoot(
102106
Authentication authentication, T invocation);
103107

108+
private boolean roleHerarchyNotSetForValidContext() {
109+
return ! roleHierarchySet && context != null;
110+
}
111+
104112
protected RoleHierarchy getRoleHierarchy() {
113+
if(roleHerarchyNotSetForValidContext()) {
114+
RoleHierarchy contextRoleHierarchy = getSingleBeanOrNull(RoleHierarchy.class);
115+
if(contextRoleHierarchy != null){
116+
roleHierarchy = contextRoleHierarchy;
117+
}
118+
roleHierarchySet = true;
119+
}
105120
return roleHierarchy;
106121
}
107122

108123
public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
124+
roleHierarchySet = true;
109125
this.roleHierarchy = roleHierarchy;
110126
}
111127

112128
protected PermissionEvaluator getPermissionEvaluator() {
129+
if(! permissionEvaluatorSet && context != null) {
130+
PermissionEvaluator contextPermissionEvaluator = getSingleBeanOrNull(PermissionEvaluator.class);
131+
if(contextPermissionEvaluator != null){
132+
permissionEvaluator = contextPermissionEvaluator;
133+
}
134+
permissionEvaluatorSet = true;
135+
}
113136
return permissionEvaluator;
114137
}
115138

116139
public void setPermissionEvaluator(PermissionEvaluator permissionEvaluator) {
140+
permissionEvaluatorSet = true;
117141
this.permissionEvaluator = permissionEvaluator;
118142
}
119143

120144
public void setApplicationContext(ApplicationContext applicationContext) {
121145
br = new BeanFactoryResolver(applicationContext);
146+
this.context = applicationContext;
147+
}
148+
149+
private <T> T getSingleBeanOrNull(Class<T> type) {
150+
String[] beanNamesForType = context.getBeanNamesForType(type);
151+
if (beanNamesForType == null || beanNamesForType.length != 1) {
152+
return null;
153+
}
154+
return context.getBean(beanNamesForType[0], type);
122155
}
123156
}

0 commit comments

Comments
 (0)