Skip to content

Commit 972039e

Browse files
committed
Add SecurityContextHolderFilter
Closes gh-9635
1 parent f9619ce commit 972039e

File tree

21 files changed

+571
-43
lines changed

21 files changed

+571
-43
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistration.java

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter;
3838
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
3939
import org.springframework.security.web.authentication.www.DigestAuthenticationFilter;
40+
import org.springframework.security.web.context.SecurityContextHolderFilter;
4041
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
4142
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
4243
import org.springframework.security.web.csrf.CsrfFilter;
@@ -70,6 +71,7 @@ final class FilterOrderRegistration {
7071
put(ChannelProcessingFilter.class, order.next());
7172
order.next(); // gh-8105
7273
put(WebAsyncManagerIntegrationFilter.class, order.next());
74+
put(SecurityContextHolderFilter.class, order.next());
7375
put(SecurityContextPersistenceFilter.class, order.next());
7476
put(HeaderWriterFilter.class, order.next());
7577
put(CorsFilter.class, order.next());

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

+12
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
3838
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
3939
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
40+
import org.springframework.security.web.context.SecurityContextRepository;
4041
import org.springframework.security.web.savedrequest.RequestCache;
4142
import org.springframework.security.web.util.matcher.AndRequestMatcher;
4243
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
@@ -144,6 +145,11 @@ public T loginProcessingUrl(String loginProcessingUrl) {
144145
return getSelf();
145146
}
146147

148+
public T securityContextRepository(SecurityContextRepository securityContextRepository) {
149+
this.authFilter.setSecurityContextRepository(securityContextRepository);
150+
return getSelf();
151+
}
152+
147153
/**
148154
* Create the {@link RequestMatcher} given a loginProcessingUrl
149155
* @param loginProcessingUrl creates the {@link RequestMatcher} based upon the
@@ -285,6 +291,12 @@ public void configure(B http) throws Exception {
285291
if (rememberMeServices != null) {
286292
this.authFilter.setRememberMeServices(rememberMeServices);
287293
}
294+
SecurityContextConfigurer securityContextConfigurer = http.getConfigurer(SecurityContextConfigurer.class);
295+
if (securityContextConfigurer != null && securityContextConfigurer.isRequireExplicitSave()) {
296+
SecurityContextRepository securityContextRepository = securityContextConfigurer
297+
.getSecurityContextRepository();
298+
this.authFilter.setSecurityContextRepository(securityContextRepository);
299+
}
288300
F filter = postProcess(this.authFilter);
289301
http.addFilter(filter);
290302
}

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

+37-12
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.security.core.context.SecurityContext;
2323
import org.springframework.security.core.context.SecurityContextHolder;
2424
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
25+
import org.springframework.security.web.context.SecurityContextHolderFilter;
2526
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
2627
import org.springframework.security.web.context.SecurityContextRepository;
2728

@@ -62,6 +63,8 @@
6263
public final class SecurityContextConfigurer<H extends HttpSecurityBuilder<H>>
6364
extends AbstractHttpConfigurer<SecurityContextConfigurer<H>, H> {
6465

66+
private boolean requireExplicitSave;
67+
6568
/**
6669
* Creates a new instance
6770
* @see HttpSecurity#securityContext()
@@ -79,23 +82,45 @@ public SecurityContextConfigurer<H> securityContextRepository(SecurityContextRep
7982
return this;
8083
}
8184

85+
public SecurityContextConfigurer<H> requireExplicitSave(boolean requireExplicitSave) {
86+
this.requireExplicitSave = requireExplicitSave;
87+
return this;
88+
}
89+
90+
boolean isRequireExplicitSave() {
91+
return this.requireExplicitSave;
92+
}
93+
94+
SecurityContextRepository getSecurityContextRepository() {
95+
SecurityContextRepository securityContextRepository = getBuilder()
96+
.getSharedObject(SecurityContextRepository.class);
97+
if (securityContextRepository == null) {
98+
securityContextRepository = new HttpSessionSecurityContextRepository();
99+
}
100+
return securityContextRepository;
101+
}
102+
82103
@Override
83104
@SuppressWarnings("unchecked")
84105
public void configure(H http) {
85-
SecurityContextRepository securityContextRepository = http.getSharedObject(SecurityContextRepository.class);
86-
if (securityContextRepository == null) {
87-
securityContextRepository = new HttpSessionSecurityContextRepository();
106+
SecurityContextRepository securityContextRepository = getSecurityContextRepository();
107+
if (this.requireExplicitSave) {
108+
SecurityContextHolderFilter securityContextHolderFilter = postProcess(
109+
new SecurityContextHolderFilter(securityContextRepository));
110+
http.addFilter(securityContextHolderFilter);
88111
}
89-
SecurityContextPersistenceFilter securityContextFilter = new SecurityContextPersistenceFilter(
90-
securityContextRepository);
91-
SessionManagementConfigurer<?> sessionManagement = http.getConfigurer(SessionManagementConfigurer.class);
92-
SessionCreationPolicy sessionCreationPolicy = (sessionManagement != null)
93-
? sessionManagement.getSessionCreationPolicy() : null;
94-
if (SessionCreationPolicy.ALWAYS == sessionCreationPolicy) {
95-
securityContextFilter.setForceEagerSessionCreation(true);
112+
else {
113+
SecurityContextPersistenceFilter securityContextFilter = new SecurityContextPersistenceFilter(
114+
securityContextRepository);
115+
SessionManagementConfigurer<?> sessionManagement = http.getConfigurer(SessionManagementConfigurer.class);
116+
SessionCreationPolicy sessionCreationPolicy = (sessionManagement != null)
117+
? sessionManagement.getSessionCreationPolicy() : null;
118+
if (SessionCreationPolicy.ALWAYS == sessionCreationPolicy) {
119+
securityContextFilter.setForceEagerSessionCreation(true);
120+
}
121+
securityContextFilter = postProcess(securityContextFilter);
122+
http.addFilter(securityContextFilter);
96123
}
97-
securityContextFilter = postProcess(securityContextFilter);
98-
http.addFilter(securityContextFilter);
99124
}
100125

101126
}

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

+29-14
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,8 @@ final class AuthenticationConfigBuilder {
216216

217217
AuthenticationConfigBuilder(Element element, boolean forceAutoConfig, ParserContext pc,
218218
SessionCreationPolicy sessionPolicy, BeanReference requestCache, BeanReference authenticationManager,
219-
BeanReference sessionStrategy, BeanReference portMapper, BeanReference portResolver,
220-
BeanMetadataElement csrfLogoutHandler) {
219+
BeanReference authenticationFilterSecurityContextRepositoryRef, BeanReference sessionStrategy,
220+
BeanReference portMapper, BeanReference portResolver, BeanMetadataElement csrfLogoutHandler) {
221221
this.httpElt = element;
222222
this.pc = pc;
223223
this.requestCache = requestCache;
@@ -231,9 +231,10 @@ final class AuthenticationConfigBuilder {
231231
createRememberMeFilter(authenticationManager);
232232
createBasicFilter(authenticationManager);
233233
createBearerTokenAuthenticationFilter(authenticationManager);
234-
createFormLoginFilter(sessionStrategy, authenticationManager);
235-
createOAuth2ClientFilters(sessionStrategy, requestCache, authenticationManager);
236-
createSaml2LoginFilter(authenticationManager);
234+
createFormLoginFilter(sessionStrategy, authenticationManager, authenticationFilterSecurityContextRepositoryRef);
235+
createOAuth2ClientFilters(sessionStrategy, requestCache, authenticationManager,
236+
authenticationFilterSecurityContextRepositoryRef);
237+
createSaml2LoginFilter(authenticationManager, authenticationFilterSecurityContextRepositoryRef);
237238
createX509Filter(authenticationManager);
238239
createJeeFilter(authenticationManager);
239240
createLogoutFilter();
@@ -269,7 +270,8 @@ private void createRememberMeProvider(String key) {
269270
this.rememberMeProviderRef = new RuntimeBeanReference(id);
270271
}
271272

272-
void createFormLoginFilter(BeanReference sessionStrategy, BeanReference authManager) {
273+
void createFormLoginFilter(BeanReference sessionStrategy, BeanReference authManager,
274+
BeanReference authenticationFilterSecurityContextRepositoryRef) {
273275
Element formLoginElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.FORM_LOGIN);
274276
RootBeanDefinition formFilter = null;
275277
if (formLoginElt != null || this.autoConfig) {
@@ -285,6 +287,10 @@ void createFormLoginFilter(BeanReference sessionStrategy, BeanReference authMana
285287
if (formFilter != null) {
286288
formFilter.getPropertyValues().addPropertyValue("allowSessionCreation", this.allowSessionCreation);
287289
formFilter.getPropertyValues().addPropertyValue("authenticationManager", authManager);
290+
if (authenticationFilterSecurityContextRepositoryRef != null) {
291+
formFilter.getPropertyValues().addPropertyValue("securityContextRepository",
292+
authenticationFilterSecurityContextRepositoryRef);
293+
}
288294
// Id is required by login page filter
289295
this.formFilterId = this.pc.getReaderContext().generateBeanName(formFilter);
290296
this.pc.registerBeanComponent(new BeanComponentDefinition(formFilter, this.formFilterId));
@@ -293,13 +299,15 @@ void createFormLoginFilter(BeanReference sessionStrategy, BeanReference authMana
293299
}
294300

295301
void createOAuth2ClientFilters(BeanReference sessionStrategy, BeanReference requestCache,
296-
BeanReference authenticationManager) {
297-
createOAuth2LoginFilter(sessionStrategy, authenticationManager);
298-
createOAuth2ClientFilter(requestCache, authenticationManager);
302+
BeanReference authenticationManager, BeanReference authenticationFilterSecurityContextRepositoryRef) {
303+
createOAuth2LoginFilter(sessionStrategy, authenticationManager,
304+
authenticationFilterSecurityContextRepositoryRef);
305+
createOAuth2ClientFilter(requestCache, authenticationManager, authenticationFilterSecurityContextRepositoryRef);
299306
registerOAuth2ClientPostProcessors();
300307
}
301308

302-
void createOAuth2LoginFilter(BeanReference sessionStrategy, BeanReference authManager) {
309+
void createOAuth2LoginFilter(BeanReference sessionStrategy, BeanReference authManager,
310+
BeanReference authenticationFilterSecurityContextRepositoryRef) {
303311
Element oauth2LoginElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.OAUTH2_LOGIN);
304312
if (oauth2LoginElt == null) {
305313
return;
@@ -311,6 +319,10 @@ void createOAuth2LoginFilter(BeanReference sessionStrategy, BeanReference authMa
311319
BeanDefinition defaultAuthorizedClientRepository = parser.getDefaultAuthorizedClientRepository();
312320
registerDefaultAuthorizedClientRepositoryIfNecessary(defaultAuthorizedClientRepository);
313321
oauth2LoginFilterBean.getPropertyValues().addPropertyValue("authenticationManager", authManager);
322+
if (authenticationFilterSecurityContextRepositoryRef != null) {
323+
oauth2LoginFilterBean.getPropertyValues().addPropertyValue("securityContextRepository",
324+
authenticationFilterSecurityContextRepositoryRef);
325+
}
314326

315327
// retrieve the other bean result
316328
BeanDefinition oauth2LoginAuthProvider = parser.getOAuth2LoginAuthenticationProvider();
@@ -340,14 +352,15 @@ void createOAuth2LoginFilter(BeanReference sessionStrategy, BeanReference authMa
340352
this.oauth2LoginOidcAuthenticationProviderRef = new RuntimeBeanReference(oauth2LoginOidcAuthProviderId);
341353
}
342354

343-
void createOAuth2ClientFilter(BeanReference requestCache, BeanReference authenticationManager) {
355+
void createOAuth2ClientFilter(BeanReference requestCache, BeanReference authenticationManager,
356+
BeanReference authenticationFilterSecurityContextRepositoryRef) {
344357
Element oauth2ClientElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.OAUTH2_CLIENT);
345358
if (oauth2ClientElt == null) {
346359
return;
347360
}
348361
this.oauth2ClientEnabled = true;
349362
OAuth2ClientBeanDefinitionParser parser = new OAuth2ClientBeanDefinitionParser(requestCache,
350-
authenticationManager);
363+
authenticationManager, authenticationFilterSecurityContextRepositoryRef);
351364
parser.parse(oauth2ClientElt, this.pc);
352365
BeanDefinition defaultAuthorizedClientRepository = parser.getDefaultAuthorizedClientRepository();
353366
registerDefaultAuthorizedClientRepositoryIfNecessary(defaultAuthorizedClientRepository);
@@ -392,14 +405,16 @@ private void registerOAuth2ClientPostProcessors() {
392405
}
393406
}
394407

395-
private void createSaml2LoginFilter(BeanReference authenticationManager) {
408+
private void createSaml2LoginFilter(BeanReference authenticationManager,
409+
BeanReference authenticationFilterSecurityContextRepositoryRef) {
396410
Element saml2LoginElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.SAML2_LOGIN);
397411
if (saml2LoginElt == null) {
398412
return;
399413
}
400414
Saml2LoginBeanDefinitionParser parser = new Saml2LoginBeanDefinitionParser(this.csrfIgnoreRequestMatchers,
401415
this.portMapper, this.portResolver, this.requestCache, this.allowSessionCreation, authenticationManager,
402-
this.authenticationProviders, this.defaultEntryPointMappings);
416+
authenticationFilterSecurityContextRepositoryRef, this.authenticationProviders,
417+
this.defaultEntryPointMappings);
403418
BeanDefinition saml2WebSsoAuthenticationFilter = parser.parse(saml2LoginElt, this.pc);
404419
this.saml2AuthorizationRequestFilter = parser.getSaml2WebSsoAuthenticationRequestFilter();
405420

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

+29-3
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
6060
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
6161
import org.springframework.security.web.context.NullSecurityContextRepository;
62+
import org.springframework.security.web.context.SecurityContextHolderFilter;
6263
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
6364
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
6465
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
@@ -104,6 +105,8 @@ class HttpConfigurationBuilder {
104105

105106
private static final String ATT_SECURITY_CONTEXT_REPOSITORY = "security-context-repository-ref";
106107

108+
private static final String ATT_SECURITY_CONTEXT_EXPLICIT_SAVE = "security-context-explicit-save";
109+
107110
private static final String ATT_INVALID_SESSION_STRATEGY_REF = "invalid-session-strategy-ref";
108111

109112
private static final String ATT_DISABLE_URL_REWRITING = "disable-url-rewriting";
@@ -202,8 +205,7 @@ class HttpConfigurationBuilder {
202205
this.sessionPolicy = !StringUtils.hasText(createSession) ? SessionCreationPolicy.IF_REQUIRED
203206
: createPolicy(createSession);
204207
createCsrfFilter();
205-
createSecurityContextRepository();
206-
createSecurityContextPersistenceFilter();
208+
createSecurityPersistence();
207209
createSessionManagementFilters();
208210
createWebAsyncManagerFilter();
209211
createRequestCacheFilter();
@@ -279,9 +281,27 @@ static String createPath(String path, boolean lowerCase) {
279281
return lowerCase ? path.toLowerCase() : path;
280282
}
281283

284+
BeanReference getSecurityContextRepositoryForAuthenticationFilters() {
285+
return (isExplicitSave()) ? this.contextRepoRef : null;
286+
}
287+
288+
private void createSecurityPersistence() {
289+
createSecurityContextRepository();
290+
if (isExplicitSave()) {
291+
createSecurityContextHolderFilter();
292+
}
293+
else {
294+
createSecurityContextPersistenceFilter();
295+
}
296+
}
297+
298+
private boolean isExplicitSave() {
299+
String explicitSaveAttr = this.httpElt.getAttribute(ATT_SECURITY_CONTEXT_EXPLICIT_SAVE);
300+
return Boolean.parseBoolean(explicitSaveAttr);
301+
}
302+
282303
private void createSecurityContextPersistenceFilter() {
283304
BeanDefinitionBuilder scpf = BeanDefinitionBuilder.rootBeanDefinition(SecurityContextPersistenceFilter.class);
284-
String disableUrlRewriting = this.httpElt.getAttribute(ATT_DISABLE_URL_REWRITING);
285305
switch (this.sessionPolicy) {
286306
case ALWAYS:
287307
scpf.addPropertyValue("forceEagerSessionCreation", Boolean.TRUE);
@@ -332,6 +352,12 @@ private void createSecurityContextRepository() {
332352
this.contextRepoRef = new RuntimeBeanReference(repoRef);
333353
}
334354

355+
private void createSecurityContextHolderFilter() {
356+
BeanDefinitionBuilder filter = BeanDefinitionBuilder.rootBeanDefinition(SecurityContextHolderFilter.class);
357+
filter.addConstructorArgValue(this.contextRepoRef);
358+
this.securityContextPersistenceFilter = filter.getBeanDefinition();
359+
}
360+
335361
private void createSessionManagementFilters() {
336362
Element sessionMgmtElt = DomUtils.getChildElementByTagName(this.httpElt, Elements.SESSION_MANAGEMENT);
337363
Element sessionCtrlElt = null;

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,11 @@ private BeanReference createFilterChain(Element element, ParserContext pc) {
144144
boolean forceAutoConfig = isDefaultHttpConfig(element);
145145
HttpConfigurationBuilder httpBldr = new HttpConfigurationBuilder(element, forceAutoConfig, pc, portMapper,
146146
portResolver, authenticationManager);
147+
httpBldr.getSecurityContextRepositoryForAuthenticationFilters();
147148
AuthenticationConfigBuilder authBldr = new AuthenticationConfigBuilder(element, forceAutoConfig, pc,
148149
httpBldr.getSessionCreationPolicy(), httpBldr.getRequestCache(), authenticationManager,
149-
httpBldr.getSessionStrategy(), portMapper, portResolver, httpBldr.getCsrfLogoutHandler());
150+
httpBldr.getSecurityContextRepositoryForAuthenticationFilters(), httpBldr.getSessionStrategy(),
151+
portMapper, portResolver, httpBldr.getCsrfLogoutHandler());
150152
httpBldr.setLogoutHandlers(authBldr.getLogoutHandlers());
151153
httpBldr.setEntryPoint(authBldr.getEntryPointBean());
152154
httpBldr.setAccessDeniedHandler(authBldr.getAccessDeniedHandlerBean());

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

+12-3
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ final class OAuth2ClientBeanDefinitionParser implements BeanDefinitionParser {
5050

5151
private final BeanReference authenticationManager;
5252

53+
private final BeanReference authenticationFilterSecurityContextRepositoryRef;
54+
5355
private BeanDefinition defaultAuthorizedClientRepository;
5456

5557
private BeanDefinition authorizationRequestRedirectFilter;
@@ -58,9 +60,11 @@ final class OAuth2ClientBeanDefinitionParser implements BeanDefinitionParser {
5860

5961
private BeanDefinition authorizationCodeAuthenticationProvider;
6062

61-
OAuth2ClientBeanDefinitionParser(BeanReference requestCache, BeanReference authenticationManager) {
63+
OAuth2ClientBeanDefinitionParser(BeanReference requestCache, BeanReference authenticationManager,
64+
BeanReference authenticationFilterSecurityContextRepositoryRef) {
6265
this.requestCache = requestCache;
6366
this.authenticationManager = authenticationManager;
67+
this.authenticationFilterSecurityContextRepositoryRef = authenticationFilterSecurityContextRepositoryRef;
6468
}
6569

6670
@Override
@@ -92,11 +96,16 @@ public BeanDefinition parse(Element element, ParserContext parserContext) {
9296
this.authorizationRequestRedirectFilter = authorizationRequestRedirectFilterBuilder
9397
.addPropertyValue("authorizationRequestRepository", authorizationRequestRepository)
9498
.addPropertyValue("requestCache", this.requestCache).getBeanDefinition();
95-
this.authorizationCodeGrantFilter = BeanDefinitionBuilder
99+
BeanDefinitionBuilder authorizationCodeGrantFilterBldr = BeanDefinitionBuilder
96100
.rootBeanDefinition(OAuth2AuthorizationCodeGrantFilter.class)
97101
.addConstructorArgValue(clientRegistrationRepository).addConstructorArgValue(authorizedClientRepository)
98102
.addConstructorArgValue(this.authenticationManager)
99-
.addPropertyValue("authorizationRequestRepository", authorizationRequestRepository).getBeanDefinition();
103+
.addPropertyValue("authorizationRequestRepository", authorizationRequestRepository);
104+
if (this.authenticationFilterSecurityContextRepositoryRef != null) {
105+
authorizationCodeGrantFilterBldr.addPropertyValue("securityContextRepository",
106+
this.authenticationFilterSecurityContextRepositoryRef);
107+
}
108+
this.authorizationCodeGrantFilter = authorizationCodeGrantFilterBldr.getBeanDefinition();
100109

101110
BeanMetadataElement accessTokenResponseClient = getAccessTokenResponseClient(authorizationCodeGrantElt);
102111
this.authorizationCodeAuthenticationProvider = BeanDefinitionBuilder

0 commit comments

Comments
 (0)