Skip to content

Commit c312d18

Browse files
committed
Add Publishing Predicate
Closes gh-17503
1 parent 901b386 commit c312d18

File tree

3 files changed

+58
-67
lines changed

3 files changed

+58
-67
lines changed

core/src/main/java/org/springframework/security/authorization/SpringAuthorizationEventPublisher.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.security.authorization;
1818

19+
import java.util.function.Predicate;
1920
import java.util.function.Supplier;
2021

2122
import org.springframework.context.ApplicationEventPublisher;
@@ -40,6 +41,8 @@ public final class SpringAuthorizationEventPublisher implements AuthorizationEve
4041

4142
private final ApplicationEventPublisher eventPublisher;
4243

44+
private Predicate<AuthorizationResult> shouldPublishResult = (result) -> !result.isGranted();
45+
4346
/**
4447
* Construct this publisher using Spring's {@link ApplicationEventPublisher}
4548
* @param eventPublisher
@@ -55,11 +58,28 @@ public SpringAuthorizationEventPublisher(ApplicationEventPublisher eventPublishe
5558
@Override
5659
public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
5760
AuthorizationResult result) {
58-
if (result == null || result.isGranted()) {
61+
if (result == null) {
62+
return;
63+
}
64+
if (!this.shouldPublishResult.test(result)) {
5965
return;
6066
}
6167
AuthorizationDeniedEvent<T> failure = new AuthorizationDeniedEvent<>(authentication, object, result);
6268
this.eventPublisher.publishEvent(failure);
6369
}
6470

71+
/**
72+
* Use this predicate to test whether to publish an event.
73+
*
74+
* <p>
75+
* Since you cannot publish a {@code null} event, checking for null is already
76+
* performed before this test is run
77+
* @param shouldPublishResult the test to perform on non-{@code null} events
78+
* @since 7.0
79+
*/
80+
public void setShouldPublishResult(Predicate<AuthorizationResult> shouldPublishResult) {
81+
Assert.notNull(shouldPublishResult, "shouldPublishResult cannot be null");
82+
this.shouldPublishResult = shouldPublishResult;
83+
}
84+
6585
}

core/src/test/java/org/springframework/security/authorization/SpringAuthorizationEventPublisherTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.security.authorization;
1818

19+
import java.util.function.Predicate;
1920
import java.util.function.Supplier;
2021

2122
import org.junit.jupiter.api.BeforeEach;
@@ -26,10 +27,13 @@
2627
import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
2728
import org.springframework.security.core.Authentication;
2829

30+
import static org.mockito.ArgumentMatchers.any;
2931
import static org.mockito.ArgumentMatchers.isA;
32+
import static org.mockito.BDDMockito.given;
3033
import static org.mockito.Mockito.mock;
3134
import static org.mockito.Mockito.verify;
3235
import static org.mockito.Mockito.verifyNoInteractions;
36+
import static org.mockito.Mockito.verifyNoMoreInteractions;
3337

