Skip to content

Commit fdfda10

Browse files
Add Max Sessions by Authentication
Closes gh-16206
1 parent 89c7473 commit fdfda10

File tree

10 files changed

+59
-59
lines changed

10 files changed

+59
-59
lines changed

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
import org.springframework.security.web.session.ForceEagerSessionCreationFilter;
6060
import org.springframework.security.web.session.InvalidSessionStrategy;
6161
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
62-
import org.springframework.security.web.session.SessionLimitStrategy;
62+
import org.springframework.security.web.session.SessionLimit;
6363
import org.springframework.security.web.session.SessionManagementFilter;
6464
import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy;
6565
import org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy;
@@ -124,7 +124,7 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
124124

125125
private SessionRegistry sessionRegistry;
126126

127-
private SessionLimitStrategy sessionLimit;
127+
private SessionLimit sessionLimit;
128128

129129
private String expiredUrl;
130130

@@ -330,7 +330,7 @@ public SessionManagementConfigurer<H> sessionFixation(
330330
* @return the {@link SessionManagementConfigurer} for further customizations
331331
*/
332332
public ConcurrencyControlConfigurer maximumSessions(int maximumSessions) {
333-
this.sessionLimit = SessionLimitStrategy.of(maximumSessions);
333+
this.sessionLimit = SessionLimit.of(maximumSessions);
334334
this.propertiesThatRequireImplicitAuthentication.add("maximumSessions = " + maximumSessions);
335335
return new ConcurrencyControlConfigurer();
336336
}
@@ -707,18 +707,19 @@ private ConcurrencyControlConfigurer() {
707707
* @return the {@link ConcurrencyControlConfigurer} for further customizations
708708
*/
709709
public ConcurrencyControlConfigurer maximumSessions(int maximumSessions) {
710-
SessionManagementConfigurer.this.sessionLimit = SessionLimitStrategy.of(maximumSessions);
710+
SessionManagementConfigurer.this.sessionLimit = SessionLimit.of(maximumSessions);
711711
return this;
712712
}
713713

714714
/**
715715
* Determines the behaviour when a session limit is detected.
716-
* @param sessionLimitStrategy the {@link SessionLimitStrategy} to check the
717-
* maximum number of sessions for a user
716+
* @param sessionLimit the {@link SessionLimit} to check the maximum number of
717+
* sessions for a user
718718
* @return the {@link ConcurrencyControlConfigurer} for further customizations
719+
* @since 6.5
719720
*/
720-
public ConcurrencyControlConfigurer maximumSessions(SessionLimitStrategy sessionLimitStrategy) {
721-
SessionManagementConfigurer.this.sessionLimit = sessionLimitStrategy;
721+
public ConcurrencyControlConfigurer maximumSessions(SessionLimit sessionLimit) {
722+
SessionManagementConfigurer.this.sessionLimit = sessionLimit;
722723
return this;
723724
}
724725

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -935,7 +935,7 @@ concurrency-control.attlist &=
935935
## The maximum number of sessions a single authenticated user can have open at the same time. Defaults to "1". A negative value denotes unlimited sessions.
936936
attribute max-sessions {xsd:token}?
937937
concurrency-control.attlist &=
938-
## Allows injection of the SessionLimitStrategy instance used by the ConcurrentSessionControlAuthenticationStrategy
938+
## Allows injection of the SessionLimit instance used by the ConcurrentSessionControlAuthenticationStrategy
939939
attribute max-sessions-ref {xsd:token}?
940940
concurrency-control.attlist &=
941941
## The URL a user will be redirected to if they attempt to use a session which has been "expired" because they have logged in again.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2690,7 +2690,7 @@
26902690
</xs:attribute>
26912691
<xs:attribute name="max-sessions-ref" type="xs:token">
26922692
<xs:annotation>
2693-
<xs:documentation>Allows injection of the SessionLimitStrategy instance used by the
2693+
<xs:documentation>Allows injection of the SessionLimit instance used by the
26942694
ConcurrentSessionControlAuthenticationStrategy
26952695
</xs:documentation>
26962696
</xs:annotation>

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

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
import org.springframework.security.web.savedrequest.RequestCache;
6565
import org.springframework.security.web.session.ConcurrentSessionFilter;
6666
import org.springframework.security.web.session.HttpSessionDestroyedEvent;
67-
import org.springframework.security.web.session.SessionLimitStrategy;
67+
import org.springframework.security.web.session.SessionLimit;
6868
import org.springframework.security.web.session.SessionManagementFilter;
6969
import org.springframework.test.web.servlet.MockMvc;
7070
import org.springframework.test.web.servlet.MvcResult;
@@ -251,8 +251,8 @@ public void loginWhenUserLoggedInAndMaxSessionsOneInLambdaThenLoginPrevented() t
251251
}
252252

253253
@Test
254-
public void loginWhenAdminUserLoggedInAndSessionLimitStrategyIsConfiguredThenLoginSuccessfully() throws Exception {
255-
this.spring.register(ConcurrencyControlWithSessionLimitStrategyConfig.class).autowire();
254+
public void loginWhenAdminUserLoggedInAndSessionLimitIsConfiguredThenLoginSuccessfully() throws Exception {
255+
this.spring.register(ConcurrencyControlWithSessionLimitConfig.class).autowire();
256256
// @formatter:off
257257
MockHttpServletRequestBuilder requestBuilder = post("/login")
258258
.with(csrf())
@@ -277,8 +277,8 @@ public void loginWhenAdminUserLoggedInAndSessionLimitStrategyIsConfiguredThenLog
277277
}
278278

279279
@Test
280-
public void loginWhenAdminUserLoggedInAndSessionLimitStrategyIsConfiguredThenLoginPrevented() throws Exception {
281-
this.spring.register(ConcurrencyControlWithSessionLimitStrategyConfig.class).autowire();
280+
public void loginWhenAdminUserLoggedInAndSessionLimitIsConfiguredThenLoginPrevented() throws Exception {
281+
this.spring.register(ConcurrencyControlWithSessionLimitConfig.class).autowire();
282282
// @formatter:off
283283
MockHttpServletRequestBuilder requestBuilder = post("/login")
284284
.with(csrf())
@@ -306,8 +306,8 @@ public void loginWhenAdminUserLoggedInAndSessionLimitStrategyIsConfiguredThenLog
306306
}
307307

308308
@Test
309-
public void loginWhenUserLoggedInAndSessionLimitStrategyIsConfiguredThenLoginPrevented() throws Exception {
310-
this.spring.register(ConcurrencyControlWithSessionLimitStrategyConfig.class).autowire();
309+
public void loginWhenUserLoggedInAndSessionLimitIsConfiguredThenLoginPrevented() throws Exception {
310+
this.spring.register(ConcurrencyControlWithSessionLimitConfig.class).autowire();
311311
// @formatter:off
312312
MockHttpServletRequestBuilder requestBuilder = post("/login")
313313
.with(csrf())
@@ -704,16 +704,16 @@ UserDetailsService userDetailsService() {
704704

705705
@Configuration
706706
@EnableWebSecurity
707-
static class ConcurrencyControlWithSessionLimitStrategyConfig {
707+
static class ConcurrencyControlWithSessionLimitConfig {
708708

709709
@Bean
710-
SecurityFilterChain filterChain(HttpSecurity http, SessionLimitStrategy sessionLimitStrategy) throws Exception {
710+
SecurityFilterChain filterChain(HttpSecurity http, SessionLimit sessionLimit) throws Exception {
711711
// @formatter:off
712712
http
713713
.formLogin(withDefaults())
714714
.sessionManagement((sessionManagement) -> sessionManagement
715715
.sessionConcurrency((sessionConcurrency) -> sessionConcurrency
716-
.maximumSessions(sessionLimitStrategy)
716+
.maximumSessions(sessionLimit)
717717
.maxSessionsPreventsLogin(true)
718718
)
719719
);
@@ -727,7 +727,7 @@ UserDetailsService userDetailsService() {
727727
}
728728

729729
@Bean
730-
SessionLimitStrategy sessionLimitStrategy() {
730+
SessionLimit SessionLimit() {
731731
return (authentication) -> {
732732
if ("admin".equals(authentication.getName())) {
733733
return 2;

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
import org.springframework.security.config.test.SpringTestContext;
3636
import org.springframework.security.config.test.SpringTestContextExtension;
3737
import org.springframework.security.core.Authentication;
38-
import org.springframework.security.web.session.SessionLimitStrategy;
38+
import org.springframework.security.web.session.SessionLimit;
3939
import org.springframework.test.web.servlet.MockMvc;
4040
import org.springframework.test.web.servlet.ResultMatcher;
4141
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
@@ -955,7 +955,7 @@ public String ok() {
955955

956956
}
957957

958-
public static class CustomSessionLimit implements SessionLimitStrategy {
958+
public static class CustomSessionLimit implements SessionLimit {
959959

960960
@Override
961961
public Integer apply(Authentication authentication) {

docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2170,7 +2170,7 @@ Specify `-1` as the value to support unlimited sessions.
21702170

21712171
[[nsa-concurrency-control-max-sessions-ref]]
21722172
* **max-sessions-ref**
2173-
Allows injection of the SessionLimitStrategy instance used by the ConcurrentSessionControlAuthenticationStrategy
2173+
Allows injection of the SessionLimit instance used by the ConcurrentSessionControlAuthenticationStrategy
21742174

21752175
[[nsa-concurrency-control-session-registry-alias]]
21762176
* **session-registry-alias**

web/src/main/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlAuthenticationStrategy.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
3434
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
3535
import org.springframework.security.web.session.ConcurrentSessionFilter;
36-
import org.springframework.security.web.session.SessionLimitStrategy;
36+
import org.springframework.security.web.session.SessionLimit;
3737
import org.springframework.security.web.session.SessionManagementFilter;
3838
import org.springframework.util.Assert;
3939

@@ -77,7 +77,7 @@ public class ConcurrentSessionControlAuthenticationStrategy
7777

7878
private boolean exceptionIfMaximumExceeded = false;
7979

80-
private SessionLimitStrategy sessionLimitStrategy = SessionLimitStrategy.of(1);
80+
private SessionLimit sessionLimit = SessionLimit.of(1);
8181

8282
/**
8383
* @param sessionRegistry the session registry which should be updated when the
@@ -131,7 +131,7 @@ public void onAuthentication(Authentication authentication, HttpServletRequest r
131131
* @return either -1 meaning unlimited, or a positive integer to limit (never zero)
132132
*/
133133
protected int getMaximumSessionsForThisUser(Authentication authentication) {
134-
return this.sessionLimitStrategy.apply(authentication);
134+
return this.sessionLimit.apply(authentication);
135135
}
136136

137137
/**
@@ -173,24 +173,24 @@ public void setExceptionIfMaximumExceeded(boolean exceptionIfMaximumExceeded) {
173173
}
174174

175175
/**
176-
* Sets the <tt>sessionLimitStrategy</tt> property. The default value is 1. Use -1 for
176+
* Sets the <tt>sessionLimit</tt> property. The default value is 1. Use -1 for
177177
* unlimited sessions.
178178
* @param maximumSessions the maximum number of permitted sessions a user can have
179179
* open simultaneously.
180180
*/
181181
public void setMaximumSessions(int maximumSessions) {
182-
this.sessionLimitStrategy = SessionLimitStrategy.of(maximumSessions);
182+
this.sessionLimit = SessionLimit.of(maximumSessions);
183183
}
184184

185185
/**
186-
* Sets the <tt>sessionLimitStrategy</tt> property. The default value is 1. Use -1 for
186+
* Sets the <tt>sessionLimit</tt> property. The default value is 1. Use -1 for
187187
* unlimited sessions.
188-
* @param sessionLimitStrategy the session limit strategy
188+
* @param sessionLimit the session limit strategy
189189
* @since 6.5
190190
*/
191-
public void setMaximumSessions(SessionLimitStrategy sessionLimitStrategy) {
192-
Assert.notNull(sessionLimitStrategy, "sessionLimitStrategy cannot be null");
193-
this.sessionLimitStrategy = sessionLimitStrategy;
191+
public void setMaximumSessions(SessionLimit sessionLimit) {
192+
Assert.notNull(sessionLimit, "sessionLimit cannot be null");
193+
this.sessionLimit = sessionLimit;
194194
}
195195

196196
/**

web/src/main/java/org/springframework/security/web/session/SessionLimitStrategy.java renamed to web/src/main/java/org/springframework/security/web/session/SessionLimit.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,19 @@
2828
* @author Claudenir Freitas
2929
* @since 6.5
3030
*/
31-
public interface SessionLimitStrategy extends Function<Authentication, Integer> {
31+
public interface SessionLimit extends Function<Authentication, Integer> {
3232

3333
/**
3434
* Represents unlimited sessions.
3535
*/
36-
SessionLimitStrategy UNLIMITED = (authentication) -> -1;
36+
SessionLimit UNLIMITED = (authentication) -> -1;
3737

3838
/**
39-
* Creates a {@link SessionLimitStrategy} that always returns the given value for any
40-
* user
39+
* Creates a {@link SessionLimit} that always returns the given value for any user
4140
* @param maxSessions the maximum number of sessions allowed
42-
* @return a {@link SessionLimitStrategy} instance that returns the given value.
41+
* @return a {@link SessionLimit} instance that returns the given value.
4342
*/
44-
static SessionLimitStrategy of(int maxSessions) {
43+
static SessionLimit of(int maxSessions) {
4544
Assert.isTrue(maxSessions != 0,
4645
"MaximumLogins must be either -1 to allow unlimited logins, or a positive integer to specify a maximum");
4746
return (authentication) -> maxSessions;

web/src/test/java/org/springframework/security/web/authentication/session/ConcurrentSessionControlAuthenticationStrategyTests.java

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
import org.springframework.security.core.Authentication;
3535
import org.springframework.security.core.session.SessionInformation;
3636
import org.springframework.security.core.session.SessionRegistry;
37-
import org.springframework.security.web.session.SessionLimitStrategy;
37+
import org.springframework.security.web.session.SessionLimit;
3838

3939
import static org.assertj.core.api.Assertions.assertThat;
4040
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -151,69 +151,69 @@ public void onAuthenticationWhenMaxSessionsExceededByTwoThenTwoSessionsExpired()
151151
public void setMaximumSessionsWithNullValue() {
152152
assertThatExceptionOfType(IllegalArgumentException.class)
153153
.isThrownBy(() -> this.strategy.setMaximumSessions(null))
154-
.withMessage("sessionLimitStrategy cannot be null");
154+
.withMessage("sessionLimit cannot be null");
155155
}
156156

157157
@Test
158-
public void noRegisteredSessionUsingSessionLimitStrategy() {
158+
public void noRegisteredSessionUsingSessionLimit() {
159159
given(this.sessionRegistry.getAllSessions(any(), anyBoolean())).willReturn(Collections.emptyList());
160-
this.strategy.setMaximumSessions(SessionLimitStrategy.of(1));
160+
this.strategy.setMaximumSessions(SessionLimit.of(1));
161161
this.strategy.setExceptionIfMaximumExceeded(true);
162162
this.strategy.onAuthentication(this.authentication, this.request, this.response);
163163
// no exception
164164
}
165165

166166
@Test
167-
public void maxSessionsSameSessionIdUsingSessionLimitStrategy() {
167+
public void maxSessionsSameSessionIdUsingSessionLimit() {
168168
MockHttpSession session = new MockHttpSession(new MockServletContext(), this.sessionInformation.getSessionId());
169169
this.request.setSession(session);
170170
given(this.sessionRegistry.getAllSessions(any(), anyBoolean()))
171171
.willReturn(Collections.singletonList(this.sessionInformation));
172-
this.strategy.setMaximumSessions(SessionLimitStrategy.of(1));
172+
this.strategy.setMaximumSessions(SessionLimit.of(1));
173173
this.strategy.setExceptionIfMaximumExceeded(true);
174174
this.strategy.onAuthentication(this.authentication, this.request, this.response);
175175
// no exception
176176
}
177177

178178
@Test
179-
public void maxSessionsWithExceptionUsingSessionLimitStrategy() {
179+
public void maxSessionsWithExceptionUsingSessionLimit() {
180180
given(this.sessionRegistry.getAllSessions(any(), anyBoolean()))
181181
.willReturn(Collections.singletonList(this.sessionInformation));
182-
this.strategy.setMaximumSessions(SessionLimitStrategy.of(1));
182+
this.strategy.setMaximumSessions(SessionLimit.of(1));
183183
this.strategy.setExceptionIfMaximumExceeded(true);
184184
assertThatExceptionOfType(SessionAuthenticationException.class)
185185
.isThrownBy(() -> this.strategy.onAuthentication(this.authentication, this.request, this.response));
186186
}
187187

188188
@Test
189-
public void maxSessionsExpireExistingUserUsingSessionLimitStrategy() {
189+
public void maxSessionsExpireExistingUserUsingSessionLimit() {
190190
given(this.sessionRegistry.getAllSessions(any(), anyBoolean()))
191191
.willReturn(Collections.singletonList(this.sessionInformation));
192-
this.strategy.setMaximumSessions(SessionLimitStrategy.of(1));
192+
this.strategy.setMaximumSessions(SessionLimit.of(1));
193193
this.strategy.onAuthentication(this.authentication, this.request, this.response);
194194
assertThat(this.sessionInformation.isExpired()).isTrue();
195195
}
196196

197197
@Test
198-
public void maxSessionsExpireLeastRecentExistingUserUsingSessionLimitStrategy() {
198+
public void maxSessionsExpireLeastRecentExistingUserUsingSessionLimit() {
199199
SessionInformation moreRecentSessionInfo = new SessionInformation(this.authentication.getPrincipal(), "unique",
200200
new Date(1374766999999L));
201201
given(this.sessionRegistry.getAllSessions(any(), anyBoolean()))
202202
.willReturn(Arrays.asList(moreRecentSessionInfo, this.sessionInformation));
203-
this.strategy.setMaximumSessions(SessionLimitStrategy.of(2));
203+
this.strategy.setMaximumSessions(SessionLimit.of(2));
204204
this.strategy.onAuthentication(this.authentication, this.request, this.response);
205205
assertThat(this.sessionInformation.isExpired()).isTrue();
206206
}
207207

208208
@Test
209-
public void onAuthenticationWhenMaxSessionsExceededByTwoThenTwoSessionsExpiredUsingSessionLimitStrategy() {
209+
public void onAuthenticationWhenMaxSessionsExceededByTwoThenTwoSessionsExpiredUsingSessionLimit() {
210210
SessionInformation oldestSessionInfo = new SessionInformation(this.authentication.getPrincipal(), "unique1",
211211
new Date(1374766134214L));
212212
SessionInformation secondOldestSessionInfo = new SessionInformation(this.authentication.getPrincipal(),
213213
"unique2", new Date(1374766134215L));
214214
given(this.sessionRegistry.getAllSessions(any(), anyBoolean()))
215215
.willReturn(Arrays.asList(oldestSessionInfo, secondOldestSessionInfo, this.sessionInformation));
216-
this.strategy.setMaximumSessions(SessionLimitStrategy.of(2));
216+
this.strategy.setMaximumSessions(SessionLimit.of(2));
217217
this.strategy.onAuthentication(this.authentication, this.request, this.response);
218218
assertThat(oldestSessionInfo.isExpired()).isTrue();
219219
assertThat(secondOldestSessionInfo.isExpired()).isTrue();
@@ -222,7 +222,7 @@ public void onAuthenticationWhenMaxSessionsExceededByTwoThenTwoSessionsExpiredUs
222222

223223
@Test
224224
public void onAuthenticationWhenSessionLimitIsUnlimited() {
225-
this.strategy.setMaximumSessions(SessionLimitStrategy.UNLIMITED);
225+
this.strategy.setMaximumSessions(SessionLimit.UNLIMITED);
226226
this.strategy.onAuthentication(this.authentication, this.request, this.response);
227227
verifyNoInteractions(this.sessionRegistry);
228228
}

web/src/test/java/org/springframework/security/web/session/SessionLimitStrategyTests.java renamed to web/src/test/java/org/springframework/security/web/session/SessionLimitTests.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,28 +30,28 @@
3030
* @author Claudenir Freitas
3131
* @since 6.5
3232
*/
33-
class SessionLimitStrategyTests {
33+
class SessionLimitTests {
3434

3535
private final Authentication authentication = Mockito.mock(Authentication.class);
3636

3737
@Test
3838
void testUnlimitedInstance() {
39-
SessionLimitStrategy sessionLimit = SessionLimitStrategy.UNLIMITED;
39+
SessionLimit sessionLimit = SessionLimit.UNLIMITED;
4040
int result = sessionLimit.apply(this.authentication);
4141
assertThat(result).isEqualTo(-1);
4242
}
4343

4444
@ParameterizedTest
4545
@ValueSource(ints = { -1, 1, 2, 3 })
4646
void testInstanceWithValidMaxSessions(int maxSessions) {
47-
SessionLimitStrategy sessionLimit = SessionLimitStrategy.of(maxSessions);
47+
SessionLimit sessionLimit = SessionLimit.of(maxSessions);
4848
int result = sessionLimit.apply(this.authentication);
4949
assertThat(result).isEqualTo(maxSessions);
5050
}
5151

5252
@Test
5353
void testInstanceWithInvalidMaxSessions() {
54-
assertThatIllegalArgumentException().isThrownBy(() -> SessionLimitStrategy.of(0))
54+
assertThatIllegalArgumentException().isThrownBy(() -> SessionLimit.of(0))
5555
.withMessage(
5656
"MaximumLogins must be either -1 to allow unlimited logins, or a positive integer to specify a maximum");
5757
}

0 commit comments

Comments
 (0)