瀏覽代碼

Support custom MethodSecurityExpressionHandler

Closes gh-15715
DingHao 11 月之前
父節點
當前提交
ef8b0addbb

+ 2 - 0
config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationManagerMethodSecurityConfiguration.java

@@ -34,6 +34,7 @@ import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Fallback;
 import org.springframework.context.annotation.Role;
 import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
 import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
@@ -114,6 +115,7 @@ final class ReactiveAuthorizationManagerMethodSecurityConfiguration implements A
 
 	@Bean
 	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+	@Fallback
 	static DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler(
 			@Autowired(required = false) GrantedAuthorityDefaults grantedAuthorityDefaults) {
 		DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();

+ 44 - 0
config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostReactiveMethodSecurityConfigurationTests.java

@@ -16,14 +16,22 @@
 
 package org.springframework.security.config.annotation.method.configuration;
 
+import java.io.Serializable;
+
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import reactor.test.StepVerifier;
 
+import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.context.annotation.Bean;
 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.SpringTestContextExtension;
+import org.springframework.security.core.Authentication;
 import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
 import org.springframework.security.test.context.support.WithMockUser;
 import org.springframework.test.context.junit.jupiter.SpringExtension;
@@ -201,6 +209,17 @@ public class PrePostReactiveMethodSecurityConfigurationTests {
 		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
 	@EnableReactiveMethodSecurity
 	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;
+		}
+
+	}
+
 }

+ 3 - 0
config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java

@@ -101,6 +101,9 @@ public interface ReactiveMethodSecurityService {
 	@HandleAuthorizationDenied(handlerClass = MethodAuthorizationDeniedHandler.class)
 	Mono<String> checkCustomResult(boolean result);
 
+	@PreAuthorize("hasPermission(#kgName, 'read')")
+	Mono<String> preAuthorizeHasPermission(String kgName);
+
 	class StarMaskingHandler implements MethodAuthorizationDeniedHandler {
 
 		@Override

+ 5 - 0
config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityServiceImpl.java

@@ -88,4 +88,9 @@ public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurity
 		return Mono.just("ok");
 	}
 
+	@Override
+	public Mono<String> preAuthorizeHasPermission(String kgName) {
+		return Mono.just("ok");
+	}
+
 }

+ 58 - 1
docs/modules/ROOT/pages/reactive/authorization/method.adoc

@@ -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`.
 You can configure the authorization rules to use a different prefix by exposing a `GrantedAuthorityDefaults` bean, like so:
 
-.Custom MethodSecurityExpressionHandler
+.Custom GrantedAuthorityDefaults
 [tabs]
 ======
 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]).
 ====
 
+[[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]]
 === Custom Authorization Managers