3438
/**
3539
* Tests for {@link SpringAuthorizationEventPublisher}
@@ -64,4 +68,16 @@ public void testAuthenticationFailureIsPublished() {
6468
verify(this.applicationEventPublisher).publishEvent(isA(AuthorizationDeniedEvent.class));
6569
}
6670

71+
@Test
72+
public void publishWhenPredicateMatchesThenEvent() {
73+
Predicate<AuthorizationResult> test = mock(Predicate.class);
74+
given(test.test(any())).willReturn(true, false);
75+
this.authorizationEventPublisher.setShouldPublishResult(test);
76+
AuthorizationResult result = new AuthorizationDecision(false);
77+
this.authorizationEventPublisher.publishAuthorizationEvent(this.authentication, mock(Object.class), result);
78+
verify(this.applicationEventPublisher).publishEvent(isA(AuthorizationDeniedEvent.class));
79+
this.authorizationEventPublisher.publishAuthorizationEvent(this.authentication, mock(Object.class), result);
80+
verifyNoMoreInteractions(this.applicationEventPublisher);
81+
}
82+
6783
}

docs/modules/ROOT/pages/servlet/authorization/events.adoc

Lines changed: 21 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ Because ``AuthorizationGrantedEvent``s have the potential to be quite noisy, the
7474

7575
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.
7676

77-
You can create your own event publisher that filters success events.
77+
You can provide your own predicate that filters success events.
7878
For example, the following publisher only publishes authorization grants where `ROLE_ADMIN` was required:
7979

8080
[tabs]
@@ -83,86 +83,41 @@ Java::
8383
+
8484
[source,java,role="primary"]
8585
----
86-
@Component
87-
public class MyAuthorizationEventPublisher implements AuthorizationEventPublisher {
88-
private final ApplicationEventPublisher publisher;
89-
private final AuthorizationEventPublisher delegate;
90-
91-
public MyAuthorizationEventPublisher(ApplicationEventPublisher publisher) {
92-
this.publisher = publisher;
93-
this.delegate = new SpringAuthorizationEventPublisher(publisher);
94-
}
95-
96-
@Override
97-
public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication,
98-
T object, AuthorizationResult result) {
99-
if (result == null) {
100-
return;
101-
}
86+
@Bean
87+
AuthorizationEventPublisher authorizationEventPublisher() {
88+
SpringAuthorizationEventPublisher eventPublisher = new SpringAuthorizationEventPublisher();
89+
eventPublisher.setShouldPublishEvent((result) -> {
10290
if (!result.isGranted()) {
103-
this.delegate.publishAuthorizationEvent(authentication, object, result);
104-
return;
105-
}
106-
if (shouldThisEventBePublished(result)) {
107-
AuthorizationGrantedEvent granted = new AuthorizationGrantedEvent(
108-
authentication, object, result);
109-
this.publisher.publishEvent(granted);
91+
return true;
11092
}
111-
}
112-
113-
private boolean shouldThisEventBePublished(AuthorizationResult result) {
114-
if (result instanceof AuthorityAuthorizationDecision authorityAuthorizationDecision) {
115-
Collection<GrantedAuthority> authorities = authorityAuthorizationDecision.getAuthorities();
116-
for (GrantedAuthority authority : authorities) {
117-
if ("ROLE_ADMIN".equals(authority.getAuthority())) {
118-
return true;
119-
}
120-
}
93+
if (result instanceof AuthorityAuthorizationDecision decision) {
94+
Collection<GrantedAuthority> authorities = decision.getAuthorities();
95+
return AuthorityUtils.authorityListToSet(authorities).contains("ROLE_ADMIN");
12196
}
12297
return false;
123-
}
98+
});
99+
return eventPublisher;
124100
}
125101
----
126102
127103
Kotlin::
128104
+
129105
[source,kotlin,role="secondary"]
130106
----
131-
@Component
132-
class MyAuthorizationEventPublisher(val publisher: ApplicationEventPublisher,
133-
val delegate: SpringAuthorizationEventPublisher = SpringAuthorizationEventPublisher(publisher)):
134-
AuthorizationEventPublisher {
135-
136-
override fun <T : Any?> publishAuthorizationEvent(
137-
authentication: Supplier<Authentication>?,
138-
`object`: T,
139-
result: AuthorizationResult?
140-
) {
141-
if (result == null) {
142-
return
143-
}
144-
if (!result.isGranted) {
145-
this.delegate.publishAuthorizationEvent(authentication, `object`, result)
146-
return
147-
}
148-
if (shouldThisEventBePublished(result)) {
149-
val granted = AuthorizationGrantedEvent(authentication, `object`, result)
150-
this.publisher.publishEvent(granted)
151-
}
152-
}
153-
154-
private fun shouldThisEventBePublished(result: AuthorizationResult): Boolean {
155-
if (decision !is AuthorityAuthorizationDecision) {
156-
return false
107+
@Bean
108+
fun authorizationEventPublisher(): AuthorizationEventPublisher {
109+
val eventPublisher = SpringAuthorizationEventPublisher()
110+
eventPublisher.setShouldPublishEvent { (result) ->
111+
if (!result.isGranted()) {
112+
return true
157113
}
158-
val authorities = decision.authorities
159-
for (authority in authorities) {
160-
if ("ROLE_ADMIN" == authority.authority) {
161-
return true
162-
}
114+
if (decision is AuthorityAuthorizationDecision) {
115+
val authorities = decision.getAuthorities()
116+
return AuthorityUtils.authorityListToSet(authorities).contains("ROLE_ADMIN")
163117
}
164118
return false
165119
}
120+
return eventPublisher
166121
}
167122
----
168123
======

0 commit comments

Comments
 (0)