Skip to content

Commit 7146c46

Browse files
authored
Merge branch 'spring-projects:main' into spring-projectsgh-16251
2 parents a3a8f23 + c72359b commit 7146c46

File tree

55 files changed

+1578
-69
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1578
-69
lines changed

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

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,28 +18,45 @@
1818

1919
import java.util.List;
2020

21+
import jakarta.servlet.Filter;
22+
import org.apache.commons.logging.Log;
23+
import org.apache.commons.logging.LogFactory;
24+
2125
import org.springframework.security.web.DefaultSecurityFilterChain;
2226
import org.springframework.security.web.FilterChainProxy;
2327
import org.springframework.security.web.SecurityFilterChain;
28+
import org.springframework.security.web.UnreachableFilterChainException;
29+
import org.springframework.security.web.access.intercept.AuthorizationFilter;
30+
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
2431
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
2532

2633
/**
2734
* A filter chain validator for filter chains built by {@link WebSecurity}
2835
*
36+
* @author Josh Cummings
37+
* @author Max Batischev
2938
* @since 6.5
3039
*/
3140
final class WebSecurityFilterChainValidator implements FilterChainProxy.FilterChainValidator {
3241

42+
private final Log logger = LogFactory.getLog(getClass());
43+
3344
@Override
3445
public void validate(FilterChainProxy filterChainProxy) {
3546
List<SecurityFilterChain> chains = filterChainProxy.getFilterChains();
47+
checkForAnyRequestRequestMatcher(chains);
48+
checkForDuplicateMatchers(chains);
49+
checkAuthorizationFilters(chains);
50+
}
51+
52+
private void checkForAnyRequestRequestMatcher(List<SecurityFilterChain> chains) {
3653
DefaultSecurityFilterChain anyRequestFilterChain = null;
3754
for (SecurityFilterChain chain : chains) {
3855
if (anyRequestFilterChain != null) {
3956
String message = "A filter chain that matches any request [" + anyRequestFilterChain
4057
+ "] has already been configured, which means that this filter chain [" + chain
4158
+ "] will never get invoked. Please use `HttpSecurity#securityMatcher` to ensure that there is only one filter chain configured for 'any request' and that the 'any request' filter chain is published last.";
42-
throw new IllegalArgumentException(message);
59+
throw new UnreachableFilterChainException(message, anyRequestFilterChain, chain);
4360
}
4461
if (chain instanceof DefaultSecurityFilterChain defaultChain) {
4562
if (defaultChain.getRequestMatcher() instanceof AnyRequestMatcher) {
@@ -49,4 +66,48 @@ public void validate(FilterChainProxy filterChainProxy) {
4966
}
5067
}
5168

69+
private void checkForDuplicateMatchers(List<SecurityFilterChain> chains) {
70+
DefaultSecurityFilterChain filterChain = null;
71+
for (SecurityFilterChain chain : chains) {
72+
if (filterChain != null) {
73+
if (chain instanceof DefaultSecurityFilterChain defaultChain) {
74+
if (defaultChain.getRequestMatcher().equals(filterChain.getRequestMatcher())) {
75+
throw new UnreachableFilterChainException(
76+
"The FilterChainProxy contains two filter chains using the" + " matcher "
77+
+ defaultChain.getRequestMatcher(),
78+
filterChain, defaultChain);
79+
}
80+
}
81+
}
82+
if (chain instanceof DefaultSecurityFilterChain defaultChain) {
83+
filterChain = defaultChain;
84+
}
85+
}
86+
}
87+
88+
private void checkAuthorizationFilters(List<SecurityFilterChain> chains) {
89+
Filter authorizationFilter = null;
90+
Filter filterSecurityInterceptor = null;
91+
for (SecurityFilterChain chain : chains) {
92+
for (Filter filter : chain.getFilters()) {
93+
if (filter instanceof AuthorizationFilter) {
94+
authorizationFilter = filter;
95+
}
96+
if (filter instanceof FilterSecurityInterceptor) {
97+
filterSecurityInterceptor = filter;
98+
}
99+
}
100+
if (authorizationFilter != null && filterSecurityInterceptor != null) {
101+
this.logger.warn(
102+
"It is not recommended to use authorizeRequests in the configuration. Please only use authorizeHttpRequests");
103+
}
104+
if (filterSecurityInterceptor != null) {
105+
this.logger.warn(
106+
"Usage of authorizeRequests is deprecated. Please use authorizeHttpRequests in the configuration");
107+
}
108+
authorizationFilter = null;
109+
filterSecurityInterceptor = null;
110+
}
111+
}
112+
52113
}

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

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
4848
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
4949
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
50+
import org.springframework.security.web.authentication.session.SessionLimit;
5051
import org.springframework.security.web.context.DelegatingSecurityContextRepository;
5152
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
5253
import org.springframework.security.web.context.NullSecurityContextRepository;
@@ -123,7 +124,7 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
123124

124125
private SessionRegistry sessionRegistry;
125126

126-
private Integer maximumSessions;
127+
private SessionLimit sessionLimit;
127128

128129
private String expiredUrl;
129130

@@ -329,7 +330,7 @@ public SessionManagementConfigurer<H> sessionFixation(
329330
* @return the {@link SessionManagementConfigurer} for further customizations
330331
*/
331332
public ConcurrencyControlConfigurer maximumSessions(int maximumSessions) {
332-
this.maximumSessions = maximumSessions;
333+
this.sessionLimit = SessionLimit.of(maximumSessions);
333334
this.propertiesThatRequireImplicitAuthentication.add("maximumSessions = " + maximumSessions);
334335
return new ConcurrencyControlConfigurer();
335336
}
@@ -570,7 +571,7 @@ private SessionAuthenticationStrategy getSessionAuthenticationStrategy(H http) {
570571
SessionRegistry sessionRegistry = getSessionRegistry(http);
571572
ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlStrategy = new ConcurrentSessionControlAuthenticationStrategy(
572573
sessionRegistry);
573-
concurrentSessionControlStrategy.setMaximumSessions(this.maximumSessions);
574+
concurrentSessionControlStrategy.setMaximumSessions(this.sessionLimit);
574575
concurrentSessionControlStrategy.setExceptionIfMaximumExceeded(this.maxSessionsPreventsLogin);
575576
concurrentSessionControlStrategy = postProcess(concurrentSessionControlStrategy);
576577
RegisterSessionAuthenticationStrategy registerSessionStrategy = new RegisterSessionAuthenticationStrategy(
@@ -614,7 +615,7 @@ private void registerDelegateApplicationListener(H http, ApplicationListener<?>
614615
* @return
615616
*/
616617
private boolean isConcurrentSessionControlEnabled() {
617-
return this.maximumSessions != null;
618+
return this.sessionLimit != null;
618619
}
619620

620621
/**
@@ -706,7 +707,19 @@ private ConcurrencyControlConfigurer() {
706707
* @return the {@link ConcurrencyControlConfigurer} for further customizations
707708
*/
708709
public ConcurrencyControlConfigurer maximumSessions(int maximumSessions) {
709-
SessionManagementConfigurer.this.maximumSessions = maximumSessions;
710+
SessionManagementConfigurer.this.sessionLimit = SessionLimit.of(maximumSessions);
711+
return this;
712+
}
713+
714+
/**
715+
* Determines the behaviour when a session limit is detected.
716+
* @param sessionLimit the {@link SessionLimit} to check the maximum number of
717+
* sessions for a user
718+
* @return the {@link ConcurrencyControlConfigurer} for further customizations
719+
* @since 6.5
720+
*/
721+
public ConcurrencyControlConfigurer maximumSessions(SessionLimit sessionLimit) {
722+
SessionManagementConfigurer.this.sessionLimit = sessionLimit;
710723
return this;
711724
}
712725

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

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.springframework.security.web.FilterChainProxy;
4040
import org.springframework.security.web.FilterInvocation;
4141
import org.springframework.security.web.SecurityFilterChain;
42+
import org.springframework.security.web.UnreachableFilterChainException;
4243
import org.springframework.security.web.access.ExceptionTranslationFilter;
4344
import org.springframework.security.web.access.intercept.AuthorizationFilter;
4445
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
@@ -53,7 +54,6 @@
5354
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
5455
import org.springframework.security.web.session.SessionManagementFilter;
5556
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
56-
import org.springframework.security.web.util.matcher.RequestMatcher;
5757

5858
public class DefaultFilterChainValidator implements FilterChainProxy.FilterChainValidator {
5959

@@ -69,31 +69,67 @@ public void validate(FilterChainProxy fcp) {
6969
}
7070
checkPathOrder(new ArrayList<>(fcp.getFilterChains()));
7171
checkForDuplicateMatchers(new ArrayList<>(fcp.getFilterChains()));
72+
checkAuthorizationFilters(new ArrayList<>(fcp.getFilterChains()));
7273
}
7374

7475
private void checkPathOrder(List<SecurityFilterChain> filterChains) {
7576
// Check that the universal pattern is listed at the end, if at all
7677
Iterator<SecurityFilterChain> chains = filterChains.iterator();
7778
while (chains.hasNext()) {
78-
RequestMatcher matcher = ((DefaultSecurityFilterChain) chains.next()).getRequestMatcher();
79-
if (AnyRequestMatcher.INSTANCE.equals(matcher) && chains.hasNext()) {
80-
throw new IllegalArgumentException("A universal match pattern ('/**') is defined "
81-
+ " before other patterns in the filter chain, causing them to be ignored. Please check the "
82-
+ "ordering in your <security:http> namespace or FilterChainProxy bean configuration");
79+
if (chains.next() instanceof DefaultSecurityFilterChain securityFilterChain) {
80+
if (AnyRequestMatcher.INSTANCE.equals(securityFilterChain.getRequestMatcher()) && chains.hasNext()) {
81+
throw new UnreachableFilterChainException("A universal match pattern ('/**') is defined "
82+
+ " before other patterns in the filter chain, causing them to be ignored. Please check the "
83+
+ "ordering in your <security:http> namespace or FilterChainProxy bean configuration",
84+
securityFilterChain, chains.next());
85+
}
8386
}
8487
}
8588
}
8689

8790
private void checkForDuplicateMatchers(List<SecurityFilterChain> chains) {
88-
while (chains.size() > 1) {
89-
DefaultSecurityFilterChain chain = (DefaultSecurityFilterChain) chains.remove(0);
90-
for (SecurityFilterChain test : chains) {
91-
if (chain.getRequestMatcher().equals(((DefaultSecurityFilterChain) test).getRequestMatcher())) {
92-
throw new IllegalArgumentException("The FilterChainProxy contains two filter chains using the"
93-
+ " matcher " + chain.getRequestMatcher() + ". If you are using multiple <http> namespace "
94-
+ "elements, you must use a 'pattern' attribute to define the request patterns to which they apply.");
91+
DefaultSecurityFilterChain filterChain = null;
92+
for (SecurityFilterChain chain : chains) {
93+
if (filterChain != null) {
94+
if (chain instanceof DefaultSecurityFilterChain defaultChain) {
95+
if (defaultChain.getRequestMatcher().equals(filterChain.getRequestMatcher())) {
96+
throw new UnreachableFilterChainException(
97+
"The FilterChainProxy contains two filter chains using the" + " matcher "
98+
+ defaultChain.getRequestMatcher()
99+
+ ". If you are using multiple <http> namespace "
100+
+ "elements, you must use a 'pattern' attribute to define the request patterns to which they apply.",
101+
defaultChain, chain);
102+
}
95103
}
96104
}
105+
if (chain instanceof DefaultSecurityFilterChain defaultChain) {
106+
filterChain = defaultChain;
107+
}
108+
}
109+
}
110+
111+
private void checkAuthorizationFilters(List<SecurityFilterChain> chains) {
112+
Filter authorizationFilter = null;
113+
Filter filterSecurityInterceptor = null;
114+
for (SecurityFilterChain chain : chains) {
115+
for (Filter filter : chain.getFilters()) {
116+
if (filter instanceof AuthorizationFilter) {
117+
authorizationFilter = filter;
118+
}
119+
if (filter instanceof FilterSecurityInterceptor) {
120+
filterSecurityInterceptor = filter;
121+
}
122+
}
123+
if (authorizationFilter != null && filterSecurityInterceptor != null) {
124+
this.logger.warn(
125+
"It is not recommended to use authorizeRequests in the configuration. Please only use authorizeHttpRequests");
126+
}
127+
if (filterSecurityInterceptor != null) {
128+
this.logger.warn(
129+
"Usage of authorizeRequests is deprecated. Please use authorizeHttpRequests in the configuration");
130+
}
131+
authorizationFilter = null;
132+
filterSecurityInterceptor = null;
97133
}
98134
}
99135

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ class HttpConfigurationBuilder {
122122

123123
private static final String ATT_SESSION_AUTH_STRATEGY_REF = "session-authentication-strategy-ref";
124124

125+
private static final String ATT_MAX_SESSIONS_REF = "max-sessions-ref";
126+
127+
private static final String ATT_MAX_SESSIONS = "max-sessions";
128+
125129
private static final String ATT_SESSION_AUTH_ERROR_URL = "session-authentication-error-url";
126130

127131
private static final String ATT_SECURITY_CONTEXT_HOLDER_STRATEGY = "security-context-holder-strategy-ref";
@@ -485,10 +489,16 @@ else if (StringUtils.hasText(sessionAuthStratRef)) {
485489
concurrentSessionStrategy.addConstructorArgValue(this.sessionRegistryRef);
486490
String maxSessions = this.pc.getReaderContext()
487491
.getEnvironment()
488-
.resolvePlaceholders(sessionCtrlElt.getAttribute("max-sessions"));
492+
.resolvePlaceholders(sessionCtrlElt.getAttribute(ATT_MAX_SESSIONS));
489493
if (StringUtils.hasText(maxSessions)) {
490494
concurrentSessionStrategy.addPropertyValue("maximumSessions", maxSessions);
491495
}
496+
String maxSessionsRef = this.pc.getReaderContext()
497+
.getEnvironment()
498+
.resolvePlaceholders(sessionCtrlElt.getAttribute(ATT_MAX_SESSIONS_REF));
499+
if (StringUtils.hasText(maxSessionsRef)) {
500+
concurrentSessionStrategy.addPropertyReference("maximumSessions", maxSessionsRef);
501+
}
492502
String exceptionIfMaximumExceeded = sessionCtrlElt.getAttribute("error-if-maximum-exceeded");
493503
if (StringUtils.hasText(exceptionIfMaximumExceeded)) {
494504
concurrentSessionStrategy.addPropertyValue("exceptionIfMaximumExceeded", exceptionIfMaximumExceeded);
@@ -591,6 +601,12 @@ private void createConcurrencyControlFilterAndSessionRegistry(Element element) {
591601
.error("Cannot use 'expired-url' attribute and 'expired-session-strategy-ref'" + " attribute together.",
592602
source);
593603
}
604+
String maxSessions = element.getAttribute(ATT_MAX_SESSIONS);
605+
String maxSessionsRef = element.getAttribute(ATT_MAX_SESSIONS_REF);
606+
if (StringUtils.hasText(maxSessions) && StringUtils.hasText(maxSessionsRef)) {
607+
this.pc.getReaderContext()
608+
.error("Cannot use 'max-sessions' attribute and 'max-sessions-ref' attribute together.", source);
609+
}
594610
if (StringUtils.hasText(expiryUrl)) {
595611
BeanDefinitionBuilder expiredSessionBldr = BeanDefinitionBuilder
596612
.rootBeanDefinition(SimpleRedirectSessionInformationExpiredStrategy.class);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ public BeanDefinition parse(Element element, ParserContext pc) {
146146
BeanMetadataElement saml2LogoutRequestSuccessHandler = BeanDefinitionBuilder
147147
.rootBeanDefinition(Saml2RelyingPartyInitiatedLogoutSuccessHandler.class)
148148
.addConstructorArgValue(logoutRequestResolver)
149+
.addPropertyValue("logoutRequestRepository", logoutRequestRepository)
149150
.getBeanDefinition();
150151
this.logoutFilter = BeanDefinitionBuilder.rootBeanDefinition(LogoutFilter.class)
151152
.addConstructorArgValue(saml2LogoutRequestSuccessHandler)

config/src/main/kotlin/org/springframework/security/config/annotation/web/session/SessionConcurrencyDsl.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ package org.springframework.security.config.annotation.web.session
1919
import org.springframework.security.config.annotation.web.builders.HttpSecurity
2020
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer
2121
import org.springframework.security.core.session.SessionRegistry
22+
import org.springframework.security.web.authentication.session.SessionLimit
2223
import org.springframework.security.web.session.SessionInformationExpiredStrategy
24+
import org.springframework.util.Assert
2325

2426
/**
2527
* A Kotlin DSL to configure the behaviour of multiple sessions using idiomatic
@@ -44,12 +46,21 @@ class SessionConcurrencyDsl {
4446
var expiredSessionStrategy: SessionInformationExpiredStrategy? = null
4547
var maxSessionsPreventsLogin: Boolean? = null
4648
var sessionRegistry: SessionRegistry? = null
49+
private var sessionLimit: SessionLimit? = null
50+
51+
fun maximumSessions(max: SessionLimit) {
52+
this.sessionLimit = max
53+
}
4754

4855
internal fun get(): (SessionManagementConfigurer<HttpSecurity>.ConcurrencyControlConfigurer) -> Unit {
56+
Assert.isTrue(maximumSessions == null || sessionLimit == null, "You cannot specify maximumSessions as both an Int and a SessionLimit. Please use only one.")
4957
return { sessionConcurrencyControl ->
5058
maximumSessions?.also {
5159
sessionConcurrencyControl.maximumSessions(maximumSessions!!)
5260
}
61+
sessionLimit?.also {
62+
sessionConcurrencyControl.maximumSessions(sessionLimit!!)
63+
}
5364
expiredUrl?.also {
5465
sessionConcurrencyControl.expiredUrl(expiredUrl)
5566
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,9 @@ concurrency-control =
934934
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}?
937+
concurrency-control.attlist &=
938+
## Allows injection of the SessionLimit instance used by the ConcurrentSessionControlAuthenticationStrategy
939+
attribute max-sessions-ref {xsd:token}?
937940
concurrency-control.attlist &=
938941
## 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.
939942
attribute expired-url {xsd:token}?

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2688,6 +2688,13 @@
26882688
</xs:documentation>
26892689
</xs:annotation>
26902690
</xs:attribute>
2691+
<xs:attribute name="max-sessions-ref" type="xs:token">
2692+
<xs:annotation>
2693+
<xs:documentation>Allows injection of the SessionLimit instance used by the
2694+
ConcurrentSessionControlAuthenticationStrategy
2695+
</xs:documentation>
2696+
</xs:annotation>
2697+
</xs:attribute>
26912698
<xs:attribute name="expired-url" type="xs:token">
26922699
<xs:annotation>
26932700
<xs:documentation>The URL a user will be redirected to if they attempt to use a session which has been

0 commit comments

Comments
 (0)