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
This commit is contained in:
Marcus Da Coregio
2022-08-19 09:30:46 -03:00
parent 53a3ff8932
commit c7912c551b
4 changed files with 171 additions and 3 deletions

View File

@@ -34,8 +34,10 @@ import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -51,6 +53,7 @@ import org.springframework.security.config.annotation.web.servlet.configuration.
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.config.users.AuthenticationTestConfiguration;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -62,6 +65,7 @@ import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -296,6 +300,28 @@ public class AuthenticationConfigurationTests {
assertThatExceptionOfType(AlreadyBuiltException.class).isThrownBy(ap::build);
}
@Test
public void configureWhenDefaultsThenDefaultAuthenticationEventPublisher() {
this.spring.register(AuthenticationConfiguration.class, ObjectPostProcessorConfiguration.class).autowire();
AuthenticationManagerBuilder authenticationManagerBuilder = this.spring.getContext()
.getBean(AuthenticationManagerBuilder.class);
AuthenticationEventPublisher eventPublisher = (AuthenticationEventPublisher) ReflectionTestUtils
.getField(authenticationManagerBuilder, "eventPublisher");
assertThat(eventPublisher).isInstanceOf(DefaultAuthenticationEventPublisher.class);
}
@Test
public void configureWhenCustomAuthenticationEventPublisherThenCustomAuthenticationEventPublisher() {
this.spring.register(AuthenticationConfiguration.class, ObjectPostProcessorConfiguration.class,
CustomAuthenticationEventPublisherConfig.class).autowire();
AuthenticationManagerBuilder authenticationManagerBuilder = this.spring.getContext()
.getBean(AuthenticationManagerBuilder.class);
AuthenticationEventPublisher eventPublisher = (AuthenticationEventPublisher) ReflectionTestUtils
.getField(authenticationManagerBuilder, "eventPublisher");
assertThat(eventPublisher)
.isInstanceOf(CustomAuthenticationEventPublisherConfig.MyAuthenticationEventPublisher.class);
}
@EnableGlobalMethodSecurity(securedEnabled = true)
static class GlobalMethodSecurityAutowiredConfig {
@@ -348,6 +374,30 @@ public class AuthenticationConfigurationTests {
}
@Configuration
static class CustomAuthenticationEventPublisherConfig {
@Bean
AuthenticationEventPublisher eventPublisher() {
return new MyAuthenticationEventPublisher();
}
static class MyAuthenticationEventPublisher implements AuthenticationEventPublisher {
@Override
public void publishAuthenticationSuccess(Authentication authentication) {
}
@Override
public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) {
}
}
}
interface Service {
void run();

View File

@@ -16,7 +16,9 @@
package org.springframework.security.config.annotation.web.configuration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import javax.servlet.http.HttpServletRequest;
@@ -32,14 +34,21 @@ import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.event.AbstractAuthenticationEvent;
import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
@@ -56,6 +65,7 @@ import org.springframework.web.bind.annotation.RestController;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
@@ -211,6 +221,48 @@ public class HttpSecurityConfigurationTests {
this.mockMvc.perform(get("/login?logout")).andExpect(status().isOk());
}
@Test
public void loginWhenUsingDefaultThenAuthenticationEventPublished() throws Exception {
this.spring
.register(SecurityEnabledConfig.class, UserDetailsConfig.class, AuthenticationEventListenerConfig.class)
.autowire();
AuthenticationEventListenerConfig.clearEvents();
this.mockMvc.perform(formLogin()).andExpect(status().is3xxRedirection());
assertThat(AuthenticationEventListenerConfig.EVENTS).isNotEmpty();
assertThat(AuthenticationEventListenerConfig.EVENTS).hasSize(1);
}
@Test
public void loginWhenUsingDefaultAndNoUserDetailsServiceThenAuthenticationEventPublished() throws Exception {
this.spring
.register(SecurityEnabledConfig.class, UserDetailsConfig.class, AuthenticationEventListenerConfig.class)
.autowire();
AuthenticationEventListenerConfig.clearEvents();
this.mockMvc.perform(formLogin()).andExpect(status().is3xxRedirection());
assertThat(AuthenticationEventListenerConfig.EVENTS).isNotEmpty();
assertThat(AuthenticationEventListenerConfig.EVENTS).hasSize(1);
}
@Test
public void loginWhenUsingCustomAuthenticationEventPublisherThenAuthenticationEventPublished() throws Exception {
this.spring.register(SecurityEnabledConfig.class, UserDetailsConfig.class,
CustomAuthenticationEventPublisherConfig.class).autowire();
CustomAuthenticationEventPublisherConfig.clearEvents();
this.mockMvc.perform(formLogin()).andExpect(status().is3xxRedirection());
assertThat(CustomAuthenticationEventPublisherConfig.EVENTS).isNotEmpty();
assertThat(CustomAuthenticationEventPublisherConfig.EVENTS).hasSize(1);
}
@Test
public void loginWhenUsingCustomAuthenticationEventPublisherAndNoUserDetailsServiceThenAuthenticationEventPublished()
throws Exception {
this.spring.register(SecurityEnabledConfig.class, CustomAuthenticationEventPublisherConfig.class).autowire();
CustomAuthenticationEventPublisherConfig.clearEvents();
this.mockMvc.perform(formLogin()).andExpect(status().is3xxRedirection());
assertThat(CustomAuthenticationEventPublisherConfig.EVENTS).isNotEmpty();
assertThat(CustomAuthenticationEventPublisherConfig.EVENTS).hasSize(1);
}
@Test
public void configureWhenAuthorizeHttpRequestsBeforeAuthorizeRequestThenException() {
assertThatExceptionOfType(BeanCreationException.class)
@@ -348,6 +400,55 @@ public class HttpSecurityConfigurationTests {
}
@Configuration
static class CustomAuthenticationEventPublisherConfig {
static List<Authentication> EVENTS = new ArrayList<>();
static void clearEvents() {
EVENTS.clear();
}
@Bean
AuthenticationEventPublisher publisher() {
return new AuthenticationEventPublisher() {
@Override
public void publishAuthenticationSuccess(Authentication authentication) {
EVENTS.add(authentication);
}
@Override
public void publishAuthenticationFailure(AuthenticationException exception,
Authentication authentication) {
EVENTS.add(authentication);
}
};
}
}
@Configuration
static class AuthenticationEventListenerConfig {
static List<AbstractAuthenticationEvent> EVENTS = new ArrayList<>();
static void clearEvents() {
EVENTS.clear();
}
@EventListener
void onAuthenticationSuccessEvent(AuthenticationSuccessEvent event) {
EVENTS.add(event);
}
@EventListener
void onAuthenticationFailureEvent(AbstractAuthenticationFailureEvent event) {
EVENTS.add(event);
}
}
@RestController
static class BaseController {