Skip to content

Commit dc8815c

Browse files
Preserve resource path in resource metadata challenge
Closes gh-19639 Signed-off-by: Tran Ngoc Nhan <ngocnhan.tran1996@gmail.com>
1 parent 3070c96 commit dc8815c

4 files changed

Lines changed: 74 additions & 11 deletions

File tree

oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/BearerTokenAuthenticationEntryPoint.java

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -115,16 +115,13 @@ public void setResourceMetadataParameterResolver(
115115
}
116116

117117
private static String getResourceMetadataParameter(HttpServletRequest request) {
118-
String path = request.getContextPath()
119-
+ OAuth2ProtectedResourceMetadataFilter.DEFAULT_OAUTH2_PROTECTED_RESOURCE_METADATA_ENDPOINT_URI;
120-
// @formatter:off
121-
return UriComponentsBuilder.fromUriString(UrlUtils.buildFullRequestUrl(request))
122-
.replacePath(path)
123-
.replaceQuery(null)
124-
.fragment(null)
125-
.build()
126-
.toUriString();
127-
// @formatter:on
118+
119+
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(UrlUtils.buildFullRequestUrl(request));
120+
String[] pathSegments = builder.build().getPathSegments().toArray(String[]::new);
121+
return builder.replacePath(request.getContextPath())
122+
.path(OAuth2ProtectedResourceMetadataFilter.DEFAULT_OAUTH2_PROTECTED_RESOURCE_METADATA_ENDPOINT_URI)
123+
.pathSegment(pathSegments)
124+
.toUriString();
128125
}
129126

130127
private static String computeWWWAuthenticateHeaderValue(Map<String, String> parameters) {

oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/BearerTokenAuthenticationEntryPointTests.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,18 @@
1919
import org.junit.jupiter.api.BeforeEach;
2020
import org.junit.jupiter.api.Test;
2121

22+
import org.junit.jupiter.params.ParameterizedTest;
23+
import org.junit.jupiter.params.provider.CsvSource;
24+
import org.junit.jupiter.params.provider.NullAndEmptySource;
25+
import org.junit.jupiter.params.provider.ValueSource;
2226
import org.springframework.http.HttpStatus;
2327
import org.springframework.mock.web.MockHttpServletRequest;
2428
import org.springframework.mock.web.MockHttpServletResponse;
2529
import org.springframework.security.authentication.BadCredentialsException;
2630
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
2731
import org.springframework.security.oauth2.server.resource.BearerTokenError;
2832
import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes;
33+
import org.springframework.util.StringUtils;
2934

3035
import static org.assertj.core.api.Assertions.assertThat;
3136

@@ -90,6 +95,34 @@ public void commenceWhenNoBearerTokenErrorAndResourceMetadataResolverSetThenStat
9095
.isEqualTo("Bearer resource_metadata=\"https://example.com/resource-from-request\"");
9196
}
9297

98+
// gh-19639
99+
@ParameterizedTest
100+
@CsvSource(
101+
textBlock = """
102+
, , http://example.com/.well-known/oauth-protected-resource,
103+
'', '', http://example.com/.well-known/oauth-protected-resource,
104+
/, /, http://example.com/.well-known/oauth-protected-resource,
105+
requestUri, contextPath, http://example.com/contextPath/.well-known/oauth-protected-resource/requestUri,
106+
/requestUri, /contextPath, http://example.com/contextPath/.well-known/oauth-protected-resource/requestUri,
107+
requestUri/, contextPath/, http://example.com/contextPath/.well-known/oauth-protected-resource/requestUri,
108+
/requestUri/, /contextPath/, http://example.com/contextPath/.well-known/oauth-protected-resource/requestUri,
109+
//requestUri//, //contextPath//, http://example.com/contextPath/.well-known/oauth-protected-resource/requestUri
110+
""")
111+
public void commenceShouldIncludeContextPathAndRequestUriInResourceMetadata(String requestUri, String contextPath,
112+
String expected) {
113+
114+
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
115+
request.setScheme("http");
116+
request.setServerName("example.com");
117+
request.setContextPath(contextPath);
118+
MockHttpServletResponse response = new MockHttpServletResponse();
119+
120+
this.authenticationEntryPoint.commence(request, response, new BadCredentialsException("test"));
121+
assertThat(response.getStatus()).isEqualTo(401);
122+
assertThat(response.getHeader("WWW-Authenticate"))
123+
.isEqualTo("Bearer resource_metadata=\"%s\"".formatted(expected));
124+
}
125+
93126
@Test
94127
public void commenceWhenInvalidRequestErrorThenStatus400AndHeaderWithError() throws Exception {
95128
MockHttpServletRequest request = new MockHttpServletRequest();

web/src/main/java/org/springframework/security/web/util/UrlUtils.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import jakarta.servlet.http.HttpServletRequest;
2323
import org.jspecify.annotations.Nullable;
24+
import org.springframework.util.StringUtils;
2425

2526
/**
2627
* Provides static methods for composing URLs.
@@ -67,7 +68,12 @@ else if ("https".equals(scheme)) {
6768
}
6869
// Use the requestURI as it is encoded (RFC 3986) and hence suitable for
6970
// redirects.
70-
url.append(requestURI);
71+
if (StringUtils.hasText(requestURI)) {
72+
if (requestURI.charAt(0) != '/') {
73+
url.append("/");
74+
}
75+
url.append(requestURI);
76+
}
7177
if (queryString != null) {
7278
url.append("?").append(queryString);
7379
}

web/src/test/java/org/springframework/security/web/util/UrlUtilsTests.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,41 @@
1717
package org.springframework.security.web.util;
1818

1919
import org.junit.jupiter.api.Test;
20+
import org.junit.jupiter.params.ParameterizedTest;
21+
import org.junit.jupiter.params.provider.CsvSource;
22+
import org.junit.jupiter.params.provider.NullAndEmptySource;
23+
import org.junit.jupiter.params.provider.ValueSource;
24+
import org.springframework.mock.web.MockHttpServletRequest;
25+
import org.springframework.util.StringUtils;
2026

2127
import static org.assertj.core.api.Assertions.assertThat;
2228

2329
/**
30+
* Test for {@link UrlUtils}
31+
*
2432
* @author Luke Taylor
2533
*/
2634
public class UrlUtilsTests {
2735

36+
@ParameterizedTest
37+
@CsvSource(textBlock = """
38+
, http://example.com,
39+
'', http://example.com,
40+
/, http://example.com/,
41+
requestUri, http://example.com/requestUri,
42+
/requestUri, http://example.com/requestUri,
43+
requestUri/, http://example.com/requestUri/,
44+
/requestUri/, http://example.com/requestUri/,
45+
//requestUri//, http://example.com//requestUri//
46+
""")
47+
public void buildFullRequestUrl(String requestUri, String expected) {
48+
49+
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
50+
request.setScheme("http");
51+
request.setServerName("example.com");
52+
assertThat(UrlUtils.buildFullRequestUrl(request)).isEqualTo(expected);
53+
}
54+
2855
@Test
2956
public void absoluteUrlsAreMatchedAsAbsolute() {
3057
assertThat(UrlUtils.isAbsoluteUrl("https://something/")).isTrue();

0 commit comments

Comments
 (0)