mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-29 15:22:15 +00:00
Use SessionAuthenticationStrategy for Remember-Me authentication
Closes gh-2253
This commit is contained in:
parent
d37d41c130
commit
7f537241e7
@ -33,6 +33,7 @@ import org.springframework.security.web.authentication.rememberme.PersistentToke
|
|||||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||||
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
|
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter;
|
||||||
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
|
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
|
||||||
|
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
|
||||||
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
|
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
|
||||||
import org.springframework.security.web.context.SecurityContextRepository;
|
import org.springframework.security.web.context.SecurityContextRepository;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
@ -296,6 +297,13 @@ public final class RememberMeConfigurer<H extends HttpSecurityBuilder<H>>
|
|||||||
rememberMeFilter.setSecurityContextRepository(securityContextRepository);
|
rememberMeFilter.setSecurityContextRepository(securityContextRepository);
|
||||||
}
|
}
|
||||||
rememberMeFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
|
rememberMeFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
|
||||||
|
|
||||||
|
SessionAuthenticationStrategy sessionAuthenticationStrategy = http
|
||||||
|
.getSharedObject(SessionAuthenticationStrategy.class);
|
||||||
|
if (sessionAuthenticationStrategy != null) {
|
||||||
|
rememberMeFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
|
||||||
|
}
|
||||||
|
|
||||||
rememberMeFilter = postProcess(rememberMeFilter);
|
rememberMeFilter = postProcess(rememberMeFilter);
|
||||||
http.addFilter(rememberMeFilter);
|
http.addFilter(rememberMeFilter);
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,7 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilde
|
|||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
|
import static org.hamcrest.Matchers.startsWith;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.BDDMockito.given;
|
import static org.mockito.BDDMockito.given;
|
||||||
@ -74,6 +75,7 @@ import static org.springframework.security.test.web.servlet.request.SecurityMock
|
|||||||
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
|
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
|
||||||
|
|
||||||
@ -334,6 +336,27 @@ public class RememberMeConfigurerTests {
|
|||||||
verify(repository).saveContext(any(), any(), any());
|
verify(repository).saveContext(any(), any(), any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rememberMeExpiresSessionWhenSessionManagementMaximumSessionsExceeds() throws Exception {
|
||||||
|
this.spring.register(RememberMeMaximumSessionsConfig.class).autowire();
|
||||||
|
|
||||||
|
MockHttpServletRequestBuilder loginRequest = post("/login").with(csrf())
|
||||||
|
.param("username", "user")
|
||||||
|
.param("password", "password")
|
||||||
|
.param("remember-me", "true");
|
||||||
|
MvcResult mvcResult = this.mvc.perform(loginRequest).andReturn();
|
||||||
|
Cookie rememberMeCookie = mvcResult.getResponse().getCookie("remember-me");
|
||||||
|
HttpSession session = mvcResult.getRequest().getSession();
|
||||||
|
|
||||||
|
MockHttpServletRequestBuilder exceedsMaximumSessionsRequest = get("/abc").cookie(rememberMeCookie);
|
||||||
|
this.mvc.perform(exceedsMaximumSessionsRequest);
|
||||||
|
|
||||||
|
MockHttpServletRequestBuilder sessionExpiredRequest = get("/abc").cookie(rememberMeCookie)
|
||||||
|
.session((MockHttpSession) session);
|
||||||
|
this.mvc.perform(sessionExpiredRequest)
|
||||||
|
.andExpect(content().string(startsWith("This session has been expired")));
|
||||||
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
static class NullUserDetailsConfig {
|
static class NullUserDetailsConfig {
|
||||||
@ -617,6 +640,35 @@ public class RememberMeConfigurerTests {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
static class RememberMeMaximumSessionsConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
// @formatter:off
|
||||||
|
http
|
||||||
|
.authorizeRequests((authorizeRequests) ->
|
||||||
|
authorizeRequests
|
||||||
|
.anyRequest().hasRole("USER")
|
||||||
|
)
|
||||||
|
.sessionManagement((sessionManagement) ->
|
||||||
|
sessionManagement
|
||||||
|
.maximumSessions(1)
|
||||||
|
)
|
||||||
|
.formLogin(withDefaults())
|
||||||
|
.rememberMe(withDefaults());
|
||||||
|
return http.build();
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
UserDetailsService userDetailsService() {
|
||||||
|
return new InMemoryUserDetailsManager(PasswordEncodedUser.user());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
static class SecurityContextRepositoryConfig {
|
static class SecurityContextRepositoryConfig {
|
||||||
|
@ -37,6 +37,8 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
|||||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||||
import org.springframework.security.web.authentication.RememberMeServices;
|
import org.springframework.security.web.authentication.RememberMeServices;
|
||||||
|
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
|
||||||
|
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
|
||||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
||||||
import org.springframework.security.web.context.SecurityContextRepository;
|
import org.springframework.security.web.context.SecurityContextRepository;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
@ -81,6 +83,8 @@ public class RememberMeAuthenticationFilter extends GenericFilterBean implements
|
|||||||
|
|
||||||
private SecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository();
|
private SecurityContextRepository securityContextRepository = new HttpSessionSecurityContextRepository();
|
||||||
|
|
||||||
|
private SessionAuthenticationStrategy sessionStrategy = new NullAuthenticatedSessionStrategy();
|
||||||
|
|
||||||
public RememberMeAuthenticationFilter(AuthenticationManager authenticationManager,
|
public RememberMeAuthenticationFilter(AuthenticationManager authenticationManager,
|
||||||
RememberMeServices rememberMeServices) {
|
RememberMeServices rememberMeServices) {
|
||||||
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
|
Assert.notNull(authenticationManager, "authenticationManager cannot be null");
|
||||||
@ -115,6 +119,7 @@ public class RememberMeAuthenticationFilter extends GenericFilterBean implements
|
|||||||
// Attempt authentication via AuthenticationManager
|
// Attempt authentication via AuthenticationManager
|
||||||
try {
|
try {
|
||||||
rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth);
|
rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth);
|
||||||
|
this.sessionStrategy.onAuthentication(rememberMeAuth, request, response);
|
||||||
// Store to SecurityContextHolder
|
// Store to SecurityContextHolder
|
||||||
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
|
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
|
||||||
context.setAuthentication(rememberMeAuth);
|
context.setAuthentication(rememberMeAuth);
|
||||||
@ -211,4 +216,17 @@ public class RememberMeAuthenticationFilter extends GenericFilterBean implements
|
|||||||
this.securityContextHolderStrategy = securityContextHolderStrategy;
|
this.securityContextHolderStrategy = securityContextHolderStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The session handling strategy which will be invoked immediately after an
|
||||||
|
* authentication request is successfully processed by the
|
||||||
|
* <tt>AuthenticationManager</tt>. Used, for example, to handle changing of the
|
||||||
|
* session identifier to prevent session fixation attacks.
|
||||||
|
* @param sessionStrategy the implementation to use. If not set a null implementation
|
||||||
|
* is used.
|
||||||
|
*/
|
||||||
|
public void setSessionAuthenticationStrategy(SessionAuthenticationStrategy sessionStrategy) {
|
||||||
|
Assert.notNull(sessionStrategy, "sessionStrategy cannot be null");
|
||||||
|
this.sessionStrategy = sessionStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
|||||||
import org.springframework.security.web.authentication.NullRememberMeServices;
|
import org.springframework.security.web.authentication.NullRememberMeServices;
|
||||||
import org.springframework.security.web.authentication.RememberMeServices;
|
import org.springframework.security.web.authentication.RememberMeServices;
|
||||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
|
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
|
||||||
|
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
|
||||||
import org.springframework.security.web.context.SecurityContextRepository;
|
import org.springframework.security.web.context.SecurityContextRepository;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
@ -170,6 +171,23 @@ public class RememberMeAuthenticationFilterTests {
|
|||||||
verify(securityContextRepository).saveContext(any(), eq(request), eq(response));
|
verify(securityContextRepository).saveContext(any(), eq(request), eq(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sessionAuthenticationStrategyInvokedIfSet() throws Exception {
|
||||||
|
SessionAuthenticationStrategy sessionAuthenticationStrategy = mock(SessionAuthenticationStrategy.class);
|
||||||
|
AuthenticationManager am = mock(AuthenticationManager.class);
|
||||||
|
given(am.authenticate(this.remembered)).willReturn(this.remembered);
|
||||||
|
RememberMeAuthenticationFilter filter = new RememberMeAuthenticationFilter(am,
|
||||||
|
new MockRememberMeServices(this.remembered));
|
||||||
|
filter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler("/target"));
|
||||||
|
filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
FilterChain fc = mock(FilterChain.class);
|
||||||
|
request.setRequestURI("x");
|
||||||
|
filter.doFilter(request, response, fc);
|
||||||
|
verify(sessionAuthenticationStrategy).onAuthentication(any(), eq(request), eq(response));
|
||||||
|
}
|
||||||
|
|
||||||
private class MockRememberMeServices implements RememberMeServices {
|
private class MockRememberMeServices implements RememberMeServices {
|
||||||
|
|
||||||
private Authentication authToReturn;
|
private Authentication authToReturn;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user