AuthorizationEventPublisher Accepts AuthorizationResult

Closes gh-15915

Co-authored-by: Max Batischev <mblancer@mail.ru>
This commit is contained in:
Josh Cummings 2024-10-14 11:27:11 -06:00
parent ef1226ddf8
commit 702538ebce
17 changed files with 196 additions and 33 deletions

View File

@ -75,6 +75,7 @@ import org.springframework.security.access.prepost.PreFilter;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.method.AuthorizationAdvisor;
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory;
import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor;
@ -110,6 +111,7 @@ import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@ -1293,6 +1295,8 @@ public class PrePostMethodSecurityConfigurationTests {
@Bean
AuthorizationEventPublisher authorizationEventPublisher() {
doCallRealMethod().when(this.publisher)
.publishAuthorizationEvent(any(), any(), any(AuthorizationResult.class));
return this.publisher;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -42,6 +42,7 @@ import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationObservationContext;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@ -78,6 +79,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@ -1241,6 +1243,8 @@ public class AuthorizeHttpRequestsConfigurerTests {
@Bean
AuthorizationEventPublisher authorizationEventPublisher() {
doCallRealMethod().when(this.publisher)
.publishAuthorizationEvent(any(), any(), any(AuthorizationResult.class));
return this.publisher;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -42,8 +42,37 @@ public interface AuthorizationEventPublisher {
* @param object the secured object
* @param decision the decision about whether the user may access the secured object
* @param <T> the secured object's type
* @deprecated use
* {@link #publishAuthorizationEvent(Supplier, Object, AuthorizationResult)} instead
*/
@Deprecated
<T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
AuthorizationDecision decision);
/**
* Publish the given details in the form of an event, typically
* {@link AuthorizationGrantedEvent} or {@link AuthorizationDeniedEvent}.
*
* Note that success events can be very noisy if enabled by default. Because of this
* implementations may choose to drop success events by default.
* @param authentication a {@link Supplier} for the current user
* @param object the secured object
* @param result {@link AuthorizationResult} the result about whether the user may
* access the secured object
* @param <T> the secured object's type
* @since 6.4
*/
default <T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
AuthorizationResult result) {
if (result == null) {
publishAuthorizationEvent(authentication, object, null);
return;
}
if (result instanceof AuthorizationDecision decision) {
publishAuthorizationEvent(authentication, object, decision);
return;
}
throw new UnsupportedOperationException("result must be of type AuthorizationDecision");
}
}

View File

@ -55,10 +55,16 @@ public final class SpringAuthorizationEventPublisher implements AuthorizationEve
@Override
public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
AuthorizationDecision decision) {
if (decision == null || decision.isGranted()) {
publishAuthorizationEvent(authentication, object, (AuthorizationResult) decision);
}
@Override
public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
AuthorizationResult result) {
if (result == null || result.isGranted()) {
return;
}
AuthorizationDeniedEvent<T> failure = new AuthorizationDeniedEvent<>(authentication, object, decision);
AuthorizationDeniedEvent<T> failure = new AuthorizationDeniedEvent<>(authentication, object, result);
this.eventPublisher.publishEvent(failure);
}

View File

@ -20,6 +20,7 @@ import java.util.function.Supplier;
import org.springframework.context.ApplicationEvent;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.core.Authentication;
/**
@ -31,10 +32,21 @@ import org.springframework.security.core.Authentication;
*/
public class AuthorizationDeniedEvent<T> extends AuthorizationEvent {
/**
* @deprecated Please use an {@link AuthorizationResult} constructor instead
*/
@Deprecated
public AuthorizationDeniedEvent(Supplier<Authentication> authentication, T object, AuthorizationDecision decision) {
super(authentication, object, decision);
}
/**
* @since 6.4
*/
public AuthorizationDeniedEvent(Supplier<Authentication> authentication, T object, AuthorizationResult result) {
super(authentication, object, result);
}
/**
* Get the object to which access was requested
* @return the object to which access was requested

View File

@ -20,6 +20,7 @@ import java.util.function.Supplier;
import org.springframework.context.ApplicationEvent;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;
@ -34,19 +35,32 @@ public class AuthorizationEvent extends ApplicationEvent {
private final Supplier<Authentication> authentication;
private final AuthorizationDecision decision;
private final AuthorizationResult result;
/**
* Construct an {@link AuthorizationEvent}
* @param authentication the principal requiring access
* @param object the object to which access was requested
* @param decision whether authorization was granted or denied
* @param result whether authorization was granted or denied
*/
public AuthorizationEvent(Supplier<Authentication> authentication, Object object, AuthorizationDecision decision) {
public AuthorizationEvent(Supplier<Authentication> authentication, Object object, AuthorizationDecision result) {
super(object);
Assert.notNull(authentication, "authentication supplier cannot be null");
this.authentication = authentication;
this.decision = decision;
this.result = result;
}
/**
* Construct an {@link AuthorizationEvent}
* @param authentication the principal requiring access
* @param object the object to which access was requested
* @param result whether authorization was granted or denied
*/
public AuthorizationEvent(Supplier<Authentication> authentication, Object object, AuthorizationResult result) {
super(object);
Assert.notNull(authentication, "authentication supplier cannot be null");
this.authentication = authentication;
this.result = result;
}
/**
@ -68,9 +82,27 @@ public class AuthorizationEvent extends ApplicationEvent {
/**
* Get the response to the principal's request
* @return the response to the principal's request
* @deprecated please use {@link #getAuthorizationResult()}
*/
@Deprecated
public AuthorizationDecision getAuthorizationDecision() {
return this.decision;
if (this.result == null) {
return null;
}
if (this.result instanceof AuthorizationDecision decision) {
return decision;
}
throw new IllegalArgumentException(
"Please either call getAuthorizationResult or ensure that the result is of type AuthorizationDecision");
}
/**
* Get the response to the principal's request
* @return the response to the principal's request
* @since 6.4
*/
public AuthorizationResult getAuthorizationResult() {
return this.result;
}
}

View File

@ -20,6 +20,7 @@ import java.util.function.Supplier;
import org.springframework.context.ApplicationEvent;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.core.Authentication;
/**
@ -31,11 +32,23 @@ import org.springframework.security.core.Authentication;
*/
public class AuthorizationGrantedEvent<T> extends AuthorizationEvent {
/**
* @deprecated please use a constructor that takes an
* {@link org.springframework.security.authorization.AuthorizationResult}
*/
@Deprecated
public AuthorizationGrantedEvent(Supplier<Authentication> authentication, T object,
AuthorizationDecision decision) {
super(authentication, object, decision);
}
/**
* @since 6.4
*/
public AuthorizationGrantedEvent(Supplier<Authentication> authentication, T object, AuthorizationResult result) {
super(authentication, object, result);
}
/**
* Get the object to which access was requested
* @return the object to which access was requested

View File

@ -60,7 +60,7 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
private int order;
private AuthorizationEventPublisher eventPublisher = AuthorizationManagerAfterMethodInterceptor::noPublish;
private AuthorizationEventPublisher eventPublisher = new NoOpAuthorizationEventPublisher();
/**
* Creates an instance.
@ -209,9 +209,4 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori
return authentication;
}
private static <T> void noPublish(Supplier<Authentication> authentication, T object,
AuthorizationDecision decision) {
}
}

View File

@ -65,7 +65,7 @@ public final class AuthorizationManagerBeforeMethodInterceptor implements Author
private int order = AuthorizationInterceptorsOrder.FIRST.getOrder();
private AuthorizationEventPublisher eventPublisher = AuthorizationManagerBeforeMethodInterceptor::noPublish;
private AuthorizationEventPublisher eventPublisher = new NoOpAuthorizationEventPublisher();
/**
* Creates an instance.
@ -299,9 +299,4 @@ public final class AuthorizationManagerBeforeMethodInterceptor implements Author
return authentication;
}
private static <T> void noPublish(Supplier<Authentication> authentication, T object,
AuthorizationDecision decision) {
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import java.util.function.Supplier;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.core.Authentication;
/**
* An {@link AuthorizationEventPublisher} implementation that does nothing.
*
* @author Max Batischev
* @since 6.4
*/
final class NoOpAuthorizationEventPublisher implements AuthorizationEventPublisher {
@Override
public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
AuthorizationDecision decision) {
}
@Override
public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
AuthorizationResult result) {
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -42,6 +42,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@ -127,6 +128,8 @@ public class AuthorizationManagerAfterMethodInterceptorTests {
AuthorizationManagerAfterMethodInterceptor advice = new AuthorizationManagerAfterMethodInterceptor(
Pointcut.TRUE, AuthenticatedAuthorizationManager.authenticated());
AuthorizationEventPublisher eventPublisher = mock(AuthorizationEventPublisher.class);
doCallRealMethod().when(eventPublisher)
.publishAuthorizationEvent(any(Supplier.class), any(), any(AuthorizationResult.class));
advice.setAuthorizationEventPublisher(eventPublisher);
SecurityContext securityContext = new SecurityContextImpl();

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -41,6 +41,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@ -121,6 +122,7 @@ public class AuthorizationManagerBeforeMethodInterceptorTests {
AuthorizationManagerBeforeMethodInterceptor advice = new AuthorizationManagerBeforeMethodInterceptor(
Pointcut.TRUE, AuthenticatedAuthorizationManager.authenticated());
AuthorizationEventPublisher eventPublisher = mock(AuthorizationEventPublisher.class);
doCallRealMethod().when(eventPublisher).publishAuthorizationEvent(any(), any(), any(AuthorizationResult.class));
advice.setAuthorizationEventPublisher(eventPublisher);
SecurityContext securityContext = new SecurityContextImpl();

View File

@ -13,7 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
springBootVersion=3.3.3
version=6.4.0-SNAPSHOT
samplesBranch=main

View File

@ -30,6 +30,7 @@ import org.springframework.security.authentication.AuthenticationCredentialsNotF
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@ -114,6 +115,11 @@ public final class AuthorizationChannelInterceptor implements ChannelInterceptor
}
@Override
public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
AuthorizationResult result) {
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -30,6 +30,7 @@ import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
@ -38,6 +39,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.verify;
/**
@ -102,6 +104,9 @@ public class AuthorizationChannelInterceptorTests {
public void preSendWhenAuthorizationEventPublisherThenPublishes() {
this.interceptor.setAuthorizationEventPublisher(this.eventPublisher);
given(this.authorizationManager.check(any(), any())).willReturn(new AuthorizationDecision(true));
lenient().doCallRealMethod()
.when(this.eventPublisher)
.publishAuthorizationEvent(any(), any(), any(AuthorizationResult.class));
this.interceptor.preSend(this.message, this.channel);
verify(this.eventPublisher).publishAuthorizationEvent(any(), any(), any());
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -33,6 +33,7 @@ import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
import org.springframework.security.authorization.event.AuthorizationGrantedEvent;
import org.springframework.security.core.Authentication;
@ -55,7 +56,7 @@ public class AuthorizationFilter extends GenericFilterBean {
private final AuthorizationManager<HttpServletRequest> authorizationManager;
private AuthorizationEventPublisher eventPublisher = AuthorizationFilter::noPublish;
private AuthorizationEventPublisher eventPublisher = new NoopAuthorizationEventPublisher();
private boolean observeOncePerRequest = false;
@ -195,11 +196,6 @@ public class AuthorizationFilter extends GenericFilterBean {
this.filterAsyncDispatch = shouldFilterAllDispatcherTypes;
}
private static <T> void noPublish(Supplier<Authentication> authentication, T object,
AuthorizationDecision decision) {
}
public boolean isObserveOncePerRequest() {
return this.observeOncePerRequest;
}
@ -235,4 +231,19 @@ public class AuthorizationFilter extends GenericFilterBean {
this.filterAsyncDispatch = filterAsyncDispatch;
}
private static class NoopAuthorizationEventPublisher implements AuthorizationEventPublisher {
@Override
public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
AuthorizationDecision decision) {
}
@Override
public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
AuthorizationResult result) {
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -38,6 +38,7 @@ import org.springframework.security.authorization.AuthenticatedAuthorizationMana
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
@ -53,6 +54,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@ -186,6 +188,7 @@ public class AuthorizationFilterTests {
SecurityContextHolder.setContext(securityContext);
AuthorizationEventPublisher eventPublisher = mock(AuthorizationEventPublisher.class);
doCallRealMethod().when(eventPublisher).publishAuthorizationEvent(any(), any(), any(AuthorizationResult.class));
authorizationFilter.setAuthorizationEventPublisher(eventPublisher);
authorizationFilter.doFilter(mockRequest, mockResponse, mockFilterChain);
verify(eventPublisher).publishAuthorizationEvent(any(Supplier.class), any(HttpServletRequest.class),