diff --git a/web/src/main/java/org/springframework/security/web/savedrequest/HttpSessionRequestCache.java b/web/src/main/java/org/springframework/security/web/savedrequest/HttpSessionRequestCache.java index 549a0fcfb41..eb4b1ce4106 100644 --- a/web/src/main/java/org/springframework/security/web/savedrequest/HttpSessionRequestCache.java +++ b/web/src/main/java/org/springframework/security/web/savedrequest/HttpSessionRequestCache.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -103,11 +103,11 @@ public void removeRequest(HttpServletRequest currentRequest, HttpServletResponse @Override public HttpServletRequest getMatchingRequest(HttpServletRequest request, HttpServletResponse response) { if (this.matchingRequestParameterName != null) { - if (!StringUtils.hasText(request.getQueryString()) - || !UriComponentsBuilder.fromUriString(UrlUtils.buildRequestUrl(request)) - .build() - .getQueryParams() - .containsKey(this.matchingRequestParameterName)) { + if (!StringUtils.hasText(request.getQueryString()) || !UriComponentsBuilder.newInstance() + .query(request.getQueryString()) + .build() + .getQueryParams() + .containsKey(this.matchingRequestParameterName)) { this.logger.trace( "matchingRequestParameterName is required for getMatchingRequest to lookup a value, but not provided"); return null; diff --git a/web/src/test/java/org/springframework/security/web/savedrequest/HttpSessionRequestCacheTests.java b/web/src/test/java/org/springframework/security/web/savedrequest/HttpSessionRequestCacheTests.java index e19a5f177d1..c6fb8b984b9 100644 --- a/web/src/test/java/org/springframework/security/web/savedrequest/HttpSessionRequestCacheTests.java +++ b/web/src/test/java/org/springframework/security/web/savedrequest/HttpSessionRequestCacheTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -168,6 +168,21 @@ public void getMatchingRequestWhenMatchingRequestParameterNameSetThenDoesNotInvo verify(request, never()).getParameterMap(); } + // gh-16656 + @Test + public void getMatchingRequestWhenMatchingRequestPathContainsPercentSignThenLookedUp() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServletPath("/30 % off"); + HttpSessionRequestCache cache = new HttpSessionRequestCache(); + cache.saveRequest(request, new MockHttpServletResponse()); + MockHttpServletRequest requestToMatch = new MockHttpServletRequest(); + requestToMatch.setServletPath("/30 % off"); + requestToMatch.setQueryString("continue"); + requestToMatch.setSession(request.getSession()); + HttpServletRequest matchingRequest = cache.getMatchingRequest(requestToMatch, new MockHttpServletResponse()); + assertThat(matchingRequest).isNotNull(); + } + private static final class CustomSavedRequest implements SavedRequest { private final SavedRequest delegate;