Skip to content

Commit 782f0dd

Browse files
authored
Merge branch 'spring-projects:main' into gh-16251
2 parents 7146c46 + 174f17e commit 782f0dd

File tree

306 files changed

+4443
-428
lines changed

Some content is hidden

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

306 files changed

+4443
-428
lines changed

.github/dco.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require:
2+
members: false

.github/workflows/continuous-integration-workflow.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ jobs:
7979
env:
8080
STRUCTURE101_LICENSEID: ${{ secrets.STRUCTURE101_LICENSEID }}
8181
run: |
82-
./gradlew check s101 -Ps101.licenseId="$STRUCTURE101_LICENSEID" --stacktrace
82+
./gradlew assemble && ./gradlew s101 -Ps101.licenseId="$STRUCTURE101_LICENSEID" --stacktrace
8383
deploy-artifacts:
8484
name: Deploy Artifacts
8585
needs: [ build, test, check-samples, check-tangles ]
@@ -116,7 +116,7 @@ jobs:
116116
send-notification:
117117
name: Send Notification
118118
needs: [ perform-release ]
119-
if: ${{ failure() || cancelled() }}
119+
if: ${{ !success() }}
120120
runs-on: ubuntu-latest
121121
steps:
122122
- name: Send Notification

.github/workflows/release-scheduler.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
strategy:
1212
matrix:
1313
# List of active maintenance branches.
14-
branch: [ main, 6.4.x, 6.3.x, 6.2.x, 5.8.x ]
14+
branch: [ main, 6.4.x, 6.3.x ]
1515
runs-on: ubuntu-latest
1616
steps:
1717
- name: Checkout

CONTRIBUTING.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ Please do your best to follow these steps.
9090
Don't worry if you don't get them all correct the first time, we will help you.
9191

9292
[[sign-cla]]
93-
1. If you have not previously done so, please sign the https://cla.spring.io/sign/spring[Contributor License Agreement].
94-
You will be reminded automatically when you submit the PR.
93+
1. All commits must include a __Signed-off-by__ trailer at the end of each commit message to indicate that the contributor agrees to the Developer Certificate of Origin.
94+
For additional details, please refer to the blog post https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring[Hello DCO, Goodbye CLA: Simplifying Contributions to Spring].
9595
[[create-an-issue]]
9696
1. Must you https://github.com/spring-projects/spring-security/issues/new/choose[create an issue] first? No, but it is recommended for features and larger bug fixes. It's easier discuss with the team first to determine the right fix or enhancement.
9797
For typos and straightforward bug fixes, starting with a pull request is encouraged.

build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ nohttp {
110110
source.builtBy(project(':spring-security-config').tasks.withType(RncToXsd))
111111
}
112112

