Skip to content

Commit bcba086

Browse files
committed
Support custom MethodSecurityExpressionHandler
Closes gh-15715
1 parent add5c56 commit bcba086

File tree

5 files changed

+112
-1
lines changed

5 files changed

+112
-1
lines changed

config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationManagerMethodSecurityConfiguration.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.springframework.context.ApplicationContext;
3535
import org.springframework.context.annotation.Bean;
3636
import org.springframework.context.annotation.Configuration;
37+
import org.springframework.context.annotation.Fallback;
3738
import org.springframework.context.annotation.Role;
3839
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
3940
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
@@ -114,6 +115,7 @@ static MethodInterceptor postAuthorizeAuthorizationMethodInterceptor(
114115

115116
@Bean
116117
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
118+
@Fallback
117119
static DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler(
118120
@Autowired(required = false) GrantedAuthorityDefaults grantedAuthorityDefaults) {
119121
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();

config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostReactiveMethodSecurityConfigurationTests.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,22 @@
1616

1717
package org.springframework.security.config.annotation.method.configuration;
1818

19+
import java.io.Serializable;
20+
1921
import org.junit.jupiter.api.Test;
2022
import org.junit.jupiter.api.extension.ExtendWith;
2123
import reactor.test.StepVerifier;
2224

25+
import org.springframework.beans.factory.config.BeanDefinition;
2326
import org.springframework.context.annotation.Bean;
2427
import org.springframework.context.annotation.Configuration;
28+
import org.springframework.context.annotation.Role;
29+
import org.springframework.security.access.PermissionEvaluator;
30+
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
31+
import org.springframework.security.authorization.AuthorizationDeniedException;
2532
import org.springframework.security.config.test.SpringTestContext;
2633
import org.springframework.security.config.test.SpringTestContextExtension;
34+
import org.springframework.security.core.Authentication;
2735
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
2836
import org.springframework.security.test.context.support.WithMockUser;
2937
import org.springframework.test.context.junit.jupiter.SpringExtension;
@@ -201,6 +209,17 @@ void preAuthorizeWhenAllowedAndHandlerWithCustomAnnotationUsingBeanThenInvokeMet
201209
StepVerifier.create(service.preAuthorizeWithMaskAnnotationUsingBean()).expectNext("ok").verifyComplete();
202210
}
203211

212+
@Test
213+
@WithMockUser(roles = "ADMIN")
214+
public void customMethodSecurityExpressionHandler() {
215+
this.spring.register(MethodSecurityServiceEnabledConfig.class, PermissionEvaluatorConfig.class).autowire();
216+
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
217+
StepVerifier.create(service.preAuthorizeHasPermission("grant")).expectNext("ok").verifyComplete();
218+
StepVerifier.create(service.preAuthorizeHasPermission("deny"))
219+
.expectError(AuthorizationDeniedException.class)
220+
.verify();
221+
}
222+
204223
@Configuration
205224
@EnableReactiveMethodSecurity
206225
static class MethodSecurityServiceEnabledConfig {
@@ -212,4 +231,29 @@ ReactiveMethodSecurityService methodSecurityService() {
212231

213232
}
214233

234+
@Configuration
235+
static class PermissionEvaluatorConfig {
236+
237+
@Bean
238+
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
239+
static DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler() {
240+
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
241+
handler.setPermissionEvaluator(new PermissionEvaluator() {
242+
@Override
243+
public boolean hasPermission(Authentication authentication, Object targetDomainObject,
244+
Object permission) {
245+
return "grant".equals(targetDomainObject);
246+
}
247+
248+
@Override
249+
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType,
250+
Object permission) {
251+
throw new UnsupportedOperationException();
252+
}
253+
});
254+
return handler;
255+
}
256+
257+
}
258+
215259
}

