Skip to content

Commit c7912c5

Browse files
Consistently set AuthenticationEventPublisher in AuthenticationManagerBuilder
Prior to this, the HttpSecurity bean was not consistent with WebSecurityConfigurerAdapter's HttpSecurity because it did not setup a default AuthenticationEventPublisher. This also fixes a problem where the AuthenticationEventPublisher bean would only be considered if there was a UserDetailsService Closes gh-11449 Closes gh-11726
1 parent 53a3ff8 commit c7912c5

File tree

4 files changed

+171
-3
lines changed

4 files changed

+171
-3
lines changed

config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 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.
@@ -39,6 +39,7 @@
3939
import org.springframework.core.log.LogMessage;
4040
import org.springframework.security.authentication.AuthenticationEventPublisher;
4141
import org.springframework.security.authentication.AuthenticationManager;
42+
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
4243
import org.springframework.security.config.annotation.ObjectPostProcessor;
4344
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
4445
import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
@@ -79,8 +80,7 @@ public class AuthenticationConfiguration {
7980
public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor,
8081
ApplicationContext context) {
8182
LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
82-
AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context,
83-
AuthenticationEventPublisher.class);
83+
AuthenticationEventPublisher authenticationEventPublisher = getAuthenticationEventPublisher(context);
8484
DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(
8585
objectPostProcessor, defaultPasswordEncoder);
8686
if (authenticationEventPublisher != null) {
@@ -142,6 +142,13 @@ public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcess
142142
this.objectPostProcessor = objectPostProcessor;
143143
}
144144

