Support custom MethodSecurityExpressionHandler
Closes gh-15715
This commit is contained in:
parent
e29058c7e4
commit
ef8b0addbb
|
@ -34,6 +34,7 @@ import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Fallback;
|
||||||
import org.springframework.context.annotation.Role;
|
import org.springframework.context.annotation.Role;
|
||||||
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
||||||
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||||
|
@ -114,6 +115,7 @@ final class ReactiveAuthorizationManagerMethodSecurityConfiguration implements A
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||||
|
@Fallback
|
||||||
static DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler(
|
static DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler(
|
||||||
@Autowired(required = false) GrantedAuthorityDefaults grantedAuthorityDefaults) {
|
@Autowired(required = false) GrantedAuthorityDefaults grantedAuthorityDefaults) {
|
||||||
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
|
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
|
||||||
|
|
|
@ -16,14 +16,22 @@
|
||||||
|
|
||||||
package org.springframework.security.config.annotation.method.configuration;
|
package org.springframework.security.config.annotation.method.configuration;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import reactor.test.StepVerifier;
|
import reactor.test.StepVerifier;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.config.BeanDefinition;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Role;
|
||||||
|
import org.springframework.security.access.PermissionEvaluator;
|
||||||
|
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.authorization.AuthorizationDeniedException;
|
||||||
import org.springframework.security.config.test.SpringTestContext;
|
import org.springframework.security.config.test.SpringTestContext;
|
||||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
|
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
|
||||||
import org.springframework.security.test.context.support.WithMockUser;
|
import org.springframework.security.test.context.support.WithMockUser;
|
||||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||||
|
@ -201,6 +209,17 @@ public class PrePostReactiveMethodSecurityConfigurationTests {
|
||||||
StepVerifier.create(service.preAuthorizeWithMaskAnnotationUsingBean()).expectNext("ok").verifyComplete();
|
StepVerifier.create(service.preAuthorizeWithMaskAnnotationUsingBean()).expectNext("ok").verifyComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser(roles = "ADMIN")
|
||||||
|
public void customMethodSecurityExpressionHandler() {
|
||||||
|
this.spring.register(MethodSecurityServiceEnabledConfig.class, PermissionEvaluatorConfig.class).autowire();
|
||||||
|
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class);
|
||||||
|
StepVerifier.create(service.preAuthorizeHasPermission("grant")).expectNext("ok").verifyComplete();
|
||||||
|
StepVerifier.create(service.preAuthorizeHasPermission("deny"))
|
||||||
|
.expectError(AuthorizationDeniedException.class)
|
||||||
|
.verify();
|
||||||
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableReactiveMethodSecurity
|
@EnableReactiveMethodSecurity
|
||||||
static class MethodSecurityServiceEnabledConfig {
|
static class MethodSecurityServiceEnabledConfig {
|
||||||
|
@ -212,4 +231,29 @@ public class PrePostReactiveMethodSecurityConfigurationTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class PermissionEvaluatorConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||||
|
static DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler() {
|
||||||
|
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
|
||||||
|
handler.setPermissionEvaluator(new PermissionEvaluator() {
|
||||||
|
@Override
|
||||||
|
public boolean hasPermission(Authentication authentication, Object targetDomainObject,
|
||||||
|
Object permission) {
|
||||||
|
return "grant".equals(targetDomainObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType,
|
||||||
|
Object permission) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,6 +101,9 @@ public interface ReactiveMethodSecurityService {
|
||||||
@HandleAuthorizationDenied(handlerClass = MethodAuthorizationDeniedHandler.class)
|
@HandleAuthorizationDenied(handlerClass = MethodAuthorizationDeniedHandler.class)
|
||||||
Mono<String> checkCustomResult(boolean result);
|
Mono<String> checkCustomResult(boolean result);
|
||||||
|
|
||||||
|
@PreAuthorize("hasPermission(#kgName, 'read')")
|
||||||
|
Mono<String> preAuthorizeHasPermission(String kgName);
|
||||||
|
|
||||||
class StarMaskingHandler implements MethodAuthorizationDeniedHandler {
|
class StarMaskingHandler implements MethodAuthorizationDeniedHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -88,4 +88,9 @@ public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurity
|
||||||
return Mono.just("ok");
|
return Mono.just("ok");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<String> preAuthorizeHasPermission(String kgName) {
|
||||||
|
return Mono.just("ok");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,7 +97,7 @@ Spring Security's `@PreAuthorize`, `@PostAuthorize`, `@PreFilter`, and `@PostFil
|
||||||
Also, for role-based authorization, Spring Security adds a default `ROLE_` prefix, which is uses when evaluating expressions like `hasRole`.
|
Also, for role-based authorization, Spring Security adds a default `ROLE_` prefix, which is uses when evaluating expressions like `hasRole`.
|
||||||
You can configure the authorization rules to use a different prefix by exposing a `GrantedAuthorityDefaults` bean, like so:
|
You can configure the authorization rules to use a different prefix by exposing a `GrantedAuthorityDefaults` bean, like so:
|
||||||
|
|
||||||
.Custom MethodSecurityExpressionHandler
|
.Custom GrantedAuthorityDefaults
|
||||||
[tabs]
|
[tabs]
|
||||||
======
|
======
|
||||||
Java::
|
Java::
|
||||||
|
@ -118,6 +118,63 @@ We expose `GrantedAuthorityDefaults` using a `static` method to ensure that Spri
|
||||||
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]).
|
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]).
|
||||||
====
|
====
|
||||||
|
|
||||||
|
[[customizing-expression-handling]]
|
||||||
|
=== Customizing Expression Handling
|
||||||
|
|
||||||
|
Or, third, you can customize how each SpEL expression is handled.
|
||||||
|
To do that, you can expose a custom `MethodSecurityExpressionHandler`, like so:
|
||||||
|
|
||||||
|
.Custom MethodSecurityExpressionHandler
|
||||||
|
[tabs]
|
||||||
|
======
|
||||||
|
Java::
|
||||||
|
+
|
||||||
|
[source,java,role="primary"]
|
||||||
|
----
|
||||||
|
@Bean
|
||||||
|
static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
|
||||||
|
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
|
||||||
|
handler.setRoleHierarchy(roleHierarchy);
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
Kotlin::
|
||||||
|
+
|
||||||
|
[source,kotlin,role="secondary"]
|
||||||
|
----
|
||||||
|
companion object {
|
||||||
|
@Bean
|
||||||
|
fun methodSecurityExpressionHandler(val roleHierarchy: RoleHierarchy) : MethodSecurityExpressionHandler {
|
||||||
|
val handler = DefaultMethodSecurityExpressionHandler()
|
||||||
|
handler.setRoleHierarchy(roleHierarchy)
|
||||||
|
return handler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
Xml::
|
||||||
|
+
|
||||||
|
[source,xml,role="secondary"]
|
||||||
|
----
|
||||||
|
<sec:method-security>
|
||||||
|
<sec:expression-handler ref="myExpressionHandler"/>
|
||||||
|
</sec:method-security>
|
||||||
|
|
||||||
|
<bean id="myExpressionHandler"
|
||||||
|
class="org.springframework.security.messaging.access.expression.DefaultMessageSecurityExpressionHandler">
|
||||||
|
<property name="roleHierarchy" ref="roleHierarchy"/>
|
||||||
|
</bean>
|
||||||
|
----
|
||||||
|
======
|
||||||
|
|
||||||
|
[TIP]
|
||||||
|
====
|
||||||
|
We expose `MethodSecurityExpressionHandler` using a `static` method to ensure that Spring publishes it before it initializes Spring Security's method security `@Configuration` classes
|
||||||
|
====
|
||||||
|
|
||||||
|
You can also subclass xref:servlet/authorization/method-security.adoc#subclass-defaultmethodsecurityexpressionhandler[`DefaultMessageSecurityExpressionHandler`] to add your own custom authorization expressions beyond the defaults.
|
||||||
|
|
||||||
[[jc-reactive-method-security-custom-authorization-manager]]
|
[[jc-reactive-method-security-custom-authorization-manager]]
|
||||||
=== Custom Authorization Managers
|
=== Custom Authorization Managers
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue