Skip to content

Commit 061f69e

Browse files
committed
Polish Authorization Event Support
- Added spring-security-config support - Renamed classes - Changed contracts to include the authenticated user and secured object - Added method security support Issue gh-9288
1 parent bd94348 commit 061f69e

19 files changed

+498
-239
lines changed

config/src/main/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfiguration.java

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,16 +17,16 @@
1717
package org.springframework.security.config.annotation.method.configuration;
1818

1919
import org.springframework.aop.Advisor;
20-
import org.springframework.beans.BeansException;
2120
import org.springframework.beans.factory.annotation.Autowired;
2221
import org.springframework.beans.factory.config.BeanDefinition;
2322
import org.springframework.context.ApplicationContext;
24-
import org.springframework.context.ApplicationContextAware;
2523
import org.springframework.context.annotation.Bean;
2624
import org.springframework.context.annotation.Configuration;
2725
import org.springframework.context.annotation.Role;
2826
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
2927
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
28+
import org.springframework.security.authorization.AuthorizationEventPublisher;
29+
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
3030
import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor;
3131
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
3232
import org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager;
@@ -45,7 +45,7 @@
4545
*/
4646
@Configuration(proxyBeanMethods = false)
4747
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
48-
final class PrePostMethodSecurityConfiguration implements ApplicationContextAware {
48+
final class PrePostMethodSecurityConfiguration {
4949

5050
private final PreFilterAuthorizationMethodInterceptor preFilterAuthorizationMethodInterceptor = new PreFilterAuthorizationMethodInterceptor();
5151

@@ -61,7 +61,8 @@ final class PrePostMethodSecurityConfiguration implements ApplicationContextAwar
6161

6262
private final DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
6363

64-
PrePostMethodSecurityConfiguration() {
64+
@Autowired
65+
PrePostMethodSecurityConfiguration(ApplicationContext context) {
6566
this.preAuthorizeAuthorizationManager.setExpressionHandler(this.expressionHandler);
6667
this.preAuthorizeAuthorizationMethodInterceptor = AuthorizationManagerBeforeMethodInterceptor
6768
.preAuthorize(this.preAuthorizeAuthorizationManager);
@@ -70,6 +71,10 @@ final class PrePostMethodSecurityConfiguration implements ApplicationContextAwar
7071
.postAuthorize(this.postAuthorizeAuthorizationManager);
7172
this.preFilterAuthorizationMethodInterceptor.setExpressionHandler(this.expressionHandler);
7273
this.postFilterAuthorizationMethodInterceptor.setExpressionHandler(this.expressionHandler);
74+
this.expressionHandler.setApplicationContext(context);
75+
AuthorizationEventPublisher publisher = new SpringAuthorizationEventPublisher(context);
76+
this.preAuthorizeAuthorizationMethodInterceptor.setAuthorizationEventPublisher(publisher);
77+
this.postAuthorizeAuthorizaitonMethodInterceptor.setAuthorizationEventPublisher(publisher);
7378
}
7479

7580
@Bean
@@ -109,9 +114,10 @@ void setGrantedAuthorityDefaults(GrantedAuthorityDefaults grantedAuthorityDefaul
109114
this.expressionHandler.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix());
110115
}
111116

112-
@Override
113-
public void setApplicationContext(ApplicationContext context) throws BeansException {
114-
this.expressionHandler.setApplicationContext(context);
117+
@Autowired(required = false)
118+
void setAuthorizationEventPublisher(AuthorizationEventPublisher eventPublisher) {
119+
this.preAuthorizeAuthorizationMethodInterceptor.setAuthorizationEventPublisher(eventPublisher);
120+
this.postAuthorizeAuthorizaitonMethodInterceptor.setAuthorizationEventPublisher(eventPublisher);
115121
}
116122

117123
}

config/src/main/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurer.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,7 +26,9 @@
2626
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
2727
import org.springframework.security.authorization.AuthorityAuthorizationManager;
2828
import org.springframework.security.authorization.AuthorizationDecision;
29+
import org.springframework.security.authorization.AuthorizationEventPublisher;
2930
import org.springframework.security.authorization.AuthorizationManager;
31+
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
3032
import org.springframework.security.config.annotation.ObjectPostProcessor;
3133
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
3234
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
@@ -52,12 +54,20 @@ public final class AuthorizeHttpRequestsConfigurer<H extends HttpSecurityBuilder
5254

5355
private final AuthorizationManagerRequestMatcherRegistry registry;
5456

57+
private final AuthorizationEventPublisher publisher;
58+
5559
/**
5660
* Creates an instance.
5761
* @param context the {@link ApplicationContext} to use
5862
*/
5963
public AuthorizeHttpRequestsConfigurer(ApplicationContext context) {
6064
this.registry = new AuthorizationManagerRequestMatcherRegistry(context);
65+
if (context.getBeanNamesForType(AuthorizationEventPublisher.class).length > 0) {
66+
this.publisher = context.getBean(AuthorizationEventPublisher.class);
67+
}
68+
else {
69+
this.publisher = new SpringAuthorizationEventPublisher(context);
70+
}
6171
}
6272

6373
/**
@@ -74,6 +84,7 @@ public AuthorizationManagerRequestMatcherRegistry getRegistry() {
7484
public void configure(H http) {
7585
AuthorizationManager<HttpServletRequest> authorizationManager = this.registry.createAuthorizationManager();
7686
AuthorizationFilter authorizationFilter = new AuthorizationFilter(authorizationManager);
87+
authorizationFilter.setAuthorizationEventPublisher(this.publisher);
7788
http.addFilter(postProcess(authorizationFilter));
7889
}
7990

config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020
import java.util.ArrayList;
2121
import java.util.Arrays;
2222
import java.util.List;
23+
import java.util.function.Supplier;
2324

2425
import org.aopalliance.intercept.MethodInterceptor;
2526
import org.aopalliance.intercept.MethodInvocation;
@@ -32,6 +33,7 @@
3233
import org.springframework.beans.factory.annotation.Autowired;
3334
import org.springframework.beans.factory.config.BeanDefinition;
3435
import org.springframework.context.annotation.Bean;
36+
import org.springframework.context.annotation.Configuration;
3537
import org.springframework.context.annotation.Role;
3638
import org.springframework.core.annotation.AnnotationConfigurationException;
3739
import org.springframework.security.access.AccessDeniedException;
@@ -43,9 +45,11 @@
4345
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
4446
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
4547
import org.springframework.security.authorization.AuthorizationDecision;
48+
import org.springframework.security.authorization.AuthorizationEventPublisher;
4649
import org.springframework.security.authorization.AuthorizationManager;
4750
import org.springframework.security.authorization.method.AuthorizationInterceptorsOrder;
4851
import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
52+
import org.springframework.security.authorization.method.MethodInvocationResult;
4953
import org.springframework.security.config.core.GrantedAuthorityDefaults;
5054
import org.springframework.security.config.test.SpringTestContext;
5155
import org.springframework.security.config.test.SpringTestContextExtension;
@@ -58,6 +62,9 @@
5862

5963
import static org.assertj.core.api.Assertions.assertThat;
6064
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
65+
import static org.mockito.ArgumentMatchers.any;
66+
import static org.mockito.Mockito.mock;
67+
import static org.mockito.Mockito.verify;
6168

6269
/**
6370
* Tests for {@link PrePostMethodSecurityConfiguration}.
@@ -350,6 +357,27 @@ public void repeatedSecuredAnnotationsWhenPresentThenFails() {
350357
.isThrownBy(() -> this.businessService.repeatedAnnotations());
351358
}
352359

360+
@WithMockUser
361+
@Test
362+
public void preAuthorizeWhenAuthorizationEventPublisherThenUses() {
363+
this.spring.register(MethodSecurityServiceConfig.class, AuthorizationEventPublisherConfig.class).autowire();
364+
assertThatExceptionOfType(AccessDeniedException.class)
365+
.isThrownBy(() -> this.methodSecurityService.preAuthorize());
366+
AuthorizationEventPublisher publisher = this.spring.getContext().getBean(AuthorizationEventPublisher.class);
367+
verify(publisher).publishAuthorizationEvent(any(Supplier.class), any(MethodInvocation.class),
368+
any(AuthorizationDecision.class));
369+
}
370+
371+
@WithMockUser
372+
@Test
373+
public void postAuthorizeWhenAuthorizationEventPublisherThenUses() {
374+
this.spring.register(MethodSecurityServiceConfig.class, AuthorizationEventPublisherConfig.class).autowire();
375+
this.methodSecurityService.postAnnotation("grant");
376+
AuthorizationEventPublisher publisher = this.spring.getContext().getBean(AuthorizationEventPublisher.class);
377+
verify(publisher).publishAuthorizationEvent(any(Supplier.class), any(MethodInvocationResult.class),
378+
any(AuthorizationDecision.class));
379+
}
380+
353381
// gh-10305
354382
@WithMockUser
355383
@Test
@@ -484,4 +512,16 @@ Advisor customAfterAdvice() {
484512

485513
}
486514

515+
@Configuration
516+
static class AuthorizationEventPublisherConfig {
517+
518+
private final AuthorizationEventPublisher publisher = mock(AuthorizationEventPublisher.class);
519+
520+
@Bean
521+
AuthorizationEventPublisher authorizationEventPublisher() {
522+
return this.publisher;
523+
}
524+
525+
}
526+
487527
}

config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,12 +16,19 @@
1616

1717
package org.springframework.security.config.annotation.web.configurers;
1818

19+
import java.util.function.Supplier;
20+
21+
import javax.servlet.http.HttpServletRequest;
22+
1923
import org.junit.jupiter.api.Test;
2024
import org.junit.jupiter.api.extension.ExtendWith;
2125

2226
import org.springframework.beans.factory.BeanCreationException;
2327
import org.springframework.beans.factory.annotation.Autowired;
2428
import org.springframework.context.annotation.Bean;
29+
import org.springframework.context.annotation.Configuration;
30+
import org.springframework.security.authorization.AuthorizationDecision;
31+
import org.springframework.security.authorization.AuthorizationEventPublisher;
2532
import org.springframework.security.authorization.AuthorizationManager;
2633
import org.springframework.security.config.annotation.ObjectPostProcessor;
2734
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
@@ -129,9 +136,9 @@ public void configureMvcMatcherAccessAuthorizationManagerWhenNullThenException()
129136
@Test
130137
public void configureWhenObjectPostProcessorRegisteredThenInvokedOnAuthorizationManagerAndAuthorizationFilter() {
131138
this.spring.register(ObjectPostProcessorConfig.class).autowire();
132-
verify(ObjectPostProcessorConfig.objectPostProcessor)
133-
.postProcess(any(RequestMatcherDelegatingAuthorizationManager.class));
134-
verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(AuthorizationFilter.class));
139+
ObjectPostProcessor objectPostProcessor = this.spring.getContext().getBean(ObjectPostProcessor.class);
140+
verify(objectPostProcessor).postProcess(any(RequestMatcherDelegatingAuthorizationManager.class));
141+
verify(objectPostProcessor).postProcess(any(AuthorizationFilter.class));
135142
}
136143

137144
@Test
@@ -369,6 +376,15 @@ public void getWhenAnyRequestAuthenticatedConfiguredAndNoUserThenRespondsWithUna
369376
this.mvc.perform(get("/")).andExpect(status().isUnauthorized());
370377
}
371378

379+
@Test
380+
public void getWhenCustomAuthorizationEventPublisherThenUses() throws Exception {
381+
this.spring.register(AuthenticatedConfig.class, AuthorizationEventPublisherConfig.class).autowire();
382+
this.mvc.perform(get("/")).andExpect(status().isUnauthorized());
383+
AuthorizationEventPublisher publisher = this.spring.getContext().getBean(AuthorizationEventPublisher.class);
384+
verify(publisher).publishAuthorizationEvent(any(Supplier.class), any(HttpServletRequest.class),
385+
any(AuthorizationDecision.class));
386+
}
387+
372388
@Test
373389
public void getWhenAnyRequestAuthenticatedConfiguredAndUserLoggedInThenRespondsWithOk() throws Exception {
374390
this.spring.register(AuthenticatedConfig.class, BasicController.class).autowire();
@@ -495,7 +511,7 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
495511
@EnableWebSecurity
496512
static class ObjectPostProcessorConfig {
497513

498-
static ObjectPostProcessor<Object> objectPostProcessor = spy(ReflectingObjectPostProcessor.class);
514+
ObjectPostProcessor<Object> objectPostProcessor = spy(ReflectingObjectPostProcessor.class);
499515

500516
@Bean
501517
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
@@ -509,8 +525,8 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
509525
}
510526

511527
@Bean
512-
static ObjectPostProcessor<Object> objectPostProcessor() {
513-
return objectPostProcessor;
528+
ObjectPostProcessor<Object> objectPostProcessor() {
529+
return this.objectPostProcessor;
514530
}
515531

516532
}
@@ -698,6 +714,18 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
698714

699715
}
700716

717+
@Configuration
718+
static class AuthorizationEventPublisherConfig {
719+
720+
private final AuthorizationEventPublisher publisher = mock(AuthorizationEventPublisher.class);
721+
722+
@Bean
723+
AuthorizationEventPublisher authorizationEventPublisher() {
724+
return this.publisher;
725+
}
726+
727+
}
728+
701729
@RestController
702730
static class BasicController {
703731

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

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,14 +16,34 @@
1616

1717
package org.springframework.security.authorization;
1818

19+
import java.util.function.Supplier;
20+
21+
import org.springframework.security.authorization.event.AuthorizationDeniedEvent;
22+
import org.springframework.security.authorization.event.AuthorizationGrantedEvent;
23+
import org.springframework.security.core.Authentication;
24+
1925
/**
26+
* A contract for publishing authorization events
27+
*
2028
* @author Parikshit Dutta
21-
* @since 5.5
29+
* @author Josh Cummings
30+
* @since 5.7
31+
* @see AuthorizationManager
2232
*/
2333
public interface AuthorizationEventPublisher {
2434

25-
void publishAuthorizationSuccess(AuthorizationDecision authorizationDecision);
26-
27-
void publishAuthorizationFailure(AuthorizationDecision authorizationDecision);
35+
/**
36+
* Publish the given details in the form of an event, typically
37+
* {@link AuthorizationGrantedEvent} or {@link AuthorizationDeniedEvent}.
38+
*
39+
* Note that success events can be very noisy if enabled by default. Because of this
40+
* implementations may choose to drop success events by default.
41+
* @param authentication a {@link Supplier} for the current user
42+
* @param object the secured object
43+
* @param decision the decision about whether the user may access the secured object
44+
* @param <T> the secured object's type
45+
*/
46+
<T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
47+
AuthorizationDecision decision);
2848

2949
}

0 commit comments

Comments
 (0)