145+
private AuthenticationEventPublisher getAuthenticationEventPublisher(ApplicationContext context) {
146+
if (context.getBeanNamesForType(AuthenticationEventPublisher.class).length > 0) {
147+
return context.getBean(AuthenticationEventPublisher.class);
148+
}
149+
return this.objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
150+
}
151+
145152
@SuppressWarnings("unchecked")
146153
private <T> T lazyBean(Class<T> interfaceName) {
147154
LazyInitTargetSource lazyTargetSource = new LazyInitTargetSource();

config/src/main/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
import org.springframework.context.annotation.Configuration;
2727
import org.springframework.context.annotation.Scope;
2828
import org.springframework.core.io.support.SpringFactoriesLoader;
29+
import org.springframework.security.authentication.AuthenticationEventPublisher;
2930
import org.springframework.security.authentication.AuthenticationManager;
31+
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
3032
import org.springframework.security.config.annotation.ObjectPostProcessor;
3133
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
3234
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
@@ -85,6 +87,7 @@ HttpSecurity httpSecurity() throws Exception {
8587
AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(
8688
this.objectPostProcessor, passwordEncoder);
8789
authenticationBuilder.parentAuthenticationManager(authenticationManager());
90+
authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher());
8891
HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
8992
// @formatter:off
9093
http
@@ -109,6 +112,13 @@ private AuthenticationManager authenticationManager() throws Exception {
109112
: this.authenticationConfiguration.getAuthenticationManager();
110113
}
111114

115+
private AuthenticationEventPublisher getAuthenticationEventPublisher() {
116+
if (this.context.getBeanNamesForType(AuthenticationEventPublisher.class).length > 0) {
117+
return this.context.getBean(AuthenticationEventPublisher.class);
118+
}
119+
return this.objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
120+
}
121+
112122
private void applyDefaultConfigurers(HttpSecurity http) throws Exception {
113123
ClassLoader classLoader = this.context.getClassLoader();
114124
List<AbstractHttpConfigurer> defaultHttpConfigurers = SpringFactoriesLoader

config/src/test/java/org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfigurationTests.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@
3434
import org.springframework.core.annotation.Order;
3535
import org.springframework.security.access.annotation.Secured;
3636
import org.springframework.security.access.prepost.PreAuthorize;
37+
import org.springframework.security.authentication.AuthenticationEventPublisher;
3738
import org.springframework.security.authentication.AuthenticationManager;
3839
import org.springframework.security.authentication.AuthenticationProvider;
40+
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
3941
import org.springframework.security.authentication.TestAuthentication;
4042
import org.springframework.security.authentication.TestingAuthenticationToken;
4143
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -51,6 +53,7 @@
5153
import org.springframework.security.config.test.SpringTestContext;
5254
import org.springframework.security.config.test.SpringTestContextExtension;
5355
import org.springframework.security.config.users.AuthenticationTestConfiguration;
56+
import org.springframework.security.core.Authentication;
5457
import org.springframework.security.core.AuthenticationException;
5558
import org.springframework.security.core.authority.AuthorityUtils;
5659
import org.springframework.security.core.context.SecurityContextHolder;
@@ -62,6 +65,7 @@
6265
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
6366
import org.springframework.security.crypto.password.PasswordEncoder;
6467
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
68+
import org.springframework.test.util.ReflectionTestUtils;
6569

6670
import static org.assertj.core.api.Assertions.assertThat;
6771
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -296,6 +300,28 @@ public void getAuthenticationManagerWhenAuthenticationConfigurationSubclassedThe
296300
assertThatExceptionOfType(AlreadyBuiltException.class).isThrownBy(ap::build);
297301
}
298302

303+
@Test
304+
public void configureWhenDefaultsThenDefaultAuthenticationEventPublisher() {
305+
this.spring.register(AuthenticationConfiguration.class, ObjectPostProcessorConfiguration.class).autowire();
306+
AuthenticationManagerBuilder authenticationManagerBuilder = this.spring.getContext()
307+
.getBean(AuthenticationManagerBuilder.class);
308+
AuthenticationEventPublisher eventPublisher = (AuthenticationEventPublisher) ReflectionTestUtils
309+
.getField(authenticationManagerBuilder, "eventPublisher");
310+
assertThat(eventPublisher).isInstanceOf(DefaultAuthenticationEventPublisher.class);
311+
}
312+
313+
@Test
314+
public void configureWhenCustomAuthenticationEventPublisherThenCustomAuthenticationEventPublisher() {
315+
this.spring.register(AuthenticationConfiguration.class, ObjectPostProcessorConfiguration.class,
316+
CustomAuthenticationEventPublisherConfig.class).autowire();
317+
AuthenticationManagerBuilder authenticationManagerBuilder = this.spring.getContext()
318+
.getBean(AuthenticationManagerBuilder.class);
319+
AuthenticationEventPublisher eventPublisher = (AuthenticationEventPublisher) ReflectionTestUtils
320+
.getField(authenticationManagerBuilder, "eventPublisher");
321+
assertThat(eventPublisher)
322+
.isInstanceOf(CustomAuthenticationEventPublisherConfig.MyAuthenticationEventPublisher.class);
323+
}
324+
299325
@EnableGlobalMethodSecurity(securedEnabled = true)
300326
static class GlobalMethodSecurityAutowiredConfig {
301327

@@ -348,6 +374,30 @@ Service service() {
348374

349375
}
350376

377+
@Configuration
378+
static class CustomAuthenticationEventPublisherConfig {
379+
380+
@Bean
381+
AuthenticationEventPublisher eventPublisher() {
382+
return new MyAuthenticationEventPublisher();
383+
}
384+
385+
static class MyAuthenticationEventPublisher implements AuthenticationEventPublisher {
386+
387+
@Override
388+
public void publishAuthenticationSuccess(Authentication authentication) {
389+
390+
}
391+
392+
@Override
393+
public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) {
394+
395+
}
396+
397+
}
398+
399+
}
400+
351401
interface Service {
352402

353403
void run();

config/src/test/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfigurationTests.java

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

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

19+
import java.util.ArrayList;
1920
import java.util.Arrays;
21+
import java.util.List;
2022
import java.util.concurrent.Callable;
2123

2224
import javax.servlet.http.HttpServletRequest;
@@ -32,14 +34,21 @@
3234
import org.springframework.beans.factory.annotation.Autowired;
3335
import org.springframework.context.annotation.Bean;
3436
import org.springframework.context.annotation.Configuration;
37+
import org.springframework.context.event.EventListener;
3538
import org.springframework.core.io.support.SpringFactoriesLoader;
3639
import org.springframework.mock.web.MockHttpSession;
3740
import org.springframework.security.access.AccessDeniedException;
41+
import org.springframework.security.authentication.AuthenticationEventPublisher;
3842
import org.springframework.security.authentication.TestingAuthenticationToken;
43+
import org.springframework.security.authentication.event.AbstractAuthenticationEvent;
44+
import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent;
45+
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
3946
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
4047
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
4148
import org.springframework.security.config.test.SpringTestContext;
4249
import org.springframework.security.config.test.SpringTestContextExtension;
50+
import org.springframework.security.core.Authentication;
51+
import org.springframework.security.core.AuthenticationException;
4352
import org.springframework.security.core.context.SecurityContextHolder;
4453
import org.springframework.security.core.userdetails.User;
4554
import org.springframework.security.core.userdetails.UserDetails;
@@ -56,6 +65,7 @@
5665
import static org.assertj.core.api.Assertions.assertThat;
5766
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
5867
import static org.springframework.security.config.Customizer.withDefaults;
68+
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
5969
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
6070
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
6171
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
@@ -211,6 +221,48 @@ public void loginWhenUsingDefaultsThenDefaultLogoutSuccessPageGenerated() throws
211221
this.mockMvc.perform(get("/login?logout")).andExpect(status().isOk());
212222
}
213223

224+
@Test
225+
public void loginWhenUsingDefaultThenAuthenticationEventPublished() throws Exception {
226+
this.spring
227+
.register(SecurityEnabledConfig.class, UserDetailsConfig.class, AuthenticationEventListenerConfig.class)
228+
.autowire();
229+
AuthenticationEventListenerConfig.clearEvents();
230+
this.mockMvc.perform(formLogin()).andExpect(status().is3xxRedirection());
231+
assertThat(AuthenticationEventListenerConfig.EVENTS).isNotEmpty();
232+
assertThat(AuthenticationEventListenerConfig.EVENTS).hasSize(1);
233+
}
234+
235+
@Test
236+
public void loginWhenUsingDefaultAndNoUserDetailsServiceThenAuthenticationEventPublished() throws Exception {
237+
this.spring
238+
.register(SecurityEnabledConfig.class, UserDetailsConfig.class, AuthenticationEventListenerConfig.class)
239+
.autowire();
240+
AuthenticationEventListenerConfig.clearEvents();
241+
this.mockMvc.perform(formLogin()).andExpect(status().is3xxRedirection());
242+
assertThat(AuthenticationEventListenerConfig.EVENTS).isNotEmpty();
243+
assertThat(AuthenticationEventListenerConfig.EVENTS).hasSize(1);
244+
}
245+
246+
@Test
247+
public void loginWhenUsingCustomAuthenticationEventPublisherThenAuthenticationEventPublished() throws Exception {
248+
this.spring.register(SecurityEnabledConfig.class, UserDetailsConfig.class,
249+
CustomAuthenticationEventPublisherConfig.class).autowire();
250+
CustomAuthenticationEventPublisherConfig.clearEvents();
251+
this.mockMvc.perform(formLogin()).andExpect(status().is3xxRedirection());
252+
assertThat(CustomAuthenticationEventPublisherConfig.EVENTS).isNotEmpty();
253+
assertThat(CustomAuthenticationEventPublisherConfig.EVENTS).hasSize(1);
254+
}
255+
256+
@Test
257+
public void loginWhenUsingCustomAuthenticationEventPublisherAndNoUserDetailsServiceThenAuthenticationEventPublished()
258+
throws Exception {
259+
this.spring.register(SecurityEnabledConfig.class, CustomAuthenticationEventPublisherConfig.class).autowire();
260+
CustomAuthenticationEventPublisherConfig.clearEvents();
261+
this.mockMvc.perform(formLogin()).andExpect(status().is3xxRedirection());
262+
assertThat(CustomAuthenticationEventPublisherConfig.EVENTS).isNotEmpty();
263+
assertThat(CustomAuthenticationEventPublisherConfig.EVENTS).hasSize(1);
264+
}
265+
214266
@Test
215267
public void configureWhenAuthorizeHttpRequestsBeforeAuthorizeRequestThenException() {
216268
assertThatExceptionOfType(BeanCreationException.class)
@@ -348,6 +400,55 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
348400

349401
}
350402

403+
@Configuration
404+
static class CustomAuthenticationEventPublisherConfig {
405+
406+
static List<Authentication> EVENTS = new ArrayList<>();
407+
408+
static void clearEvents() {
409+
EVENTS.clear();
410+
}
411+
412+
@Bean
413+
AuthenticationEventPublisher publisher() {
414+
return new AuthenticationEventPublisher() {
415+
416+
@Override
417+
public void publishAuthenticationSuccess(Authentication authentication) {
418+
EVENTS.add(authentication);
419+
}
420+
421+
@Override
422+
public void publishAuthenticationFailure(AuthenticationException exception,
423+
Authentication authentication) {
424+
EVENTS.add(authentication);
425+
}
426+
};
427+
}
428+
429+
}
430+
431+
@Configuration
432+
static class AuthenticationEventListenerConfig {
433+
434+
static List<AbstractAuthenticationEvent> EVENTS = new ArrayList<>();
435+
436+
static void clearEvents() {
437+
EVENTS.clear();
438+
}
439+
440+
@EventListener
441+
void onAuthenticationSuccessEvent(AuthenticationSuccessEvent event) {
442+
EVENTS.add(event);
443+
}
444+
445+
@EventListener
446+
void onAuthenticationFailureEvent(AbstractAuthenticationFailureEvent event) {
447+
EVENTS.add(event);
448+
}
449+
450+
}
451+
351452
@RestController
352453
static class BaseController {
353454

0 commit comments

Comments
 (0)