diff --git a/docs/manual/src/docs/asciidoc/_includes/test.adoc b/docs/manual/src/docs/asciidoc/_includes/test.adoc index e2e95ecd8c..0faf82b674 100644 --- a/docs/manual/src/docs/asciidoc/_includes/test.adoc +++ b/docs/manual/src/docs/asciidoc/_includes/test.adoc @@ -204,6 +204,19 @@ public void getMessageWithUserDetailsCustomUsername() { } ---- +We can also provide an explicit bean name to look up the `UserDetailsService`. +For example, this test would look up the username of "customUsername" using the `UserDetailsService` with the bean name "myUserDetailsService". + +[source,java] +---- +@T@Test +@WithUserDetails(value="customUsername", userDetailsServiceBeanName="myUserDetailsService") +public void getMessageWithUserDetailsServiceBeanName() { + String message = messageService.getMessage(); + ... +} +---- + Like `@WithMockUser` we can also place our annotation at the class level so that every test uses the same user. However unlike `@WithMockUser`, `@WithUserDetails` requires the user to exist. diff --git a/test/src/main/java/org/springframework/security/test/context/support/WithUserDetails.java b/test/src/main/java/org/springframework/security/test/context/support/WithUserDetails.java index 32e5e42e04..6781afca86 100644 --- a/test/src/main/java/org/springframework/security/test/context/support/WithUserDetails.java +++ b/test/src/main/java/org/springframework/security/test/context/support/WithUserDetails.java @@ -60,4 +60,14 @@ public @interface WithUserDetails { * @return */ String value() default "user"; + + /** + * The bean name for the {@link UserDetailsService} to use. If this is not + * provided, then the lookup is done by type and expects only a single + * {@link UserDetailsService} bean to be exposed. + * + * @return the bean name for the {@link UserDetailsService} to use. + * @since 4.1 + */ + String userDetailsServiceBeanName() default ""; } \ No newline at end of file diff --git a/test/src/main/java/org/springframework/security/test/context/support/WithUserDetailsSecurityContextFactory.java b/test/src/main/java/org/springframework/security/test/context/support/WithUserDetailsSecurityContextFactory.java index 59f4e44179..971182b142 100644 --- a/test/src/main/java/org/springframework/security/test/context/support/WithUserDetailsSecurityContextFactory.java +++ b/test/src/main/java/org/springframework/security/test/context/support/WithUserDetailsSecurityContextFactory.java @@ -15,6 +15,7 @@ */ package org.springframework.security.test.context.support; +import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -23,6 +24,7 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; /** * A {@link WithUserDetailsSecurityContextFactory} that works with {@link WithUserDetails} @@ -37,14 +39,18 @@ import org.springframework.util.Assert; final class WithUserDetailsSecurityContextFactory implements WithSecurityContextFactory { - private UserDetailsService userDetailsService; + private BeanFactory beans; @Autowired - public WithUserDetailsSecurityContextFactory(UserDetailsService userDetailsService) { - this.userDetailsService = userDetailsService; + public WithUserDetailsSecurityContextFactory(BeanFactory beans) { + this.beans = beans; } public SecurityContext createSecurityContext(WithUserDetails withUser) { + String beanName = withUser.userDetailsServiceBeanName(); + UserDetailsService userDetailsService = StringUtils.hasLength(beanName) + ? this.beans.getBean(beanName, UserDetailsService.class) + : this.beans.getBean(UserDetailsService.class); String username = withUser.value(); Assert.hasLength(username, "value() must be non empty String"); UserDetails principal = userDetailsService.loadUserByUsername(username); diff --git a/test/src/test/java/org/springframework/security/test/context/showcase/WithUserDetailsTests.java b/test/src/test/java/org/springframework/security/test/context/showcase/WithUserDetailsTests.java index ce36f9c64f..f4ba6ee53f 100644 --- a/test/src/test/java/org/springframework/security/test/context/showcase/WithUserDetailsTests.java +++ b/test/src/test/java/org/springframework/security/test/context/showcase/WithUserDetailsTests.java @@ -66,6 +66,14 @@ public class WithUserDetailsTests { assertThat(getPrincipal()).isInstanceOf(CustomUserDetails.class); } + @Test + @WithUserDetails(value="customUsername", userDetailsServiceBeanName="myUserDetailsService") + public void getMessageWithUserDetailsServiceBeanName() { + String message = messageService.getMessage(); + assertThat(message).contains("customUsername"); + assertThat(getPrincipal()).isInstanceOf(CustomUserDetails.class); + } + @EnableGlobalMethodSecurity(prePostEnabled = true) @ComponentScan(basePackageClasses = HelloMessageService.class) static class Config { @@ -73,12 +81,12 @@ public class WithUserDetailsTests { @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth - .userDetailsService(userDetailsService()); + .userDetailsService(myUserDetailsService()); } // @formatter:on @Bean - public UserDetailsService userDetailsService() { + public UserDetailsService myUserDetailsService() { return new CustomUserDetailsService(); } } diff --git a/test/src/test/java/org/springframework/security/test/context/support/WithUserDetailsSecurityContextFactoryTests.java b/test/src/test/java/org/springframework/security/test/context/support/WithUserDetailsSecurityContextFactoryTests.java index 4f6846ec88..626148606a 100644 --- a/test/src/test/java/org/springframework/security/test/context/support/WithUserDetailsSecurityContextFactoryTests.java +++ b/test/src/test/java/org/springframework/security/test/context/support/WithUserDetailsSecurityContextFactoryTests.java @@ -16,13 +16,14 @@ package org.springframework.security.test.context.support; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.beans.factory.BeanFactory; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.userdetails.UserDetails; @@ -35,6 +36,8 @@ public class WithUserDetailsSecurityContextFactoryTests { private UserDetailsService userDetailsService; @Mock private UserDetails userDetails; + @Mock + private BeanFactory beans; @Mock private WithUserDetails withUserDetails; @@ -43,7 +46,8 @@ public class WithUserDetailsSecurityContextFactoryTests { @Before public void setup() { - factory = new WithUserDetailsSecurityContextFactory(userDetailsService); + when(beans.getBean(UserDetailsService.class)).thenReturn(userDetailsService); + factory = new WithUserDetailsSecurityContextFactory(beans); } @Test(expected = IllegalArgumentException.class) @@ -67,5 +71,25 @@ public class WithUserDetailsSecurityContextFactoryTests { assertThat(context.getAuthentication()).isInstanceOf( UsernamePasswordAuthenticationToken.class); assertThat(context.getAuthentication().getPrincipal()).isEqualTo(userDetails); + verify(beans).getBean(UserDetailsService.class); + verifyNoMoreInteractions(beans); + } + + // gh-3346 + @Test + public void createSecurityContextWithUserDetailsServiceName() { + String beanName = "secondUserDetailsServiceBean"; + String username = "user"; + when(withUserDetails.value()).thenReturn(username); + when(withUserDetails.userDetailsServiceBeanName()).thenReturn(beanName); + when(userDetailsService.loadUserByUsername(username)).thenReturn(userDetails); + when(beans.getBean(beanName, UserDetailsService.class)).thenReturn(userDetailsService); + + SecurityContext context = factory.createSecurityContext(withUserDetails); + assertThat(context.getAuthentication()).isInstanceOf( + UsernamePasswordAuthenticationToken.class); + assertThat(context.getAuthentication().getPrincipal()).isEqualTo(userDetails); + verify(beans).getBean(beanName, UserDetailsService.class); + verifyNoMoreInteractions(beans); } } \ No newline at end of file diff --git a/test/src/test/java/org/springframework/security/test/web/servlet/showcase/secured/WithUserDetailsAuthenticationTests.java b/test/src/test/java/org/springframework/security/test/web/servlet/showcase/secured/WithUserDetailsAuthenticationTests.java index a1a3d39027..d912cbe963 100644 --- a/test/src/test/java/org/springframework/security/test/web/servlet/showcase/secured/WithUserDetailsAuthenticationTests.java +++ b/test/src/test/java/org/springframework/security/test/web/servlet/showcase/secured/WithUserDetailsAuthenticationTests.java @@ -15,16 +15,20 @@ */ package org.springframework.security.test.web.servlet.showcase.secured; +import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.test.context.support.WithUserDetails; import org.springframework.test.context.ContextConfiguration; @@ -35,11 +39,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; -import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = WithUserDetailsAuthenticationTests.Config.class) @WebAppConfiguration