Skip to content

Commit

Permalink
Consistently set AuthenticationEventPublisher in AuthenticationManage…
Browse files Browse the repository at this point in the history
…rBuilder

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
  • Loading branch information
marcusdacoregio committed Aug 19, 2022
1 parent 7c7f938 commit a8d6c1d
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 5 deletions.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
Expand Down Expand Up @@ -39,6 +39,7 @@
import org.springframework.core.log.LogMessage;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
Expand Down Expand Up @@ -79,8 +80,7 @@ public class AuthenticationConfiguration {
public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor,
ApplicationContext context) {
LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context,
AuthenticationEventPublisher.class);
AuthenticationEventPublisher authenticationEventPublisher = getAuthenticationEventPublisher(context);
DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(
objectPostProcessor, defaultPasswordEncoder);
if (authenticationEventPublisher != null) {
Expand Down Expand Up @@ -142,6 +142,13 @@ public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcess
this.objectPostProcessor = objectPostProcessor;
}

private AuthenticationEventPublisher getAuthenticationEventPublisher(ApplicationContext context) {
if (context.getBeanNamesForType(AuthenticationEventPublisher.class).length > 0) {
return context.getBean(AuthenticationEventPublisher.class);
}
return this.objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
}

@SuppressWarnings("unchecked")
private <T> T lazyBean(Class<T> interfaceName) {
LazyInitTargetSource lazyTargetSource = new LazyInitTargetSource();
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 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.
Expand All @@ -24,7 +24,9 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
Expand Down Expand Up @@ -82,6 +84,7 @@ HttpSecurity httpSecurity() throws Exception {
AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(
this.objectPostProcessor, passwordEncoder);
authenticationBuilder.parentAuthenticationManager(authenticationManager());
authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher());
HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
// @formatter:off
http
Expand All @@ -105,6 +108,13 @@ private AuthenticationManager authenticationManager() throws Exception {
: this.authenticationConfiguration.getAuthenticationManager();
}

private AuthenticationEventPublisher getAuthenticationEventPublisher() {
if (this.context.getBeanNamesForType(AuthenticationEventPublisher.class).length > 0) {
return this.context.getBean(AuthenticationEventPublisher.class);
}
return this.objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
}

private Map<Class<?>, Object> createSharedObjects() {
Map<Class<?>, Object> sharedObjects = new HashMap<>();
sharedObjects.put(ApplicationContext.class, this.context);
Expand Down
Expand Up @@ -34,8 +34,10 @@
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;
Expand All @@ -51,6 +53,7 @@
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;
Expand All @@ -62,6 +65,7 @@
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;
Expand Down Expand Up @@ -294,6 +298,28 @@ public void getAuthenticationManagerWhenAuthenticationConfigurationSubclassedThe
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 {

Expand Down Expand Up @@ -346,6 +372,30 @@ Service service() {

}

@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();
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2022 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.
Expand All @@ -16,6 +16,8 @@

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

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;

import javax.servlet.http.HttpServletRequest;
Expand All @@ -27,12 +29,19 @@
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.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.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;
Expand All @@ -48,6 +57,7 @@

import static org.assertj.core.api.Assertions.assertThat;
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;
Expand Down Expand Up @@ -200,6 +210,48 @@ public void loginWhenUsingDefaultsThenDefaultLogoutSuccessPageGenerated() throws
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);
}

@RestController
static class NameController {

Expand Down Expand Up @@ -270,6 +322,55 @@ UserDetailsService userDetailsService() {

}

@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 {

Expand Down

0 comments on commit a8d6c1d

Please sign in to comment.