Skip to content

Commit 938ffe8

Browse files
authored
Merge branch 'spring-projects:main' into csrf-docs
2 parents 6e3ffd9 + e2f98db commit 938ffe8

File tree

86 files changed

+3446
-1216
lines changed

Some content is hidden

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

86 files changed

+3446
-1216
lines changed

.github/workflows/mark-duplicate-dependabot-prs.yml

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
1-
name: Mark Duplicate PRs
1+
name: Mark Duplicate Dependabot PRs
22

33
on:
44
pull_request:
55
types: [closed]
66

77
jobs:
8-
debug:
9-
runs-on: ubuntu-latest
10-
steps:
11-
- name: Debug Event Payload
12-
run: |
13-
echo "Merged: ${{ github.event.pull_request.merged }}"
14-
echo "User Login: ${{ github.event.pull_request.user.login }}"
15-
168
check_duplicate_prs:
179
runs-on: ubuntu-latest
1810
if: github.event.pull_request.merged == true && github.event.pull_request.user.login == 'dependabot[bot]'
@@ -33,7 +25,7 @@ jobs:
3325
DEPENDENCY_NAME: ${{ steps.extract.outputs.dependency_name }}
3426
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3527
run: |
36-
PRS=$(gh pr list --search "milestone:${{ github.event.pull_request.milestone.title }} is:merged $DEPENDENCY_NAME" --json number --jq 'map(.number) | join(",")')
28+
PRS=$(gh pr list --search 'milestone:${{ github.event.pull_request.milestone.title }} is:merged in:title "$DEPENDENCY_NAME"' --json number --jq 'map(.number) | join(",")')
3729
echo "prs=$PRS" >> $GITHUB_OUTPUT
3830
3931
- name: Label Duplicate PRs

aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PreAuthorizeAspectTests.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ public class PreAuthorizeAspectTests {
4747

4848
private PrePostSecured prePostSecured = new PrePostSecured();
4949

50+
private MultipleInterfaces multiple = new MultipleInterfaces();
51+
5052
@BeforeEach
5153
public final void setUp() {
5254
MockitoAnnotations.initMocks(this);
@@ -110,6 +112,12 @@ public void nestedDenyAllPreAuthorizeDeniesAccess() {
110112
.isThrownBy(() -> this.secured.myObject().denyAllMethod());
111113
}
112114

115+
@Test
116+
public void multipleInterfacesPreAuthorizeAllows() {
117+
// aspectj doesn't inherit annotations
118+
this.multiple.securedMethod();
119+
}
120+
113121
interface SecuredInterface {
114122

115123
@PreAuthorize("hasRole('X')")
@@ -177,4 +185,19 @@ void denyAllMethod() {
177185

178186
}
179187

188+
interface AnotherSecuredInterface {
189+
190+
@PreAuthorize("hasRole('Y')")
191+
void securedMethod();
192+
193+
}
194+
195+
static class MultipleInterfaces implements SecuredInterface, AnotherSecuredInterface {
196+
197+
@Override
198+
public void securedMethod() {
199+
}
200+
201+
}
202+
180203
}

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

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ private boolean isDispatcherServlet(ServletRegistration registration) {
315315
}
316316
}
317317

318-
private String computeErrorMessage(Collection<? extends ServletRegistration> registrations) {
318+
private static String computeErrorMessage(Collection<? extends ServletRegistration> registrations) {
319319
String template = "This method cannot decide whether these patterns are Spring MVC patterns or not. "
320320
+ "If this endpoint is a Spring MVC endpoint, please use requestMatchers(MvcRequestMatcher); "
321321
+ "otherwise, please use requestMatchers(AntPathRequestMatcher).\n\n"
@@ -509,7 +509,7 @@ static class DispatcherServletRequestMatcher implements RequestMatcher {
509509
public boolean matches(HttpServletRequest request) {
510510
String name = request.getHttpServletMapping().getServletName();
511511
ServletRegistration registration = this.servletContext.getServletRegistration(name);
512-
Assert.notNull(name, "Failed to find servlet [" + name + "] in the servlet context");
512+
Assert.notNull(registration, computeErrorMessage(this.servletContext.getServletRegistrations().values()));
513513
try {
514514
Class<?> clazz = Class.forName(registration.getClassName());
515515
return DispatcherServlet.class.isAssignableFrom(clazz);
@@ -551,18 +551,12 @@ RequestMatcher requestMatcher(HttpServletRequest request) {
551551

552552
@Override
553553
public boolean matches(HttpServletRequest request) {
554-
if (this.dispatcherServlet.matches(request)) {
555-
return this.mvc.matches(request);
556-
}
557-
return this.ant.matches(request);
554+
return requestMatcher(request).matches(request);
558555
}
559556

560557
@Override
561558
public MatchResult matcher(HttpServletRequest request) {
562-
if (this.dispatcherServlet.matches(request)) {
563-
return this.mvc.matcher(request);
564-
}
565-
return this.ant.matcher(request);
559+
return requestMatcher(request).matcher(request);
566560
}
567561

568562
@Override

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

Lines changed: 82 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,13 @@
1616

1717
package org.springframework.security.config.annotation.web.configurers.saml2;
1818

19+
import java.util.ArrayList;
1920
import java.util.LinkedHashMap;
21+
import java.util.List;
2022
import java.util.Map;
2123

24+
import jakarta.servlet.http.HttpServletRequest;
25+
2226
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
2327
import org.springframework.context.ApplicationContext;
2428
import org.springframework.security.authentication.AuthenticationManager;
@@ -33,6 +37,7 @@
3337
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
3438
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
3539
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
40+
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations;
3641
import org.springframework.security.saml2.provider.service.web.HttpSessionSaml2AuthenticationRequestRepository;
3742
import org.springframework.security.saml2.provider.service.web.OpenSamlAuthenticationTokenConverter;
3843
import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository;
@@ -50,6 +55,7 @@
5055
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
5156
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
5257
import org.springframework.security.web.util.matcher.OrRequestMatcher;
58+
import org.springframework.security.web.util.matcher.ParameterRequestMatcher;
5359
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
5460
import org.springframework.security.web.util.matcher.RequestMatcher;
5561
import org.springframework.security.web.util.matcher.RequestMatchers;
@@ -111,7 +117,13 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
111117

112118
private String loginPage;
113119

114-
private String authenticationRequestUri = Saml2AuthenticationRequestResolver.DEFAULT_AUTHENTICATION_REQUEST_URI;
120+
private String authenticationRequestUri = "/saml2/authenticate";
121+
122+
private String[] authenticationRequestParams = { "registrationId={registrationId}" };
123+
124+
private RequestMatcher authenticationRequestMatcher = RequestMatchers.anyOf(
125+
new AntPathRequestMatcher(Saml2AuthenticationRequestResolver.DEFAULT_AUTHENTICATION_REQUEST_URI),
126+
new AntPathQueryRequestMatcher(this.authenticationRequestUri, this.authenticationRequestParams));
115127

116128
private Saml2AuthenticationRequestResolver authenticationRequestResolver;
117129

@@ -196,11 +208,31 @@ public Saml2LoginConfigurer<B> authenticationRequestResolver(
196208
* Request
197209
* @return the {@link Saml2LoginConfigurer} for further configuration
198210
* @since 6.0
211+
* @deprecated Use {@link #authenticationRequestUriQuery} instead
199212
*/
200213
public Saml2LoginConfigurer<B> authenticationRequestUri(String authenticationRequestUri) {
201-
Assert.state(authenticationRequestUri.contains("{registrationId}"),
202-
"authenticationRequestUri must contain {registrationId} path variable");
203-
this.authenticationRequestUri = authenticationRequestUri;
214+
return authenticationRequestUriQuery(authenticationRequestUri);
215+
}
216+
217+
/**
218+
* Customize the URL that the SAML Authentication Request will be sent to. This method
219+
* also supports query parameters like so: <pre>
220+
* authenticationRequestUriQuery("/saml/authenticate?registrationId={registrationId}")
221+
* </pre> {@link RelyingPartyRegistrations}
222+
* @param authenticationRequestUriQuery the URI and query to use for the SAML 2.0
223+
* Authentication Request
224+
* @return the {@link Saml2LoginConfigurer} for further configuration
225+
* @since 6.0
226+
*/
227+
public Saml2LoginConfigurer<B> authenticationRequestUriQuery(String authenticationRequestUriQuery) {
228+
Assert.state(authenticationRequestUriQuery.contains("{registrationId}"),
229+
"authenticationRequestUri must contain {registrationId} path variable or query value");
230+
String[] parts = authenticationRequestUriQuery.split("[?&]");
231+
this.authenticationRequestUri = parts[0];
232+
this.authenticationRequestParams = new String[parts.length - 1];
233+
System.arraycopy(parts, 1, this.authenticationRequestParams, 0, parts.length - 1);
234+
this.authenticationRequestMatcher = new AntPathQueryRequestMatcher(this.authenticationRequestUri,
235+
this.authenticationRequestParams);
204236
return this;
205237
}
206238

@@ -255,7 +287,7 @@ public void init(B http) throws Exception {
255287
}
256288
else {
257289
Map<String, String> providerUrlMap = getIdentityProviderUrlMap(this.authenticationRequestUri,
258-
this.relyingPartyRegistrationRepository);
290+
this.authenticationRequestParams, this.relyingPartyRegistrationRepository);
259291
boolean singleProvider = providerUrlMap.size() == 1;
260292
if (singleProvider) {
261293
// Setup auto-redirect to provider login page
@@ -336,8 +368,7 @@ private Saml2AuthenticationRequestResolver getAuthenticationRequestResolver(B ht
336368
}
337369
OpenSaml4AuthenticationRequestResolver openSaml4AuthenticationRequestResolver = new OpenSaml4AuthenticationRequestResolver(
338370
relyingPartyRegistrationRepository(http));
339-
openSaml4AuthenticationRequestResolver
340-
.setRequestMatcher(new AntPathRequestMatcher(this.authenticationRequestUri));
371+
openSaml4AuthenticationRequestResolver.setRequestMatcher(this.authenticationRequestMatcher);
341372
return openSaml4AuthenticationRequestResolver;
342373
}
343374

@@ -382,20 +413,28 @@ private void initDefaultLoginFilter(B http) {
382413
return;
383414
}
384415
loginPageGeneratingFilter.setSaml2LoginEnabled(true);
385-
loginPageGeneratingFilter.setSaml2AuthenticationUrlToProviderName(
386-
this.getIdentityProviderUrlMap(this.authenticationRequestUri, this.relyingPartyRegistrationRepository));
416+
loginPageGeneratingFilter
417+
.setSaml2AuthenticationUrlToProviderName(this.getIdentityProviderUrlMap(this.authenticationRequestUri,
418+
this.authenticationRequestParams, this.relyingPartyRegistrationRepository));
387419
loginPageGeneratingFilter.setLoginPageUrl(this.getLoginPage());
388420
loginPageGeneratingFilter.setFailureUrl(this.getFailureUrl());
389421
}
390422

391423
@SuppressWarnings("unchecked")
392-
private Map<String, String> getIdentityProviderUrlMap(String authRequestPrefixUrl,
424+
private Map<String, String> getIdentityProviderUrlMap(String authRequestPrefixUrl, String[] authRequestQueryParams,
393425
RelyingPartyRegistrationRepository idpRepo) {
394426
Map<String, String> idps = new LinkedHashMap<>();
395427
if (idpRepo instanceof Iterable) {
396428
Iterable<RelyingPartyRegistration> repo = (Iterable<RelyingPartyRegistration>) idpRepo;
397-
repo.forEach((p) -> idps.put(authRequestPrefixUrl.replace("{registrationId}", p.getRegistrationId()),
398-
p.getRegistrationId()));
429+
StringBuilder authRequestQuery = new StringBuilder("?");
430+
for (String authRequestQueryParam : authRequestQueryParams) {
431+
authRequestQuery.append(authRequestQueryParam + "&");
432+
}
433+
authRequestQuery.deleteCharAt(authRequestQuery.length() - 1);
434+
String authenticationRequestUriQuery = authRequestPrefixUrl + authRequestQuery;
435+
repo.forEach(
436+
(p) -> idps.put(authenticationRequestUriQuery.replace("{registrationId}", p.getRegistrationId()),
437+
p.getRegistrationId()));
399438
}
400439
return idps;
401440
}
@@ -437,4 +476,35 @@ private <C> void setSharedObject(B http, Class<C> clazz, C object) {
437476
}
438477
}
439478

479+
static class AntPathQueryRequestMatcher implements RequestMatcher {
480+
481+
private final RequestMatcher matcher;
482+
483+
AntPathQueryRequestMatcher(String path, String... params) {
484+
List<RequestMatcher> matchers = new ArrayList<>();
485+
matchers.add(new AntPathRequestMatcher(path));
486+
for (String param : params) {
487+
String[] parts = param.split("=");
488+
if (parts.length == 1) {
489+
matchers.add(new ParameterRequestMatcher(parts[0]));
490+
}
491+
else {
492+
matchers.add(new ParameterRequestMatcher(parts[0], parts[1]));
493+
}
494+
}
495+
this.matcher = new AndRequestMatcher(matchers);
496+
}
497+
498+
@Override
499+
public boolean matches(HttpServletRequest request) {
500+
return matcher(request).isMatch();
501+
}
502+
503+
@Override
504+
public MatchResult matcher(HttpServletRequest request) {
505+
return this.matcher.matcher(request);
506+
}
507+
508+
}
509+
440510
}

config/src/main/java/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParser.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.springframework.core.io.ResourceLoader;
4040
import org.springframework.security.converter.RsaKeyConverters;
4141
import org.springframework.security.saml2.core.Saml2X509Credential;
42+
import org.springframework.security.saml2.provider.service.registration.AssertingPartyMetadata;
4243
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
4344
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
4445
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations;
@@ -153,7 +154,7 @@ private static Map<String, Map<String, Object>> getAssertingParties(Element elem
153154
}
154155

155156
private static void addVerificationCredentials(Map<String, Object> assertingParty,
156-
RelyingPartyRegistration.AssertingPartyDetails.Builder builder) {
157+
AssertingPartyMetadata.Builder<?> builder) {
157158
List<String> verificationCertificateLocations = (List<String>) assertingParty.get(ELT_VERIFICATION_CREDENTIAL);
158159
List<Saml2X509Credential> verificationCredentials = new ArrayList<>();
159160
for (String certificateLocation : verificationCertificateLocations) {
@@ -163,7 +164,7 @@ private static void addVerificationCredentials(Map<String, Object> assertingPart
163164
}
164165

165166
private static void addEncryptionCredentials(Map<String, Object> assertingParty,
166-
RelyingPartyRegistration.AssertingPartyDetails.Builder builder) {
167+
AssertingPartyMetadata.Builder<?> builder) {
167168
List<String> encryptionCertificateLocations = (List<String>) assertingParty.get(ELT_ENCRYPTION_CREDENTIAL);
168169
List<Saml2X509Credential> encryptionCredentials = new ArrayList<>();
169170
for (String certificateLocation : encryptionCertificateLocations) {
@@ -220,8 +221,8 @@ private static RelyingPartyRegistration.Builder getBuilderFromMetadataLocationIf
220221
}
221222
else {
222223
builder = RelyingPartyRegistration.withRegistrationId(registrationId)
223-
.assertingPartyDetails((apBuilder) -> buildAssertingParty(relyingPartyRegistrationElt, assertingParties,
224-
apBuilder, parserContext));
224+
.assertingPartyMetadata((apBuilder) -> buildAssertingParty(relyingPartyRegistrationElt,
225+
assertingParties, apBuilder, parserContext));
225226
}
226227
addRemainingProperties(relyingPartyRegistrationElt, builder);
227228
return builder;
@@ -260,7 +261,7 @@ private static void addRemainingProperties(Element relyingPartyRegistrationElt,
260261
}
261262

262263
private static void buildAssertingParty(Element relyingPartyElt, Map<String, Map<String, Object>> assertingParties,
263-
RelyingPartyRegistration.AssertingPartyDetails.Builder builder, ParserContext parserContext) {
264+
AssertingPartyMetadata.Builder<?> builder, ParserContext parserContext) {
264265
String assertingPartyId = relyingPartyElt.getAttribute(ATT_ASSERTING_PARTY_ID);
265266
if (!assertingParties.containsKey(assertingPartyId)) {
266267
Object source = parserContext.extractSource(relyingPartyElt);
@@ -293,7 +294,7 @@ private static void buildAssertingParty(Element relyingPartyElt, Map<String, Map
293294
}
294295

295296
private static void addSigningAlgorithms(Map<String, Object> assertingParty,
296-
RelyingPartyRegistration.AssertingPartyDetails.Builder builder) {
297+
AssertingPartyMetadata.Builder<?> builder) {
297298
String signingAlgorithmsAttr = getAsString(assertingParty, ATT_SIGNING_ALGORITHMS);
298299
if (StringUtils.hasText(signingAlgorithmsAttr)) {
299300
List<String> signingAlgorithms = Arrays.asList(signingAlgorithmsAttr.split(","));

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import org.springframework.security.web.authentication.AuthenticationSuccessHand
4848
class Saml2Dsl {
4949
var relyingPartyRegistrationRepository: RelyingPartyRegistrationRepository? = null
5050
var loginPage: String? = null
51+
var authenticationRequestUriQuery: String? = null
5152
var authenticationSuccessHandler: AuthenticationSuccessHandler? = null
5253
var authenticationFailureHandler: AuthenticationFailureHandler? = null
5354
var failureUrl: String? = null
@@ -88,6 +89,9 @@ class Saml2Dsl {
8889
defaultSuccessUrlOption?.also {
8990
saml2Login.defaultSuccessUrl(defaultSuccessUrlOption!!.first, defaultSuccessUrlOption!!.second)
9091
}
92+
authenticationRequestUriQuery?.also {
93+
saml2Login.authenticationRequestUriQuery(authenticationRequestUriQuery)
94+
}
9195
authenticationSuccessHandler?.also { saml2Login.successHandler(authenticationSuccessHandler) }
9296
authenticationFailureHandler?.also { saml2Login.failureHandler(authenticationFailureHandler) }
9397
authenticationManager?.also { saml2Login.authenticationManager(authenticationManager) }

config/src/test/java/org/springframework/security/config/annotation/web/AbstractRequestMatcherRegistryTests.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,19 @@ public void matchesWhenDispatcherServletThenMvc() {
341341
verifyNoMoreInteractions(mvc);
342342
}
343343

344+
@Test
345+
public void matchesWhenNoMappingThenException() {
346+
MockServletContext servletContext = new MockServletContext();
347+
servletContext.addServlet("default", DispatcherServlet.class).addMapping("/");
348+
servletContext.addServlet("path", Servlet.class).addMapping("/services/*");
349+
MvcRequestMatcher mvc = mock(MvcRequestMatcher.class);
350+
AntPathRequestMatcher ant = mock(AntPathRequestMatcher.class);
351+
DispatcherServletDelegatingRequestMatcher requestMatcher = new DispatcherServletDelegatingRequestMatcher(ant,
352+
mvc, servletContext);
353+
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/services/endpoint");
354+
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> requestMatcher.matcher(request));
355+
}
356+
344357
private void mockMvcIntrospector(boolean isPresent) {
345358
ApplicationContext context = this.matcherRegistry.getApplicationContext();
346359
given(context.containsBean("mvcHandlerMappingIntrospector")).willReturn(isPresent);

0 commit comments

Comments
 (0)