config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ public interface ReactiveMethodSecurityService {
101101
@HandleAuthorizationDenied(handlerClass = MethodAuthorizationDeniedHandler.class)
102102
Mono<String> checkCustomResult(boolean result);
103103

104+
@PreAuthorize("hasPermission(#kgName, 'read')")
105+
Mono<String> preAuthorizeHasPermission(String kgName);
106+
104107
class StarMaskingHandler implements MethodAuthorizationDeniedHandler {
105108

106109
@Override

config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityServiceImpl.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,9 @@ public Mono<String> checkCustomResult(boolean result) {
8888
return Mono.just("ok");
8989
}
9090

91+
@Override
92+
public Mono<String> preAuthorizeHasPermission(String kgName) {
93+
return Mono.just("ok");
94+
}
95+
9196
}

docs/modules/ROOT/pages/reactive/authorization/method.adoc

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ Spring Security's `@PreAuthorize`, `@PostAuthorize`, `@PreFilter`, and `@PostFil
9797
Also, for role-based authorization, Spring Security adds a default `ROLE_` prefix, which is uses when evaluating expressions like `hasRole`.
9898
You can configure the authorization rules to use a different prefix by exposing a `GrantedAuthorityDefaults` bean, like so:
9999

100-
.Custom MethodSecurityExpressionHandler
100+
.Custom GrantedAuthorityDefaults
101101
[tabs]
102102
======
103103
Java::
@@ -118,6 +118,63 @@ We expose `GrantedAuthorityDefaults` using a `static` method to ensure that Spri
118118
Since the `GrantedAuthorityDefaults` bean is part of internal workings of Spring Security, we should also expose it as an infrastructural bean effectively avoiding some warnings related to bean post-processing (see https://github.com/spring-projects/spring-security/issues/14751[gh-14751]).
119119
====
120120

121+
[[customizing-expression-handling]]
122+
=== Customizing Expression Handling
123+
124+
Or, third, you can customize how each SpEL expression is handled.
125+
To do that, you can expose a custom `MethodSecurityExpressionHandler`, like so:
126+
127+
.Custom MethodSecurityExpressionHandler
128+
[tabs]
129+
======
130+
Java::
131+
+
132+
[source,java,role="primary"]
133+
----
134+
@Bean
135+
static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
136+
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
137+
handler.setRoleHierarchy(roleHierarchy);
138+
return handler;
139+
}
140+
----
141+
142+
Kotlin::
143+
+
144+
[source,kotlin,role="secondary"]
145+
----
146+
companion object {
147+
@Bean
148+
fun methodSecurityExpressionHandler(val roleHierarchy: RoleHierarchy) : MethodSecurityExpressionHandler {
149+
val handler = DefaultMethodSecurityExpressionHandler()
150+
handler.setRoleHierarchy(roleHierarchy)
151+
return handler
152+
}
153+
}
154+
----
155+
156+
Xml::
157+
+
158+
[source,xml,role="secondary"]
159+
----
160+
<sec:method-security>
161+
<sec:expression-handler ref="myExpressionHandler"/>
162+
</sec:method-security>
163+
164+
<bean id="myExpressionHandler"
165+
class="org.springframework.security.messaging.access.expression.DefaultMessageSecurityExpressionHandler">
166+
<property name="roleHierarchy" ref="roleHierarchy"/>
167+
</bean>
168+
----
169+
======
170+
171+
[TIP]
172+
====
173+
We expose `MethodSecurityExpressionHandler` using a `static` method to ensure that Spring publishes it before it initializes Spring Security's method security `@Configuration` classes
174+
====
175+
176+
You can also subclass xref:servlet/authorization/method-security.adoc#subclass-defaultmethodsecurityexpressionhandler[`DefaultMessageSecurityExpressionHandler`] to add your own custom authorization expressions beyond the defaults.
177+
121178
[[jc-reactive-method-security-custom-authorization-manager]]
122179
=== Custom Authorization Managers
123180

0 commit comments

Comments
 (0)