113+
tasks.named('checkstyleNohttp') {
114+
maxHeapSize = '1g'
115+
}
116+
113117
tasks.register('cloneRepository', IncludeRepoTask) {
114118
repository = project.getProperties().get("repositoryName")
115119
ref = project.getProperties().get("ref")

buildSrc/src/main/java/s101/S101Plugin.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ private void configure(S101Configure configure) {
5050

5151
private void configure(JavaExec exec) {
5252
exec.setDescription("Runs Structure101 headless analysis, installing and configuring if necessary");
53-
exec.dependsOn("check");
53+
exec.dependsOn("assemble");
5454
Project project = exec.getProject();
5555
S101PluginExtension extension = project.getExtensions().getByType(S101PluginExtension.class);
5656
exec

cas/src/main/java/org/springframework/security/cas/jackson2/CasJackson2Module.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
* @since 4.2
4242
* @see org.springframework.security.jackson2.SecurityJackson2Modules
4343
*/
44+
@SuppressWarnings("serial")
4445
public class CasJackson2Module extends SimpleModule {
4546

4647
public CasJackson2Module() {

config/src/integration-test/java/org/springframework/security/config/annotation/configurers/WebAuthnWebDriverTests.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.junit.jupiter.api.BeforeEach;
3434
import org.junit.jupiter.api.Test;
3535
import org.openqa.selenium.By;
36+
import org.openqa.selenium.WebDriverException;
3637
import org.openqa.selenium.WebElement;
3738
import org.openqa.selenium.chrome.ChromeDriverService;
3839
import org.openqa.selenium.chrome.ChromeOptions;
@@ -273,12 +274,14 @@ private AbstractStringAssert<?> assertHasAlertStartingWith(String alertType, Str
273274

274275
/**
275276
* Await until the assertion passes. If the assertion fails, it will display the
276-
* assertion error in stdout.
277+
* assertion error in stdout. WebDriver-related exceptions are ignored, so that
278+
* {@code assertion}s can interact with the page and be retried on error, e.g.
279+
* {@code assertThat(this.driver.findElement(By.Id("some-id")).isNotNull()}.
277280
*/
278281
private void await(Supplier<AbstractAssert<?, ?>> assertion) {
279282
new FluentWait<>(this.driver).withTimeout(Duration.ofSeconds(2))
280283
.pollingEvery(Duration.ofMillis(100))
281-
.ignoring(AssertionError.class)
284+
.ignoring(AssertionError.class, WebDriverException.class)
282285
.until((d) -> {
283286
assertion.get();
284287
return true;

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

Lines changed: 22 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.springframework.http.HttpMethod;
4141
import org.springframework.lang.Nullable;
4242
import org.springframework.security.config.ObjectPostProcessor;
43+
import org.springframework.security.config.annotation.web.ServletRegistrationsSupport.RegistrationMapping;
4344
import org.springframework.security.config.annotation.web.configurers.AbstractConfigAttributeRequestMatcherRegistry;
4445
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
4546
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@@ -235,103 +236,31 @@ private boolean anyPathsDontStartWithLeadingSlash(String... patterns) {
235236
}
236237

237238
private RequestMatcher resolve(AntPathRequestMatcher ant, MvcRequestMatcher mvc, ServletContext servletContext) {
238-
Map<String, ? extends ServletRegistration> registrations = mappableServletRegistrations(servletContext);
239-
if (registrations.isEmpty()) {
239+
ServletRegistrationsSupport registrations = new ServletRegistrationsSupport(servletContext);
240+
Collection<RegistrationMapping> mappings = registrations.mappings();
241+
if (mappings.isEmpty()) {
240242
return new DispatcherServletDelegatingRequestMatcher(ant, mvc, new MockMvcRequestMatcher());
241243
}
242-
if (!hasDispatcherServlet(registrations)) {
244+
Collection<RegistrationMapping> dispatcherServletMappings = registrations.dispatcherServletMappings();
245+
if (dispatcherServletMappings.isEmpty()) {
243246
return new DispatcherServletDelegatingRequestMatcher(ant, mvc, new MockMvcRequestMatcher());
244247
}
245-
ServletRegistration dispatcherServlet = requireOneRootDispatcherServlet(registrations);
246-
if (dispatcherServlet != null) {
247-
if (registrations.size() == 1) {
248-
return mvc;
249-
}
250-
return new DispatcherServletDelegatingRequestMatcher(ant, mvc, servletContext);
251-
}
252-
dispatcherServlet = requireOnlyPathMappedDispatcherServlet(registrations);
253-
if (dispatcherServlet != null) {
254-
String mapping = dispatcherServlet.getMappings().iterator().next();
255-
mvc.setServletPath(mapping.substring(0, mapping.length() - 2));
256-
return mvc;
248+
if (dispatcherServletMappings.size() > 1) {
249+
String errorMessage = computeErrorMessage(servletContext.getServletRegistrations().values());
250+
throw new IllegalArgumentException(errorMessage);
257251
}
258-
String errorMessage = computeErrorMessage(registrations.values());
259-
throw new IllegalArgumentException(errorMessage);
260-
}
261-
262-
private Map<String, ? extends ServletRegistration> mappableServletRegistrations(ServletContext servletContext) {
263-
Map<String, ServletRegistration> mappable = new LinkedHashMap<>();
264-
for (Map.Entry<String, ? extends ServletRegistration> entry : servletContext.getServletRegistrations()
265-
.entrySet()) {
266-
if (!entry.getValue().getMappings().isEmpty()) {
267-
mappable.put(entry.getKey(), entry.getValue());
268-
}
269-
}
270-
return mappable;
271-
}
272-
273-
private boolean hasDispatcherServlet(Map<String, ? extends ServletRegistration> registrations) {
274-
if (registrations == null) {
275-
return false;
252+
RegistrationMapping dispatcherServlet = dispatcherServletMappings.iterator().next();
253+
if (mappings.size() > 1 && !dispatcherServlet.isDefault()) {
254+
String errorMessage = computeErrorMessage(servletContext.getServletRegistrations().values());
255+
throw new IllegalArgumentException(errorMessage);
276256
}
277-
for (ServletRegistration registration : registrations.values()) {
278-
if (isDispatcherServlet(registration)) {
279-
return true;
280-
}
281-
}
282-
return false;
283-
}
284-
285-
private ServletRegistration requireOneRootDispatcherServlet(
286-
Map<String, ? extends ServletRegistration> registrations) {
287-
ServletRegistration rootDispatcherServlet = null;
288-
for (ServletRegistration registration : registrations.values()) {
289-
if (!isDispatcherServlet(registration)) {
290-
continue;
291-
}
292-
if (registration.getMappings().size() > 1) {
293-
return null;
294-
}
295-
if (!"/".equals(registration.getMappings().iterator().next())) {
296-
return null;
297-
}
298-
rootDispatcherServlet = registration;
299-
}
300-
return rootDispatcherServlet;
301-
}
302-
303-
private ServletRegistration requireOnlyPathMappedDispatcherServlet(
304-
Map<String, ? extends ServletRegistration> registrations) {
305-
ServletRegistration pathDispatcherServlet = null;
306-
for (ServletRegistration registration : registrations.values()) {
307-
if (!isDispatcherServlet(registration)) {
308-
return null;
309-
}
310-
if (registration.getMappings().size() > 1) {
311-
return null;
312-
}
313-
String mapping = registration.getMappings().iterator().next();
314-
if (!mapping.startsWith("/") || !mapping.endsWith("/*")) {
315-
return null;
316-
}
317-
if (pathDispatcherServlet != null) {
318-
return null;
257+
if (dispatcherServlet.isDefault()) {
258+
if (mappings.size() == 1) {
259+
return mvc;
319260
}
320-
pathDispatcherServlet = registration;
321-
}
322-
return pathDispatcherServlet;
323-
}
324-
325-
private boolean isDispatcherServlet(ServletRegistration registration) {
326-
Class<?> dispatcherServlet = ClassUtils.resolveClassName("org.springframework.web.servlet.DispatcherServlet",
327-
null);
328-
try {
329-
Class<?> clazz = Class.forName(registration.getClassName());
330-
return dispatcherServlet.isAssignableFrom(clazz);
331-
}
332-
catch (ClassNotFoundException ex) {
333-
return false;
261+
return new DispatcherServletDelegatingRequestMatcher(ant, mvc);
334262
}
263+
return mvc;
335264
}
336265

337266
private static String computeErrorMessage(Collection<? extends ServletRegistration> registrations) {
@@ -518,18 +447,12 @@ public boolean matches(HttpServletRequest request) {
518447

519448
static class DispatcherServletRequestMatcher implements RequestMatcher {
520449

521-
private final ServletContext servletContext;
522-
523-
DispatcherServletRequestMatcher(ServletContext servletContext) {
524-
this.servletContext = servletContext;
525-
}
526-
527450
@Override
528451
public boolean matches(HttpServletRequest request) {
529452
String name = request.getHttpServletMapping().getServletName();
530-
ServletRegistration registration = this.servletContext.getServletRegistration(name);
453+
ServletRegistration registration = request.getServletContext().getServletRegistration(name);
531454
Assert.notNull(registration,
532-
() -> computeErrorMessage(this.servletContext.getServletRegistrations().values()));
455+
() -> computeErrorMessage(request.getServletContext().getServletRegistrations().values()));
533456
try {
534457
Class<?> clazz = Class.forName(registration.getClassName());
535458
return DispatcherServlet.class.isAssignableFrom(clazz);
@@ -549,10 +472,8 @@ static class DispatcherServletDelegatingRequestMatcher implements RequestMatcher
549472

550473
private final RequestMatcher dispatcherServlet;
551474

552-
DispatcherServletDelegatingRequestMatcher(AntPathRequestMatcher ant, MvcRequestMatcher mvc,
553-
ServletContext servletContext) {
554-
this(ant, mvc, new OrRequestMatcher(new MockMvcRequestMatcher(),
555-
new DispatcherServletRequestMatcher(servletContext)));
475+
DispatcherServletDelegatingRequestMatcher(AntPathRequestMatcher ant, MvcRequestMatcher mvc) {
476+
this(ant, mvc, new OrRequestMatcher(new MockMvcRequestMatcher(), new DispatcherServletRequestMatcher()));
556477
}
557478

558479
DispatcherServletDelegatingRequestMatcher(AntPathRequestMatcher ant, MvcRequestMatcher mvc,
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2002-2025 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.config.annotation.web;
18+
19+
import java.util.ArrayList;
20+
import java.util.Collection;
21+
import java.util.Map;
22+
23+
import jakarta.servlet.ServletContext;
24+
import jakarta.servlet.ServletRegistration;
25+
26+
import org.springframework.util.ClassUtils;
27+
28+
class ServletRegistrationsSupport {
29+
30+
private final Collection<RegistrationMapping> registrations;
31+
32+
ServletRegistrationsSupport(ServletContext servletContext) {
33+
Map<String, ? extends ServletRegistration> registrations = servletContext.getServletRegistrations();
34+
Collection<RegistrationMapping> mappings = new ArrayList<>();
35+
for (Map.Entry<String, ? extends ServletRegistration> entry : registrations.entrySet()) {
36+
if (!entry.getValue().getMappings().isEmpty()) {
37+
for (String mapping : entry.getValue().getMappings()) {
38+
mappings.add(new RegistrationMapping(entry.getValue(), mapping));
39+
}
40+
}
41+
}
42+
this.registrations = mappings;
43+
}
44+
45+
Collection<RegistrationMapping> dispatcherServletMappings() {
46+
Collection<RegistrationMapping> mappings = new ArrayList<>();
47+
for (RegistrationMapping registration : this.registrations) {
48+
if (registration.isDispatcherServlet()) {
49+
mappings.add(registration);
50+
}
51+
}
52+
return mappings;
53+
}
54+
55+
Collection<RegistrationMapping> mappings() {
56+
return this.registrations;
57+
}
58+
59+
record RegistrationMapping(ServletRegistration registration, String mapping) {
60+
boolean isDispatcherServlet() {
61+
Class<?> dispatcherServlet = ClassUtils
62+
.resolveClassName("org.springframework.web.servlet.DispatcherServlet", null);
63+
try {
64+
Class<?> clazz = Class.forName(this.registration.getClassName());
65+
return dispatcherServlet.isAssignableFrom(clazz);
66+
}
67+
catch (ClassNotFoundException ex) {
68+
return false;
69+
}
70+
}
71+
72+
boolean isDefault() {
73+
return "/".equals(this.mapping);
74+
}
75+
}
76+
77+
}

0 commit comments

Comments
 (0)