Skip to content

Commit 9a9a43a

Browse files
committed
ForceEagerSessionCreationFilter
Closes gh-11109
1 parent 9601efd commit 9a9a43a

File tree

14 files changed

+152
-2
lines changed

14 files changed

+152
-2
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/HttpSecurityBuilder.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
4343
import org.springframework.security.web.session.ConcurrentSessionFilter;
4444
import org.springframework.security.web.session.DisableEncodeUrlFilter;
45+
import org.springframework.security.web.session.ForceEagerSessionCreationFilter;
4546
import org.springframework.security.web.session.SessionManagementFilter;
4647

4748
/**
@@ -124,6 +125,7 @@ public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>>
124125
* The ordering of the Filters is:
125126
*
126127
* <ul>
128+
* <li>{@link ForceEagerSessionCreationFilter}</li>
127129
* <li>{@link DisableEncodeUrlFilter}</li>
128130
* <li>{@link ChannelProcessingFilter}</li>
129131
* <li>{@link SecurityContextPersistenceFilter}</li>

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
4848
import org.springframework.security.web.session.ConcurrentSessionFilter;
4949
import org.springframework.security.web.session.DisableEncodeUrlFilter;
50+
import org.springframework.security.web.session.ForceEagerSessionCreationFilter;
5051
import org.springframework.security.web.session.SessionManagementFilter;
5152
import org.springframework.web.filter.CorsFilter;
5253

@@ -70,6 +71,7 @@ final class FilterOrderRegistration {
7071
FilterOrderRegistration() {
7172
Step order = new Step(INITIAL_ORDER, ORDER_STEP);
7273
put(DisableEncodeUrlFilter.class, order.next());
74+
put(ForceEagerSessionCreationFilter.class, order.next());
7375
put(ChannelProcessingFilter.class, order.next());
7476
order.next(); // gh-8105
7577
put(WebAsyncManagerIntegrationFilter.class, order.next());

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.springframework.security.web.context.SecurityContextHolderFilter;
2626
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
2727
import org.springframework.security.web.context.SecurityContextRepository;
28+
import org.springframework.security.web.session.ForceEagerSessionCreationFilter;
2829

2930
/**
3031
* Allows persisting and restoring of the {@link SecurityContext} found on the
@@ -117,6 +118,7 @@ public void configure(H http) {
117118
? sessionManagement.getSessionCreationPolicy() : null;
118119
if (SessionCreationPolicy.ALWAYS == sessionCreationPolicy) {
119120
securityContextFilter.setForceEagerSessionCreation(true);
121+
http.addFilter(postProcess(new ForceEagerSessionCreationFilter()));
120122
}
121123
securityContextFilter = postProcess(securityContextFilter);
122124
http.addFilter(securityContextFilter);

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import org.springframework.security.web.savedrequest.RequestCache;
5454
import org.springframework.security.web.session.ConcurrentSessionFilter;
5555
import org.springframework.security.web.session.DisableEncodeUrlFilter;
56+
import org.springframework.security.web.session.ForceEagerSessionCreationFilter;
5657
import org.springframework.security.web.session.InvalidSessionStrategy;
5758
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
5859
import org.springframework.security.web.session.SessionManagementFilter;
@@ -380,6 +381,9 @@ public void configure(H http) {
380381
if (!this.enableSessionUrlRewriting) {
381382
http.addFilter(new DisableEncodeUrlFilter());
382383
}
384+
if (this.sessionPolicy == SessionCreationPolicy.ALWAYS) {
385+
http.addFilter(new ForceEagerSessionCreationFilter());
386+
}
383387
}
384388

385389
private ConcurrentSessionFilter createConcurrencyFilter(H http) {

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
6969
import org.springframework.security.web.session.ConcurrentSessionFilter;
7070
import org.springframework.security.web.session.DisableEncodeUrlFilter;
71+
import org.springframework.security.web.session.ForceEagerSessionCreationFilter;
7172
import org.springframework.security.web.session.SessionManagementFilter;
7273
import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy;
7374
import org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy;
@@ -147,6 +148,8 @@ class HttpConfigurationBuilder {
147148

148149
private BeanDefinition securityContextPersistenceFilter;
149150

151+
private BeanDefinition forceEagerSessionCreationFilter;
152+
150153
private BeanReference contextRepoRef;
151154

152155
private BeanReference sessionRegistryRef;
@@ -206,6 +209,7 @@ class HttpConfigurationBuilder {
206209
String createSession = element.getAttribute(ATT_CREATE_SESSION);
207210
this.sessionPolicy = !StringUtils.hasText(createSession) ? SessionCreationPolicy.IF_REQUIRED
208211
: createPolicy(createSession);
212+
createForceEagerSessionCreationFilter();
209213
createDisableEncodeUrlFilter();
210214
createCsrfFilter();
211215
createSecurityPersistence();
@@ -303,6 +307,12 @@ private boolean isExplicitSave() {
303307
return Boolean.parseBoolean(explicitSaveAttr);
304308
}
305309

310+
private void createForceEagerSessionCreationFilter() {
311+
if (this.sessionPolicy == SessionCreationPolicy.ALWAYS) {
312+
this.forceEagerSessionCreationFilter = new RootBeanDefinition(ForceEagerSessionCreationFilter.class);
313+
}
314+
}
315+
306316
private void createSecurityContextPersistenceFilter() {
307317
BeanDefinitionBuilder scpf = BeanDefinitionBuilder.rootBeanDefinition(SecurityContextPersistenceFilter.class);
308318
switch (this.sessionPolicy) {
@@ -767,6 +777,10 @@ BeanReference getRequestCache() {
767777

768778
List<OrderDecorator> getFilters() {
769779
List<OrderDecorator> filters = new ArrayList<>();
780+
if (this.forceEagerSessionCreationFilter != null) {
781+
filters.add(new OrderDecorator(this.forceEagerSessionCreationFilter,
782+
SecurityFilters.FORCE_EAGER_SESSION_FILTER));
783+
}
770784
if (this.disableUrlRewriteFilter != null) {
771785
filters.add(new OrderDecorator(this.disableUrlRewriteFilter, SecurityFilters.DISABLE_ENCODE_URL_FILTER));
772786
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ enum SecurityFilters {
3131

3232
DISABLE_ENCODE_URL_FILTER,
3333

34+
FORCE_EAGER_SESSION_FILTER,
35+
3436
CHANNEL_FILTER,
3537

3638
SECURITY_CONTEXT_FILTER,

config/src/main/resources/org/springframework/security/config/spring-security-6.0.rnc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1290,4 +1290,4 @@ position =
12901290
## The explicit position at which the custom-filter should be placed in the chain. Use if you are replacing a standard filter.
12911291
attribute position {named-security-filter}
12921292

1293-
named-security-filter = "FIRST" | "DISABLE_ENCODE_URL_FILTER" | "CHANNEL_FILTER" | "SECURITY_CONTEXT_FILTER" | "CONCURRENT_SESSION_FILTER" | "WEB_ASYNC_MANAGER_FILTER" | "HEADERS_FILTER" | "CORS_FILTER" | "SAML2_LOGOUT_REQUEST_FILTER" | "SAML2_LOGOUT_RESPONSE_FILTER" | "CSRF_FILTER" | "SAML2_LOGOUT_FILTER" | "LOGOUT_FILTER" | "OAUTH2_AUTHORIZATION_REQUEST_FILTER" | "SAML2_AUTHENTICATION_REQUEST_FILTER" | "X509_FILTER" | "PRE_AUTH_FILTER" | "CAS_FILTER" | "OAUTH2_LOGIN_FILTER" | "SAML2_AUTHENTICATION_FILTER" | "FORM_LOGIN_FILTER" | "LOGIN_PAGE_FILTER" |"LOGOUT_PAGE_FILTER" | "DIGEST_AUTH_FILTER" | "BEARER_TOKEN_AUTH_FILTER" | "BASIC_AUTH_FILTER" | "REQUEST_CACHE_FILTER" | "SERVLET_API_SUPPORT_FILTER" | "JAAS_API_SUPPORT_FILTER" | "REMEMBER_ME_FILTER" | "ANONYMOUS_FILTER" | "OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER" | "WELL_KNOWN_CHANGE_PASSWORD_REDIRECT_FILTER" | "SESSION_MANAGEMENT_FILTER" | "EXCEPTION_TRANSLATION_FILTER" | "FILTER_SECURITY_INTERCEPTOR" | "SWITCH_USER_FILTER" | "LAST"
1293+
named-security-filter = "FIRST" | "DISABLE_ENCODE_URL_FILTER" | "FORCE_EAGER_SESSION_FILTER" | "CHANNEL_FILTER" | "SECURITY_CONTEXT_FILTER" | "CONCURRENT_SESSION_FILTER" | "WEB_ASYNC_MANAGER_FILTER" | "HEADERS_FILTER" | "CORS_FILTER" | "SAML2_LOGOUT_REQUEST_FILTER" | "SAML2_LOGOUT_RESPONSE_FILTER" | "CSRF_FILTER" | "SAML2_LOGOUT_FILTER" | "LOGOUT_FILTER" | "OAUTH2_AUTHORIZATION_REQUEST_FILTER" | "SAML2_AUTHENTICATION_REQUEST_FILTER" | "X509_FILTER" | "PRE_AUTH_FILTER" | "CAS_FILTER" | "OAUTH2_LOGIN_FILTER" | "SAML2_AUTHENTICATION_FILTER" | "FORM_LOGIN_FILTER" | "LOGIN_PAGE_FILTER" |"LOGOUT_PAGE_FILTER" | "DIGEST_AUTH_FILTER" | "BEARER_TOKEN_AUTH_FILTER" | "BASIC_AUTH_FILTER" | "REQUEST_CACHE_FILTER" | "SERVLET_API_SUPPORT_FILTER" | "JAAS_API_SUPPORT_FILTER" | "REMEMBER_ME_FILTER" | "ANONYMOUS_FILTER" | "OAUTH2_AUTHORIZATION_CODE_GRANT_FILTER" | "WELL_KNOWN_CHANGE_PASSWORD_REDIRECT_FILTER" | "SESSION_MANAGEMENT_FILTER" | "EXCEPTION_TRANSLATION_FILTER" | "FILTER_SECURITY_INTERCEPTOR" | "SWITCH_USER_FILTER" | "LAST"

config/src/main/resources/org/springframework/security/config/spring-security-6.0.xsd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3632,6 +3632,7 @@
36323632
<xs:restriction base="xs:token">
36333633
<xs:enumeration value="FIRST"/>
36343634
<xs:enumeration value="DISABLE_ENCODE_URL_FILTER"/>
3635+
<xs:enumeration value="FORCE_EAGER_SESSION_FILTER"/>
36353636
<xs:enumeration value="CHANNEL_FILTER"/>
36363637
<xs:enumeration value="SECURITY_CONTEXT_FILTER"/>
36373638
<xs:enumeration value="CONCURRENT_SESSION_FILTER"/>

config/src/test/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistrationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public void putWhenCustomFilterAlreadyExistsThenDoesNotOverride() {
5252

5353
@Test
5454
public void putWhenPredefinedFilterThenDoesNotOverride() {
55-
int position = 200;
55+
int position = 300;
5656
Integer predefinedFilterOrderBefore = this.filterOrderRegistration.getOrder(ChannelProcessingFilter.class);
5757
this.filterOrderRegistration.put(MyFilter.class, position);
5858
Integer myFilterOrder = this.filterOrderRegistration.getOrder(MyFilter.class);

docs/modules/ROOT/pages/servlet/architecture.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ However, there are times that it is beneficial to know the ordering.
167167

168168
The following is a comprehensive list of Spring Security Filter ordering:
169169

170+
* xref:servlet/authentication/session-management.adoc#session-mgmt-force-session-creation[`ForceEagerSessionCreationFilter`]
170171
* `ChannelProcessingFilter`
171172
* `WebAsyncManagerIntegrationFilter`
172173
* `SecurityContextPersistenceFilter`

docs/modules/ROOT/pages/servlet/authentication/session-management.adoc

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,35 @@
33
HTTP session-related functionality is handled by a combination of the {security-api-url}org/springframework/security/authentication/AuthenticationProvider.html[`SessionManagementFilter`] and the {security-api-url}org/springframework/security/web/authentication/session/SessionAuthenticationStrategy.html[`SessionAuthenticationStrategy`] interface, to which the filter delegates.
44
Typical usage includes session-fixation protection attack prevention, detection of session timeouts, and restrictions on how many sessions an authenticated user may have open concurrently.
55

6+
[[session-mgmt-force-session-creation]]
7+
== Force Eager Session Creation
8+
9+
At times it can be valuable to eagerly create sessions.
10+
This can be done by using the {security-api-url}org/springframework/security/web/session/ForceEagerSessionCreationFilter.html[`ForceEagerSessionCreationFilter`] which can be configured using:
11+
12+
====
13+
.Java
14+
[source,java,role="primary"]
15+
----
16+
@Bean
17+
public SecurityFilterChain filterChain(HttpSecurity http) {
18+
http
19+
.sessionManagement(session -> session
20+
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
21+
);
22+
return http.build();
23+
}
24+
----
25+
26+
.XML
27+
[source,xml,role="secondary"]
28+
----
29+
<http create-session="ALWAYS">
30+
31+
</http>
32+
----
33+
====
34+
635
== Detecting Timeouts
736
You can configure Spring Security to detect the submission of an invalid session ID and redirect the user to an appropriate URL.
837
To do so, configure the `session-management` element:

docs/modules/ROOT/pages/servlet/configuration/xml-namespace.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,10 @@ The filters, aliases, and namespace elements and attributes that create the filt
274274
| `DisableEncodeUrlFilter`
275275
| `http@disable-url-rewriting`
276276

277+
| FORCE_EAGER_SESSION_FILTER
278+
| `ForceEagerSessionCreationFilter`
279+
| `http@create-session="ALWAYS"`
280+
277281
| CHANNEL_FILTER
278282
| `ChannelProcessingFilter`
279283
| `http/intercept-url@requires-channel`
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.session;
18+
19+
import java.io.IOException;
20+
21+
import jakarta.servlet.FilterChain;
22+
import jakarta.servlet.ServletException;
23+
import jakarta.servlet.http.HttpServletRequest;
24+
import jakarta.servlet.http.HttpServletResponse;
25+
import jakarta.servlet.http.HttpSession;
26+
27+
import org.springframework.core.log.LogMessage;
28+
import org.springframework.web.filter.OncePerRequestFilter;
29+
30+
/**
31+
* Eagerly creates {@link HttpSession} if it does not already exist.
32+
*
33+
* @author Rob Winch
34+
* @since 5.7
35+
*/
36+
public class ForceEagerSessionCreationFilter extends OncePerRequestFilter {
37+
38+
@Override
39+
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
40+
throws ServletException, IOException {
41+
HttpSession session = request.getSession();
42+
if (this.logger.isDebugEnabled() && session.isNew()) {
43+
this.logger.debug(LogMessage.format("Created session eagerly"));
44+
}
45+
filterChain.doFilter(request, response);
46+
}
47+
48+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.session;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import org.springframework.mock.web.MockFilterChain;
22+
import org.springframework.mock.web.MockHttpServletRequest;
23+
import org.springframework.mock.web.MockHttpServletResponse;
24+
25+
import static org.assertj.core.api.Assertions.assertThat;
26+
27+
class ForceEagerSessionCreationFilterTests {
28+
29+
@Test
30+
void createsSession() throws Exception {
31+
ForceEagerSessionCreationFilter filter = new ForceEagerSessionCreationFilter();
32+
MockHttpServletRequest request = new MockHttpServletRequest();
33+
MockFilterChain chain = new MockFilterChain();
34+
35+
filter.doFilter(request, new MockHttpServletResponse(), chain);
36+
37+
assertThat(request.getSession(false)).isNotNull();
38+
assertThat(chain.getRequest()).isEqualTo(request);
39+
}
40+
41+
}

0 commit comments

Comments
 (0)