diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurer.java index d6d352995d..c1d579fc94 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 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. @@ -18,6 +18,8 @@ package org.springframework.security.config.annotation.web.configurers; import java.util.UUID; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.context.ApplicationContext; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.RememberMeAuthenticationProvider; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; @@ -403,7 +405,7 @@ public final class RememberMeConfigurer> */ private UserDetailsService getUserDetailsService(H http) { if (this.userDetailsService == null) { - this.userDetailsService = http.getSharedObject(UserDetailsService.class); + this.userDetailsService = getSharedOrBean(http, UserDetailsService.class); } Assert.state(this.userDetailsService != null, () -> "userDetailsService cannot be null. Invoke " + RememberMeConfigurer.class.getSimpleName() @@ -431,4 +433,25 @@ public final class RememberMeConfigurer> return this.key; } + private C getSharedOrBean(H http, Class type) { + C shared = http.getSharedObject(type); + if (shared != null) { + return shared; + } + return getBeanOrNull(type); + } + + private T getBeanOrNull(Class type) { + ApplicationContext context = getBuilder().getSharedObject(ApplicationContext.class); + if (context == null) { + return null; + } + try { + return context.getBean(type); + } + catch (NoSuchBeanDefinitionException ex) { + return null; + } + } + } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java index 6e85353be0..56d0a41330 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java @@ -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. @@ -41,6 +41,7 @@ import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.RememberMeServices; import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter; import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices; @@ -116,6 +117,20 @@ public class RememberMeConfigurerTests { verify(DuplicateDoesNotOverrideConfig.userDetailsService).loadUserByUsername("user"); } + @Test + public void rememberMeWhenUserDetailsServiceNotConfiguredThenUsesBean() throws Exception { + this.spring.register(UserDetailsServiceBeanConfig.class).autowire(); + MvcResult mvcResult = this.mvc.perform(post("/login").with(csrf()).param("username", "user") + .param("password", "password").param("remember-me", "true")).andReturn(); + Cookie rememberMeCookie = mvcResult.getResponse().getCookie("remember-me"); + // @formatter:off + MockHttpServletRequestBuilder request = get("/abc").cookie(rememberMeCookie); + SecurityMockMvcResultMatchers.AuthenticatedMatcher remembermeAuthentication = authenticated() + .withAuthentication((auth) -> assertThat(auth).isInstanceOf(RememberMeAuthenticationToken.class)); + // @formatter:on + this.mvc.perform(request).andExpect(remembermeAuthentication); + } + @Test public void loginWhenRememberMeTrueThenRespondsWithRememberMeCookie() throws Exception { this.spring.register(RememberMeConfig.class).autowire(); @@ -369,6 +384,26 @@ public class RememberMeConfigurerTests { } + @EnableWebSecurity + static class UserDetailsServiceBeanConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // @formatter:off + http + .formLogin(withDefaults()) + .rememberMe(withDefaults()); + // @formatter:on + return http.build(); + } + + @Bean + UserDetailsService customUserDetailsService() { + return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); + } + + } + @EnableWebSecurity static class RememberMeConfig extends WebSecurityConfigurerAdapter {