mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-07-11 21:03:31 +00:00
Add Publishing Predicate
Closes gh-17503
This commit is contained in:
parent
901b386ca6
commit
c312d18191
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.springframework.security.authorization;
|
package org.springframework.security.authorization;
|
||||||
|
|
||||||
|
import java.util.function.Predicate;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
@ -40,6 +41,8 @@ public final class SpringAuthorizationEventPublisher implements AuthorizationEve
|
|||||||
|
|
||||||
private final ApplicationEventPublisher eventPublisher;
|
private final ApplicationEventPublisher eventPublisher;
|
||||||
|
|
||||||
|
private Predicate<AuthorizationResult> shouldPublishResult = (result) -> !result.isGranted();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct this publisher using Spring's {@link ApplicationEventPublisher}
|
* Construct this publisher using Spring's {@link ApplicationEventPublisher}
|
||||||
* @param eventPublisher
|
* @param eventPublisher
|
||||||
@ -55,11 +58,28 @@ public final class SpringAuthorizationEventPublisher implements AuthorizationEve
|
|||||||
@Override
|
@Override
|
||||||
public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
|
public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
|
||||||
AuthorizationResult result) {
|
AuthorizationResult result) {
|
||||||
if (result == null || result.isGranted()) {
|
if (result == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.shouldPublishResult.test(result)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
AuthorizationDeniedEvent<T> failure = new AuthorizationDeniedEvent<>(authentication, object, result);
|
AuthorizationDeniedEvent<T> failure = new AuthorizationDeniedEvent<>(authentication, object, result);
|
||||||
this.eventPublisher.publishEvent(failure);
|
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,6 +16,7 @@
|
|||||||
|
|
||||||
package org.springframework.security.authorization;
|
package org.springframework.security.authorization;
|
||||||
|
|
||||||
|
import java.util.function.Predicate;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
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.authorization.event.AuthorizationDeniedEvent;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.isA;
|
import static org.mockito.ArgumentMatchers.isA;
|
||||||
|
import static org.mockito.BDDMockito.given;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.verifyNoInteractions;
|
import static org.mockito.Mockito.verifyNoInteractions;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link SpringAuthorizationEventPublisher}
|
* Tests for {@link SpringAuthorizationEventPublisher}
|
||||||
@ -64,4 +68,16 @@ public class SpringAuthorizationEventPublisherTests {
|
|||||||
verify(this.applicationEventPublisher).publishEvent(isA(AuthorizationDeniedEvent.class));
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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.
|
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:
|
For example, the following publisher only publishes authorization grants where `ROLE_ADMIN` was required:
|
||||||
|
|
||||||
[tabs]
|
[tabs]
|
||||||
@ -83,44 +83,20 @@ Java::
|
|||||||
+
|
+
|
||||||
[source,java,role="primary"]
|
[source,java,role="primary"]
|
||||||
----
|
----
|
||||||
@Component
|
@Bean
|
||||||
public class MyAuthorizationEventPublisher implements AuthorizationEventPublisher {
|
AuthorizationEventPublisher authorizationEventPublisher() {
|
||||||
private final ApplicationEventPublisher publisher;
|
SpringAuthorizationEventPublisher eventPublisher = new SpringAuthorizationEventPublisher();
|
||||||
private final AuthorizationEventPublisher delegate;
|
eventPublisher.setShouldPublishEvent((result) -> {
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
if (!result.isGranted()) {
|
if (!result.isGranted()) {
|
||||||
this.delegate.publishAuthorizationEvent(authentication, object, result);
|
return true;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (shouldThisEventBePublished(result)) {
|
if (result instanceof AuthorityAuthorizationDecision decision) {
|
||||||
AuthorizationGrantedEvent granted = new AuthorizationGrantedEvent(
|
Collection<GrantedAuthority> authorities = decision.getAuthorities();
|
||||||
authentication, object, result);
|
return AuthorityUtils.authorityListToSet(authorities).contains("ROLE_ADMIN");
|
||||||
this.publisher.publishEvent(granted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
});
|
||||||
|
return eventPublisher;
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
@ -128,41 +104,20 @@ Kotlin::
|
|||||||
+
|
+
|
||||||
[source,kotlin,role="secondary"]
|
[source,kotlin,role="secondary"]
|
||||||
----
|
----
|
||||||
@Component
|
@Bean
|
||||||
class MyAuthorizationEventPublisher(val publisher: ApplicationEventPublisher,
|
fun authorizationEventPublisher(): AuthorizationEventPublisher {
|
||||||
val delegate: SpringAuthorizationEventPublisher = SpringAuthorizationEventPublisher(publisher)):
|
val eventPublisher = SpringAuthorizationEventPublisher()
|
||||||
AuthorizationEventPublisher {
|
eventPublisher.setShouldPublishEvent { (result) ->
|
||||||
|
if (!result.isGranted()) {
|
||||||
override fun <T : Any?> publishAuthorizationEvent(
|
return true
|
||||||
authentication: Supplier<Authentication>?,
|
|
||||||
`object`: T,
|
|
||||||
result: AuthorizationResult?
|
|
||||||
) {
|
|
||||||
if (result == null) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if (!result.isGranted) {
|
if (decision is AuthorityAuthorizationDecision) {
|
||||||
this.delegate.publishAuthorizationEvent(authentication, `object`, result)
|
val authorities = decision.getAuthorities()
|
||||||
return
|
return AuthorityUtils.authorityListToSet(authorities).contains("ROLE_ADMIN")
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
val authorities = decision.authorities
|
|
||||||
for (authority in authorities) {
|
|
||||||
if ("ROLE_ADMIN" == authority.authority) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
return eventPublisher
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
======
|
======
|
||||||
|
Loading…
x
Reference in New Issue
Block a user