浏览代码

Add Publishing Predicate

Closes gh-17503
Josh Cummings 1 月之前
父节点
当前提交
c312d18191

+ 21 - 1
core/src/main/java/org/springframework/security/authorization/SpringAuthorizationEventPublisher.java

@@ -16,6 +16,7 @@
 
 package org.springframework.security.authorization;
 
+import java.util.function.Predicate;
 import java.util.function.Supplier;
 
 import org.springframework.context.ApplicationEventPublisher;
@@ -40,6 +41,8 @@ public final class SpringAuthorizationEventPublisher implements AuthorizationEve
 
 	private final ApplicationEventPublisher eventPublisher;
 
+	private Predicate<AuthorizationResult> shouldPublishResult = (result) -> !result.isGranted();
+
 	/**
 	 * Construct this publisher using Spring's {@link ApplicationEventPublisher}
 	 * @param eventPublisher
@@ -55,11 +58,28 @@ public final class SpringAuthorizationEventPublisher implements AuthorizationEve
 	@Override
 	public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
 			AuthorizationResult result) {
-		if (result == null || result.isGranted()) {
+		if (result == null) {
+			return;
+		}
+		if (!this.shouldPublishResult.test(result)) {
 			return;
 		}
 		AuthorizationDeniedEvent<T> failure = new AuthorizationDeniedEvent<>(authentication, object, result);
 		this.eventPublisher.publishEvent(failure);
 	}
 
+	/**
+	 * Use this predicate to test whether to publish an event.
+	 *
+	 * <p>
+	 * Since you cannot publish a {@code null} event, checking for null is already
+	 * performed before this test is run
+	 * @param shouldPublishResult the test to perform on non-{@code null} events
+	 * @since 7.0
+	 */
+	public void setShouldPublishResult(Predicate<AuthorizationResult> shouldPublishResult) {
+		Assert.notNull(shouldPublishResult, "shouldPublishResult cannot be null");
+		this.shouldPublishResult = shouldPublishResult;
+	}
+
 }

+ 16 - 0
core/src/test/java/org/springframework/security/authorization/SpringAuthorizationEventPublisherTests.java

@@ -16,6 +16,7 @@
 
 package org.springframework.security.authorization;
 
+import java.util.function.Predicate;
 import java.util.function.Supplier;
 
 import org.junit.jupiter.api.BeforeEach;
