Skip to content

Commit 944f565

Browse files
committed
Use SecurityContextHolderStrategy for Remember-me
Issue gh-11060 Isuse gh-11061
1 parent b316a32 commit 944f565

File tree

7 files changed

+114
-15
lines changed

7 files changed

+114
-15
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ public void configure(H http) {
293293
if (this.authenticationSuccessHandler != null) {
294294
rememberMeFilter.setAuthenticationSuccessHandler(this.authenticationSuccessHandler);
295295
}
296+
rememberMeFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
296297
rememberMeFilter = postProcess(rememberMeFilter);
297298
http.addFilter(rememberMeFilter);
298299
}

config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ final class AuthenticationConfigBuilder {
228228
this.portResolver = portResolver;
229229
this.csrfLogoutHandler = csrfLogoutHandler;
230230
createAnonymousFilter(authenticationFilterSecurityContextHolderStrategyRef);
231-
createRememberMeFilter(authenticationManager);
231+
createRememberMeFilter(authenticationManager, authenticationFilterSecurityContextHolderStrategyRef);
232232
createBasicFilter(authenticationManager, authenticationFilterSecurityContextHolderStrategyRef);
233233
createBearerTokenAuthenticationFilter(authenticationManager);
234234
createFormLoginFilter(sessionStrategy, authenticationManager,
@@ -245,7 +245,8 @@ final class AuthenticationConfigBuilder {
245245
createExceptionTranslationFilter(authenticationFilterSecurityContextHolderStrategyRef);
246246
}
247247

248-
void createRememberMeFilter(BeanReference authenticationManager) {
248+
void createRememberMeFilter(BeanReference authenticationManager,
249+
BeanMetadataElement authenticationFilterSecurityContextHolderStrategyRef) {
249250
// Parse remember me before logout as RememberMeServices is also a LogoutHandler
250251
// implementation.
251252
Element rememberMeElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.REMEMBER_ME);
@@ -255,7 +256,7 @@ void createRememberMeFilter(BeanReference authenticationManager) {
255256
key = createKey();
256257
}
257258
RememberMeBeanDefinitionParser rememberMeParser = new RememberMeBeanDefinitionParser(key,
258-
authenticationManager);
259+
authenticationManager, authenticationFilterSecurityContextHolderStrategyRef);
259260
this.rememberMeFilter = rememberMeParser.parse(rememberMeElt, this.pc);
260261
this.rememberMeServicesId = rememberMeParser.getRememberMeServicesId();
261262
createRememberMeProvider(key);

config/src/main/java/org/springframework/security/config/http/RememberMeBeanDefinitionParser.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.apache.commons.logging.LogFactory;
2121
import org.w3c.dom.Element;
2222

23+
import org.springframework.beans.BeanMetadataElement;
2324
import org.springframework.beans.factory.config.BeanDefinition;
2425
import org.springframework.beans.factory.config.BeanReference;
2526
import org.springframework.beans.factory.config.RuntimeBeanReference;
@@ -70,11 +71,15 @@ class RememberMeBeanDefinitionParser implements BeanDefinitionParser {
7071

7172
private final BeanReference authenticationManager;
7273

74+
private final BeanMetadataElement authenticationFilterSecurityContextHolderStrategyRef;
75+
7376
private String rememberMeServicesId;
7477

75-
RememberMeBeanDefinitionParser(String key, BeanReference authenticationManager) {
78+
RememberMeBeanDefinitionParser(String key, BeanReference authenticationManager,
79+
BeanMetadataElement authenticationFilterSecurityContextHolderStrategyRef) {
7680
this.key = key;
7781
this.authenticationManager = authenticationManager;
82+
this.authenticationFilterSecurityContextHolderStrategyRef = authenticationFilterSecurityContextHolderStrategyRef;
7883
}
7984

8085
@Override
@@ -175,6 +180,8 @@ else if (!servicesRefSet) {
175180
}
176181
filter.addConstructorArgValue(this.authenticationManager);
177182
filter.addConstructorArgReference(servicesName);
183+
filter.addPropertyValue("securityContextHolderStrategy",
184+
this.authenticationFilterSecurityContextHolderStrategyRef);
178185
pc.popAndRegisterContainingComponent();
179186
return filter.getBeanDefinition();
180187
}

config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@
3030
import org.springframework.security.authentication.RememberMeAuthenticationToken;
3131
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
3232
import org.springframework.security.config.annotation.ObjectPostProcessor;
33+
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
3334
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
3435
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
3536
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
3637
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
3738
import org.springframework.security.config.test.SpringTestContext;
3839
import org.springframework.security.config.test.SpringTestContextExtension;
40+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
3941
import org.springframework.security.core.userdetails.PasswordEncodedUser;
4042
import org.springframework.security.core.userdetails.User;
4143
import org.springframework.security.core.userdetails.UserDetailsService;
@@ -55,6 +57,7 @@
5557
import static org.mockito.ArgumentMatchers.any;
5658
import static org.mockito.ArgumentMatchers.anyString;
5759
import static org.mockito.BDDMockito.given;
60+
import static org.mockito.Mockito.atLeastOnce;
5861
import static org.mockito.Mockito.mock;
5962
import static org.mockito.Mockito.spy;
6063
import static org.mockito.Mockito.verify;
@@ -100,7 +103,8 @@ public void postWhenNoUserDetailsServiceThenException() {
100103
@Test
101104
public void configureWhenRegisteringObjectPostProcessorThenInvokedOnRememberMeAuthenticationFilter() {
102105
this.spring.register(ObjectPostProcessorConfig.class).autowire();
103-
verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(RememberMeAuthenticationFilter.class));
106+
verify(this.spring.getContext().getBean(ObjectPostProcessor.class))
107+
.postProcess(any(RememberMeAuthenticationFilter.class));
104108
}
105109

106110
@Test
@@ -131,6 +135,21 @@ public void rememberMeWhenUserDetailsServiceNotConfiguredThenUsesBean() throws E
131135
this.mvc.perform(request).andExpect(remembermeAuthentication);
132136
}
133137

138+
@Test
139+
public void rememberMeWhenCustomSecurityContextHolderStrategyThenUses() throws Exception {
140+
this.spring.register(UserDetailsServiceBeanConfig.class, SecurityContextChangedListenerConfig.class).autowire();
141+
MvcResult mvcResult = this.mvc.perform(post("/login").with(csrf()).param("username", "user")
142+
.param("password", "password").param("remember-me", "true")).andReturn();
143+
Cookie rememberMeCookie = mvcResult.getResponse().getCookie("remember-me");
144+
// @formatter:off
145+
MockHttpServletRequestBuilder request = get("/abc").cookie(rememberMeCookie);
146+
SecurityMockMvcResultMatchers.AuthenticatedMatcher remembermeAuthentication = authenticated()
147+
.withAuthentication((auth) -> assertThat(auth).isInstanceOf(RememberMeAuthenticationToken.class));
148+
// @formatter:on
149+
this.mvc.perform(request).andExpect(remembermeAuthentication);
150+
verify(this.spring.getContext().getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext();
151+
}
152+
134153
@Test
135154
public void loginWhenRememberMeTrueThenRespondsWithRememberMeCookie() throws Exception {
136155
this.spring.register(RememberMeConfig.class).autowire();
@@ -315,14 +334,14 @@ protected void configure(AuthenticationManagerBuilder auth) {
315334
@EnableWebSecurity
316335
static class ObjectPostProcessorConfig extends WebSecurityConfigurerAdapter {
317336

318-
static ObjectPostProcessor<Object> objectPostProcessor = spy(ReflectingObjectPostProcessor.class);
337+
ObjectPostProcessor<Object> objectPostProcessor = spy(ReflectingObjectPostProcessor.class);
319338

320339
@Override
321340
protected void configure(HttpSecurity http) throws Exception {
322341
// @formatter:off
323342
http
324343
.rememberMe()
325-
.userDetailsService(new AuthenticationManagerBuilder(objectPostProcessor).getDefaultUserDetailsService());
344+
.userDetailsService(new AuthenticationManagerBuilder(this.objectPostProcessor).getDefaultUserDetailsService());
326345
// @formatter:on
327346
}
328347

@@ -335,8 +354,8 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
335354
}
336355

337356
@Bean
338-
static ObjectPostProcessor<Object> objectPostProcessor() {
339-
return objectPostProcessor;
357+
ObjectPostProcessor<Object> objectPostProcessor() {
358+
return this.objectPostProcessor;
340359
}
341360

342361
}

config/src/test/java/org/springframework/security/config/http/RememberMeConfigTests.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.springframework.security.TestDataSource;
3030
import org.springframework.security.config.test.SpringTestContext;
3131
import org.springframework.security.config.test.SpringTestContextExtension;
32+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
3233
import org.springframework.security.core.userdetails.User;
3334
import org.springframework.security.core.userdetails.UserDetailsService;
3435
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;
@@ -219,6 +220,18 @@ public void configureWhenUsingPersistentTokenRepositoryAndANegativeTokenValidity
219220
() -> this.spring.configLocations(xml("NegativeTokenValidityWithPersistentRepository")).autowire());
220221
}
221222

223+
@Test
224+
public void rememberMeWhenCustomSecurityContextHolderStrategyThenUses() throws Exception {
225+
this.spring.configLocations(xml("WithSecurityContextHolderStrategy")).autowire();
226+
MvcResult result = rememberAuthentication("user", "password").andReturn();
227+
Cookie cookie = rememberMeCookie(result);
228+
// @formatter:off
229+
this.mvc.perform(get("/authenticated").cookie(cookie))
230+
.andExpect(status().isOk());
231+
// @formatter:on
232+
verify(this.spring.getContext().getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext();
233+
}
234+
222235
@Test
223236
public void requestWithRememberMeWhenUsingCustomUserDetailsServiceThenInvokesThisUserDetailsService()
224237
throws Exception {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Copyright 2002-2018 the original author or authors.
4+
~
5+
~ Licensed under the Apache License, Version 2.0 (the "License");
6+
~ you may not use this file except in compliance with the License.
7+
~ You may obtain a copy of the License at
8+
~
9+
~ https://www.apache.org/licenses/LICENSE-2.0
10+
~
11+
~ Unless required by applicable law or agreed to in writing, software
12+
~ distributed under the License is distributed on an "AS IS" BASIS,
13+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
~ See the License for the specific language governing permissions and
15+
~ limitations under the License.
16+
-->
17+
18+
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
19+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
20+
xmlns="http://www.springframework.org/schema/security"
21+
xsi:schemaLocation="
22+
http://www.springframework.org/schema/security
23+
https://www.springframework.org/schema/security/spring-security.xsd
24+
http://www.springframework.org/schema/beans
25+
https://www.springframework.org/schema/beans/spring-beans.xsd">
26+
27+
<http auto-config="true" security-context-holder-strategy-ref="ref">
28+
<intercept-url pattern="/authenticated" access="authenticated"/>
29+
<remember-me/>
30+
</http>
31+
32+
<b:bean id="ref" class="org.mockito.Mockito" factory-method="spy">
33+
<b:constructor-arg>
34+
<b:bean class="org.springframework.security.config.MockSecurityContextHolderStrategy"/>
35+
</b:constructor-arg>
36+
</b:bean>
37+
38+
<b:bean
39+
name="basicController"
40+
class="org.springframework.security.config.http.RememberMeConfigTests.BasicController"/>
41+
42+
<b:import resource="userservice.xml"/>
43+
</b:beans>

web/src/main/java/org/springframework/security/web/authentication/rememberme/RememberMeAuthenticationFilter.java

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.springframework.security.core.AuthenticationException;
3535
import org.springframework.security.core.context.SecurityContext;
3636
import org.springframework.security.core.context.SecurityContextHolder;
37+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
3738
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
3839
import org.springframework.security.web.authentication.RememberMeServices;
3940
import org.springframework.security.web.context.NullSecurityContextRepository;
@@ -67,6 +68,9 @@
6768
*/
6869
public class RememberMeAuthenticationFilter extends GenericFilterBean implements ApplicationEventPublisherAware {
6970

71+
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
72+
.getContextHolderStrategy();
73+
7074
private ApplicationEventPublisher eventPublisher;
7175

7276
private AuthenticationSuccessHandler successHandler;
@@ -99,10 +103,10 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
99103

100104
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
101105
throws IOException, ServletException {
102-
if (SecurityContextHolder.getContext().getAuthentication() != null) {
106+
if (this.securityContextHolderStrategy.getContext().getAuthentication() != null) {
103107
this.logger.debug(LogMessage
104108
.of(() -> "SecurityContextHolder not populated with remember-me token, as it already contained: '"
105-
+ SecurityContextHolder.getContext().getAuthentication() + "'"));
109+
+ this.securityContextHolderStrategy.getContext().getAuthentication() + "'"));
106110
chain.doFilter(request, response);
107111
return;
108112
}
@@ -112,16 +116,16 @@ private void doFilter(HttpServletRequest request, HttpServletResponse response,
112116
try {
113117
rememberMeAuth = this.authenticationManager.authenticate(rememberMeAuth);
114118
// Store to SecurityContextHolder
115-
SecurityContext context = SecurityContextHolder.createEmptyContext();
119+
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
116120
context.setAuthentication(rememberMeAuth);
117-
SecurityContextHolder.setContext(context);
121+
this.securityContextHolderStrategy.setContext(context);
118122
onSuccessfulAuthentication(request, response, rememberMeAuth);
119123
this.logger.debug(LogMessage.of(() -> "SecurityContextHolder populated with remember-me token: '"
120-
+ SecurityContextHolder.getContext().getAuthentication() + "'"));
124+
+ this.securityContextHolderStrategy.getContext().getAuthentication() + "'"));
121125
this.securityContextRepository.saveContext(context, request, response);
122126
if (this.eventPublisher != null) {
123127
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
124-
SecurityContextHolder.getContext().getAuthentication(), this.getClass()));
128+
this.securityContextHolderStrategy.getContext().getAuthentication(), this.getClass()));
125129
}
126130
if (this.successHandler != null) {
127131
this.successHandler.onAuthenticationSuccess(request, response, rememberMeAuth);
@@ -196,4 +200,15 @@ public void setSecurityContextRepository(SecurityContextRepository securityConte
196200
this.securityContextRepository = securityContextRepository;
197201
}
198202

203+
/**
204+
* Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use
205+
* the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}.
206+
*
207+
* @since 5.8
208+
*/
209+
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
210+
Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null");
211+
this.securityContextHolderStrategy = securityContextHolderStrategy;
212+
}
213+
199214
}

0 commit comments

Comments
 (0)