From ce720ad38e99e3b27a318dba8f8f16befa4da176 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Tue, 29 Mar 2022 15:22:38 -0600 Subject: [PATCH] Document Authorization Events Issue gh-9288 --- docs/modules/ROOT/nav.adoc | 1 + .../pages/servlet/authorization/events.adoc | 160 ++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 docs/modules/ROOT/pages/servlet/authorization/events.adoc diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index fba7f23233..d33f092900 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -54,6 +54,7 @@ *** xref:servlet/authorization/secure-objects.adoc[Secure Object Implementations] *** xref:servlet/authorization/method-security.adoc[Method Security] *** xref:servlet/authorization/acls.adoc[Domain Object Security ACLs] +*** xref:servlet/authorization/events.adoc[Authorization Events] ** xref:servlet/oauth2/index.adoc[OAuth2] *** xref:servlet/oauth2/login/index.adoc[OAuth2 Log In] **** xref:servlet/oauth2/login/core.adoc[Core Configuration] diff --git a/docs/modules/ROOT/pages/servlet/authorization/events.adoc b/docs/modules/ROOT/pages/servlet/authorization/events.adoc new file mode 100644 index 0000000000..d85611a0d3 --- /dev/null +++ b/docs/modules/ROOT/pages/servlet/authorization/events.adoc @@ -0,0 +1,160 @@ +[[servlet-events]] += Authorization Events + +For each authorization that is denied, an `AuthorizationDeniedEvent` is fired. +Also, it's possible to fire and `AuthorizationGrantedEvent` for authorizations that are granted. + +To listen for these events, you must first publish an `AuthorizationEventPublisher`. + +Spring Security's `SpringAuthorizationEventPublisher` will probably do fine. +It comes publishes authorization events using Spring's `ApplicationEventPublisher`: + +==== +.Java +[source,java,role="primary"] +---- +@Bean +public AuthorizationEventPublisher authorizationEventPublisher + (ApplicationEventPublisher applicationEventPublisher) { + return new SpringAuthorizationEventPublisher(applicationEventPublisher); +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@Bean +fun authorizationEventPublisher + (applicationEventPublisher: ApplicationEventPublisher?): AuthorizationEventPublisher { + return SpringAuthorizationEventPublisher(applicationEventPublisher) +} +---- +==== + +Then, you can use Spring's `@EventListener` support: + +==== +.Java +[source,java,role="primary"] +---- +@Component +public class AuthenticationEvents { + + @EventListener + public void onFailure(AuthorizationDeniedEvent failure) { + // ... + } +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@Component +class AuthenticationEvents { + + @EventListener + fun onFailure(failure: AuthorizationDeniedEvent?) { + // ... + } +} +---- +==== + +[[authorization-granted-events]] +== Authorization Granted Events + +Because ``AuthorizationGrantedEvent``s have the potential to be quite noisy, they are not published by default. + +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. +For example, the following publisher only publishes authorization grants where `ROLE_ADMIN` was required: + +==== +.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 void publishAuthorizationEvent(Supplier authentication, + T object, AuthorizationDecision decision) { + if (decision == null) { + return; + } + if (!decision.isGranted()) { + this.delegate.publishAuthorizationEvent(authentication, object, decision); + return; + } + if (shouldThisEventBePublished(decision)) { + AuthorizationGrantedEvent granted = new AuthorizationGrantedEvent( + authentication, object, decision); + this.publisher.publishEvent(granted); + } + } + + private boolean shouldThisEventBePublished(AuthorizationDecision decision) { + if (!(decision instanceof AuthorityAuthorizationDecision)) { + return false; + } + Collection authorities = ((AuthorityAuthorizationDecision) decision).getAuthorities(); + for (GrantedAuthority authority : authorities) { + if ("ROLE_ADMIN".equals(authority.getAuthority())) { + return true; + } + } + return false; + } +} +---- + +.Kotlin +[source,kotlin,role="secondary"] +---- +@Component +class MyAuthorizationEventPublisher(val publisher: ApplicationEventPublisher, + val delegate: SpringAuthorizationEventPublisher = SpringAuthorizationEventPublisher(publisher)): + AuthorizationEventPublisher { + + override fun publishAuthorizationEvent( + authentication: Supplier?, + `object`: T, + decision: AuthorizationDecision? + ) { + if (decision == null) { + return + } + if (!decision.isGranted) { + this.delegate.publishAuthorizationEvent(authentication, `object`, decision) + return + } + if (shouldThisEventBePublished(decision)) { + val granted = AuthorizationGrantedEvent(authentication, `object`, decision) + this.publisher.publishEvent(granted) + } + } + + private fun shouldThisEventBePublished(decision: AuthorizationDecision): Boolean { + if (decision !is AuthorityAuthorizationDecision) { + return false + } + val authorities = decision.authorities + for (authority in authorities) { + if ("ROLE_ADMIN" == authority.authority) { + return true + } + } + return false + } +} +---- +====