@@ -26,10 +27,13 @@ import org.springframework.security.authentication.TestAuthentication;
 import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
 import org.springframework.security.core.Authentication;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.BDDMockito.given;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 /**
  * Tests for {@link SpringAuthorizationEventPublisher}
@@ -64,4 +68,16 @@ public class SpringAuthorizationEventPublisherTests {
 		verify(this.applicationEventPublisher).publishEvent(isA(AuthorizationDeniedEvent.class));
 	}
 
+	@Test
+	public void publishWhenPredicateMatchesThenEvent() {
+		Predicate<AuthorizationResult> test = mock(Predicate.class);
+		given(test.test(any())).willReturn(true, false);
+		this.authorizationEventPublisher.setShouldPublishResult(test);
+		AuthorizationResult result = new AuthorizationDecision(false);
+		this.authorizationEventPublisher.publishAuthorizationEvent(this.authentication, mock(Object.class), result);
+		verify(this.applicationEventPublisher).publishEvent(isA(AuthorizationDeniedEvent.class));
+		this.authorizationEventPublisher.publishAuthorizationEvent(this.authentication, mock(Object.class), result);
+		verifyNoMoreInteractions(this.applicationEventPublisher);
+	}
+
 }

+ 21 - 66
docs/modules/ROOT/pages/servlet/authorization/events.adoc

@@ -74,7 +74,7 @@ Because ``AuthorizationGrantedEvent``s have the potential to be quite noisy, the
 
 In fact, publishing these events will likely require some business logic on your part to ensure that your application is not inundated with noisy authorization events.
 
-You can create your own event publisher that filters success events.
+You can provide your own predicate that filters success events.
 For example, the following publisher only publishes authorization grants where `ROLE_ADMIN` was required:
 
 [tabs]
@@ -83,44 +83,20 @@ Java::
 +
 [source,java,role="primary"]
 ----
-@Component
-public class MyAuthorizationEventPublisher implements AuthorizationEventPublisher {
-    private final ApplicationEventPublisher publisher;
-    private final AuthorizationEventPublisher delegate;
-
-    public MyAuthorizationEventPublisher(ApplicationEventPublisher publisher) {
-        this.publisher = publisher;
-        this.delegate = new SpringAuthorizationEventPublisher(publisher);
-    }
-
-    @Override
-    public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication,
-        T object, AuthorizationResult result) {
-        if (result == null) {
-            return;
-        }
+@Bean
+AuthorizationEventPublisher authorizationEventPublisher() {
+    SpringAuthorizationEventPublisher eventPublisher = new SpringAuthorizationEventPublisher();
+    eventPublisher.setShouldPublishEvent((result) -> {
         if (!result.isGranted()) {
-            this.delegate.publishAuthorizationEvent(authentication, object, result);
-            return;
-        }
-        if (shouldThisEventBePublished(result)) {
-            AuthorizationGrantedEvent granted = new AuthorizationGrantedEvent(
-                authentication, object, result);
-            this.publisher.publishEvent(granted);
+            return true;
         }
-    }
-
-    private boolean shouldThisEventBePublished(AuthorizationResult result) {
-        if (result instanceof AuthorityAuthorizationDecision authorityAuthorizationDecision) {
-            Collection<GrantedAuthority> authorities = authorityAuthorizationDecision.getAuthorities();
-            for (GrantedAuthority authority : authorities) {
-                if ("ROLE_ADMIN".equals(authority.getAuthority())) {
-                    return true;
-                }
-            }
+        if (result instanceof AuthorityAuthorizationDecision decision) {
+            Collection<GrantedAuthority> authorities = decision.getAuthorities();
+            return AuthorityUtils.authorityListToSet(authorities).contains("ROLE_ADMIN");
         }
         return false;
-    }
+    });
+    return eventPublisher;
 }
 ----
 
@@ -128,41 +104,20 @@ Kotlin::
 +
 [source,kotlin,role="secondary"]
 ----
-@Component
-class MyAuthorizationEventPublisher(val publisher: ApplicationEventPublisher,
-    val delegate: SpringAuthorizationEventPublisher = SpringAuthorizationEventPublisher(publisher)):
-    AuthorizationEventPublisher {
-
-    override fun <T : Any?> publishAuthorizationEvent(
-        authentication: Supplier<Authentication>?,
-        `object`: T,
-        result: AuthorizationResult?
-    ) {
-        if (result == null) {
-            return
-        }
-        if (!result.isGranted) {
-            this.delegate.publishAuthorizationEvent(authentication, `object`, result)
-            return
-        }
-        if (shouldThisEventBePublished(result)) {
-            val granted = AuthorizationGrantedEvent(authentication, `object`, result)
-            this.publisher.publishEvent(granted)
-        }
-    }
-
-    private fun shouldThisEventBePublished(result: AuthorizationResult): Boolean {
-        if (decision !is AuthorityAuthorizationDecision) {
-            return false
+@Bean
+fun authorizationEventPublisher(): AuthorizationEventPublisher {
+    val eventPublisher = SpringAuthorizationEventPublisher()
+    eventPublisher.setShouldPublishEvent { (result) ->
+        if (!result.isGranted()) {
+            return true
         }
-        val authorities = decision.authorities
-        for (authority in authorities) {
-            if ("ROLE_ADMIN" == authority.authority) {
-                return true
-            }
+        if (decision is AuthorityAuthorizationDecision) {
+            val authorities = decision.getAuthorities()
+            return AuthorityUtils.authorityListToSet(authorities).contains("ROLE_ADMIN")
         }
         return false
     }
+    return eventPublisher
 }
 ----
 ======