Add BearerTokenAuthenticationEntryPoint#setResourceMetadataParameterResolver

Closes gh-18542

Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
This commit is contained in:
Daniel Garnier-Moiroux 2026-01-23 17:28:45 +01:00 committed by Joe Grandja
parent 5b7c4ae8d8
commit 4957c5a7e9
2 changed files with 29 additions and 1 deletions

View File

@ -18,6 +18,7 @@ package org.springframework.security.oauth2.server.resource.web;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@ -31,6 +32,7 @@ import org.springframework.security.oauth2.server.resource.BearerTokenError;
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponentsBuilder;
@ -51,6 +53,8 @@ public final class BearerTokenAuthenticationEntryPoint implements Authentication
private String realmName;
private Function<HttpServletRequest, String> resourceMetadataParameterResolver = BearerTokenAuthenticationEntryPoint::getResourceMetadataParameter;
/**
* Collect error details from the provided parameters and format according to RFC
* 6750, specifically {@code error}, {@code error_description}, {@code error_uri}, and
@ -83,7 +87,7 @@ public final class BearerTokenAuthenticationEntryPoint implements Authentication
status = bearerTokenError.getHttpStatus();
}
}
parameters.put("resource_metadata", getResourceMetadataParameter(request));
parameters.put("resource_metadata", this.resourceMetadataParameterResolver.apply(request));
String wwwAuthenticate = computeWWWAuthenticateHeaderValue(parameters);
response.addHeader(HttpHeaders.WWW_AUTHENTICATE, wwwAuthenticate);
response.setStatus(status.value());
@ -97,6 +101,17 @@ public final class BearerTokenAuthenticationEntryPoint implements Authentication
this.realmName = realmName;
}
/**
* Set the resolver to compute the {@code resource_metadata} parameter from the
* request.
* @param resourceMetadataParameterResolver
*/
public void setResourceMetadataParameterResolver(
Function<HttpServletRequest, String> resourceMetadataParameterResolver) {
Assert.notNull(resourceMetadataParameterResolver, "resourceMetadataParameterResolver cannot be null");
this.resourceMetadataParameterResolver = resourceMetadataParameterResolver;
}
private static String getResourceMetadataParameter(HttpServletRequest request) {
String path = request.getContextPath()
+ OAuth2ProtectedResourceMetadataFilter.DEFAULT_OAUTH2_PROTECTED_RESOURCE_METADATA_ENDPOINT_URI;

View File

@ -77,6 +77,19 @@ public class BearerTokenAuthenticationEntryPointTests {
}
@Test
public void commenceWhenNoBearerTokenErrorAndResourceMetadataResolverSetThenStatus401AndAuthHeaderWithResolvedResourceMetadata() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setAttribute("resource_id", "https://example.com/resource-from-request");
MockHttpServletResponse response = new MockHttpServletResponse();
this.authenticationEntryPoint
.setResourceMetadataParameterResolver((req) -> req.getAttribute("resource_id").toString());
this.authenticationEntryPoint.commence(request, response, new BadCredentialsException("test"));
assertThat(response.getStatus()).isEqualTo(401);
assertThat(response.getHeader("WWW-Authenticate"))
.isEqualTo("Bearer resource_metadata=\"https://example.com/resource-from-request\"");
}
@Test
public void commenceWhenInvalidRequestErrorThenStatus400AndHeaderWithError() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();