Skip to content

Commit bf075a2

Browse files
candrewsrwinch
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 gh-4077
1 parent d0ede98 commit bf075a2

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
@@ -25,6 +25,9 @@ import org.springframework.beans.factory.config.BeanPostProcessor
2525
import org.springframework.context.ApplicationListener
2626
import org.springframework.context.annotation.Bean
2727
import org.springframework.security.access.AccessDecisionManager;
28+
import org.springframework.security.access.PermissionEvaluator
29+
import org.springframework.security.access.hierarchicalroles.RoleHierarchy
30+
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl
2831
import org.springframework.security.access.event.AuthorizedEvent
2932
import org.springframework.security.access.vote.AffirmativeBased
3033
import org.springframework.security.authentication.RememberMeAuthenticationToken
@@ -35,6 +38,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity
3538
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
3639
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
3740
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurerConfigs.CustomExpressionRootConfig
41+
import org.springframework.security.core.Authentication
3842
import org.springframework.security.core.authority.AuthorityUtils
3943
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor
4044

@@ -574,4 +578,119 @@ public class ExpressionUrlAuthorizationConfigurerTests extends BaseSpringSpec {
574578
}
575579

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

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)