Skip to content

Commit a8d6c1d

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 7c7f938 commit a8d6c1d

File tree

4 files changed

+173
-5
lines changed

4 files changed

+173
-5
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: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 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.
@@ -24,7 +24,9 @@
2424
import org.springframework.context.annotation.Bean;
2525
import org.springframework.context.annotation.Configuration;
2626
import org.springframework.context.annotation.Scope;
27+
import org.springframework.security.authentication.AuthenticationEventPublisher;
2728
import org.springframework.security.authentication.AuthenticationManager;
29+
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
2830
import org.springframework.security.config.annotation.ObjectPostProcessor;
2931
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
3032
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
@@ -82,6 +84,7 @@ HttpSecurity httpSecurity() throws Exception {
8284
AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(
8385
this.objectPostProcessor, passwordEncoder);
8486
authenticationBuilder.parentAuthenticationManager(authenticationManager());
87+
authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher());
8588
HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
8689
// @formatter:off
8790
http
@@ -105,6 +108,13 @@ private AuthenticationManager authenticationManager() throws Exception {
105108
: this.authenticationConfiguration.getAuthenticationManager();
106109
}
107110

111+
private AuthenticationEventPublisher getAuthenticationEventPublisher() {
112+
if (this.context.getBeanNamesForType(AuthenticationEventPublisher.class).length > 0) {
113+
return this.context.getBean(AuthenticationEventPublisher.class);
114+
}
115+
return this.objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
116+
}
117+
108118
private Map<Class<?>, Object> createSharedObjects() {
109119
Map<Class<?>, Object> sharedObjects = new HashMap<>();
110120
sharedObjects.put(ApplicationContext.class, this.context);

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;
@@ -294,6 +298,28 @@ public void getAuthenticationManagerWhenAuthenticationConfigurationSubclassedThe
294298
assertThatExceptionOfType(AlreadyBuiltException.class).isThrownBy(ap::build);
295299
}
296300

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

@@ -346,6 +372,30 @@ Service service() {
346372

347373
}
348374

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

351401
void run();

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

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 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,6 +16,8 @@
1616

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

19+
import java.util.ArrayList;
20+
import java.util.List;
1921
import java.util.concurrent.Callable;
2022

2123
import javax.servlet.http.HttpServletRequest;
@@ -27,12 +29,19 @@
2729
import org.springframework.beans.factory.annotation.Autowired;
2830
import org.springframework.context.annotation.Bean;
2931
import org.springframework.context.annotation.Configuration;
32+
import org.springframework.context.event.EventListener;
3033
import org.springframework.mock.web.MockHttpSession;
3134
import org.springframework.security.access.AccessDeniedException;
35+
import org.springframework.security.authentication.AuthenticationEventPublisher;
3236
import org.springframework.security.authentication.TestingAuthenticationToken;
37+
import org.springframework.security.authentication.event.AbstractAuthenticationEvent;
38+
import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent;
39+
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
3340
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
3441
import org.springframework.security.config.test.SpringTestContext;
3542
import org.springframework.security.config.test.SpringTestContextExtension;
43+
import org.springframework.security.core.Authentication;
44+
import org.springframework.security.core.AuthenticationException;
3645
import org.springframework.security.core.context.SecurityContextHolder;
3746
import org.springframework.security.core.userdetails.User;
3847
import org.springframework.security.core.userdetails.UserDetails;
@@ -48,6 +57,7 @@
4857

4958
import static org.assertj.core.api.Assertions.assertThat;
5059
import static org.springframework.security.config.Customizer.withDefaults;
60+
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
5161
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
5262
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
5363
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
@@ -200,6 +210,48 @@ public void loginWhenUsingDefaultsThenDefaultLogoutSuccessPageGenerated() throws
200210
this.mockMvc.perform(get("/login?logout")).andExpect(status().isOk());
201211
}
202212

213+
@Test
214+
public void loginWhenUsingDefaultThenAuthenticationEventPublished() throws Exception {
215+
this.spring
216+
.register(SecurityEnabledConfig.class, UserDetailsConfig.class, AuthenticationEventListenerConfig.class)
217+
.autowire();
218+
AuthenticationEventListenerConfig.clearEvents();
219+
this.mockMvc.perform(formLogin()).andExpect(status().is3xxRedirection());
220+
assertThat(AuthenticationEventListenerConfig.EVENTS).isNotEmpty();
221+
assertThat(AuthenticationEventListenerConfig.EVENTS).hasSize(1);
222+
}
223+
224+
@Test
225+
public void loginWhenUsingDefaultAndNoUserDetailsServiceThenAuthenticationEventPublished() 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 loginWhenUsingCustomAuthenticationEventPublisherThenAuthenticationEventPublished() throws Exception {
237+
this.spring.register(SecurityEnabledConfig.class, UserDetailsConfig.class,
238+
CustomAuthenticationEventPublisherConfig.class).autowire();
239+
CustomAuthenticationEventPublisherConfig.clearEvents();
240+
this.mockMvc.perform(formLogin()).andExpect(status().is3xxRedirection());
241+
assertThat(CustomAuthenticationEventPublisherConfig.EVENTS).isNotEmpty();
242+
assertThat(CustomAuthenticationEventPublisherConfig.EVENTS).hasSize(1);
243+
}
244+
245+
@Test
246+
public void loginWhenUsingCustomAuthenticationEventPublisherAndNoUserDetailsServiceThenAuthenticationEventPublished()
247+
throws Exception {
248+
this.spring.register(SecurityEnabledConfig.class, CustomAuthenticationEventPublisherConfig.class).autowire();
249+
CustomAuthenticationEventPublisherConfig.clearEvents();
250+
this.mockMvc.perform(formLogin()).andExpect(status().is3xxRedirection());
251+
assertThat(CustomAuthenticationEventPublisherConfig.EVENTS).isNotEmpty();
252+
assertThat(CustomAuthenticationEventPublisherConfig.EVENTS).hasSize(1);
253+
}
254+
203255
@RestController
204256
static class NameController {
205257

@@ -270,6 +322,55 @@ UserDetailsService userDetailsService() {
270322

271323
}
272324

325+
@Configuration
326+
static class CustomAuthenticationEventPublisherConfig {
327+
328+
static List<Authentication> EVENTS = new ArrayList<>();
329+
330+
static void clearEvents() {
331+
EVENTS.clear();
332+
}
333+
334+
@Bean
335+
AuthenticationEventPublisher publisher() {
336+
return new AuthenticationEventPublisher() {
337+
338+
@Override
339+
public void publishAuthenticationSuccess(Authentication authentication) {
340+
EVENTS.add(authentication);
341+
}
342+
343+
@Override
344+
public void publishAuthenticationFailure(AuthenticationException exception,
345+
Authentication authentication) {
346+
EVENTS.add(authentication);
347+
}
348+
};
349+
}
350+
351+
}
352+
353+
@Configuration
354+
static class AuthenticationEventListenerConfig {
355+
356+
static List<AbstractAuthenticationEvent> EVENTS = new ArrayList<>();
357+
358+
static void clearEvents() {
359+
EVENTS.clear();
360+
}
361+
362+
@EventListener
363+
void onAuthenticationSuccessEvent(AuthenticationSuccessEvent event) {
364+
EVENTS.add(event);
365+
}
366+
367+
@EventListener
368+
void onAuthenticationFailureEvent(AbstractAuthenticationFailureEvent event) {
369+
EVENTS.add(event);
370+
}
371+
372+
}
373+
273374
@RestController
274375
static class BaseController {
275376

0 commit comments

Comments
 (0)