From ac2eaf4826428e273dcc1b8af8a4864443e9ca2e Mon Sep 17 00:00:00 2001 From: Joe Grandja <10884212+jgrandja@users.noreply.github.com> Date: Fri, 17 Oct 2025 16:36:03 -0400 Subject: [PATCH] Return PAR endpoint metadata only when enabled Closes gh-2219 --- .../OAuth2AuthorizationServerConfigurer.java | 26 +++++++++++- .../web/configurers/OidcConfigurer.java | 24 ++++++++++- ...dcProviderConfigurationEndpointFilter.java | 2 - ...orizationServerMetadataEndpointFilter.java | 2 - ...Auth2AuthorizationServerMetadataTests.java | 42 ++++++++++++++++++- .../OidcProviderConfigurationTests.java | 40 +++++++++++++++++- ...viderConfigurationEndpointFilterTests.java | 3 +- ...tionServerMetadataEndpointFilterTests.java | 3 +- 8 files changed, 130 insertions(+), 12 deletions(-) diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java index 094fc581..504a416c 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java @@ -47,6 +47,8 @@ import org.springframework.security.oauth2.server.authorization.authentication.O import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; +import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext; +import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; import org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter; @@ -57,6 +59,7 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; +import org.springframework.web.util.UriComponentsBuilder; /** * An {@link AbstractHttpConfigurer} for OAuth 2.0 Authorization Server support. @@ -407,6 +410,27 @@ public final class OAuth2AuthorizationServerConfigurer @Override public void configure(HttpSecurity httpSecurity) { + OAuth2PushedAuthorizationRequestEndpointConfigurer pushedAuthorizationRequestEndpointConfigurer = getConfigurer( + OAuth2PushedAuthorizationRequestEndpointConfigurer.class); + if (pushedAuthorizationRequestEndpointConfigurer != null) { + OAuth2AuthorizationServerMetadataEndpointConfigurer authorizationServerMetadataEndpointConfigurer = getConfigurer( + OAuth2AuthorizationServerMetadataEndpointConfigurer.class); + + authorizationServerMetadataEndpointConfigurer.addDefaultAuthorizationServerMetadataCustomizer((builder) -> { + AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext(); + String issuer = authorizationServerContext.getIssuer(); + AuthorizationServerSettings authorizationServerSettings = authorizationServerContext + .getAuthorizationServerSettings(); + + String pushedAuthorizationRequestEndpoint = UriComponentsBuilder.fromUriString(issuer) + .path(authorizationServerSettings.getPushedAuthorizationRequestEndpoint()) + .build() + .toUriString(); + + builder.pushedAuthorizationRequestEndpoint(pushedAuthorizationRequestEndpoint); + }); + } + this.configurers.values().forEach((configurer) -> configurer.configure(httpSecurity)); AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils @@ -453,7 +477,7 @@ public final class OAuth2AuthorizationServerConfigurer } @SuppressWarnings("unchecked") - private T getConfigurer(Class type) { + T getConfigurer(Class type) { return (T) this.configurers.get(type); } diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcConfigurer.java index 166c3136..60135b6d 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcConfigurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 the original author or authors. + * Copyright 2020-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. @@ -148,6 +148,28 @@ public final class OidcConfigurer extends AbstractOAuth2Configurer { }); } + OAuth2PushedAuthorizationRequestEndpointConfigurer pushedAuthorizationRequestEndpointConfigurer = httpSecurity + .getConfigurer(OAuth2AuthorizationServerConfigurer.class) + .getConfigurer(OAuth2PushedAuthorizationRequestEndpointConfigurer.class); + if (pushedAuthorizationRequestEndpointConfigurer != null) { + OidcProviderConfigurationEndpointConfigurer providerConfigurationEndpointConfigurer = getConfigurer( + OidcProviderConfigurationEndpointConfigurer.class); + + providerConfigurationEndpointConfigurer.addDefaultProviderConfigurationCustomizer((builder) -> { + AuthorizationServerContext authorizationServerContext = AuthorizationServerContextHolder.getContext(); + String issuer = authorizationServerContext.getIssuer(); + AuthorizationServerSettings authorizationServerSettings = authorizationServerContext + .getAuthorizationServerSettings(); + + String pushedAuthorizationRequestEndpoint = UriComponentsBuilder.fromUriString(issuer) + .path(authorizationServerSettings.getPushedAuthorizationRequestEndpoint()) + .build() + .toUriString(); + + builder.pushedAuthorizationRequestEndpoint(pushedAuthorizationRequestEndpoint); + }); + } + this.configurers.values().forEach((configurer) -> configurer.configure(httpSecurity)); } diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java index 43dffe51..b527396f 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java @@ -101,8 +101,6 @@ public final class OidcProviderConfigurationEndpointFilter extends OncePerReques OidcProviderConfiguration.Builder providerConfiguration = OidcProviderConfiguration.builder() .issuer(issuer) .authorizationEndpoint(asUrl(issuer, authorizationServerSettings.getAuthorizationEndpoint())) - .pushedAuthorizationRequestEndpoint( - asUrl(issuer, authorizationServerSettings.getPushedAuthorizationRequestEndpoint())) .deviceAuthorizationEndpoint(asUrl(issuer, authorizationServerSettings.getDeviceAuthorizationEndpoint())) .tokenEndpoint(asUrl(issuer, authorizationServerSettings.getTokenEndpoint())) .tokenEndpointAuthenticationMethods(clientAuthenticationMethods()) diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java index c2f9e067..608766ae 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java @@ -101,8 +101,6 @@ public final class OAuth2AuthorizationServerMetadataEndpointFilter extends OnceP .builder() .issuer(issuer) .authorizationEndpoint(asUrl(issuer, authorizationServerSettings.getAuthorizationEndpoint())) - .pushedAuthorizationRequestEndpoint( - asUrl(issuer, authorizationServerSettings.getPushedAuthorizationRequestEndpoint())) .deviceAuthorizationEndpoint(asUrl(issuer, authorizationServerSettings.getDeviceAuthorizationEndpoint())) .tokenEndpoint(asUrl(issuer, authorizationServerSettings.getTokenEndpoint())) .tokenEndpointAuthenticationMethods(clientAuthenticationMethods()) diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataTests.java index abe46c78..d17cbf37 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2024 the original author or authors. + * Copyright 2020-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. @@ -35,6 +35,7 @@ import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.oauth2.jose.TestJwks; @@ -80,6 +81,9 @@ public class OAuth2AuthorizationServerMetadataTests { @Autowired private JdbcOperations jdbcOperations; + @Autowired + private AuthorizationServerSettings authorizationServerSettings; + @BeforeAll public static void setupClass() { JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); @@ -155,6 +159,17 @@ public class OAuth2AuthorizationServerMetadataTests { hasItems("scope1", "scope2"))); } + @Test + public void requestWhenAuthorizationServerMetadataRequestAndPushedAuthorizationRequestEnabledThenMetadataResponseIncludesPushedAuthorizationRequestEndpoint() + throws Exception { + this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequestEnabled.class).autowire(); + + this.mvc.perform(get(ISSUER.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI))) + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$.pushed_authorization_request_endpoint") + .value(ISSUER.concat(this.authorizationServerSettings.getPushedAuthorizationRequestEndpoint()))); + } + @EnableWebSecurity @Import(OAuth2AuthorizationServerConfiguration.class) static class AuthorizationServerConfiguration { @@ -226,4 +241,29 @@ public class OAuth2AuthorizationServerMetadataTests { } + @EnableWebSecurity + @Configuration(proxyBeanMethods = false) + static class AuthorizationServerConfigurationWithPushedAuthorizationRequestEnabled + extends AuthorizationServerConfiguration { + + // @formatter:off + @Bean + SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { + OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = + OAuth2AuthorizationServerConfigurer.authorizationServer(); + http + .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) + .with(authorizationServerConfigurer, (authorizationServer) -> + authorizationServer + .pushedAuthorizationRequestEndpoint(Customizer.withDefaults()) + ) + .authorizeHttpRequests((authorize) -> + authorize.anyRequest().authenticated() + ); + return http.build(); + } + // @formatter:on + + } + } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationTests.java index e857577a..5c2ab621 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2024 the original author or authors. + * Copyright 2020-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. @@ -144,6 +144,18 @@ public class OidcProviderConfigurationTests { .value(ISSUER.concat(this.authorizationServerSettings.getOidcClientRegistrationEndpoint()))); } + @Test + public void requestWhenConfigurationRequestAndPushedAuthorizationRequestEnabledThenConfigurationResponseIncludesPushedAuthorizationRequestEndpoint() + throws Exception { + this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequestEnabled.class).autowire(); + + this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))) + .andExpect(status().is2xxSuccessful()) + .andExpectAll(defaultConfigurationMatchers(ISSUER)) + .andExpect(jsonPath("$.pushed_authorization_request_endpoint") + .value(ISSUER.concat(this.authorizationServerSettings.getPushedAuthorizationRequestEndpoint()))); + } + private ResultMatcher[] defaultConfigurationMatchers(String issuer) { // @formatter:off return new ResultMatcher[] { @@ -407,4 +419,30 @@ public class OidcProviderConfigurationTests { } + @EnableWebSecurity + @Configuration(proxyBeanMethods = false) + static class AuthorizationServerConfigurationWithPushedAuthorizationRequestEnabled + extends AuthorizationServerConfiguration { + + // @formatter:off + @Bean + SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { + OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = + OAuth2AuthorizationServerConfigurer.authorizationServer(); + http + .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) + .with(authorizationServerConfigurer, (authorizationServer) -> + authorizationServer + .pushedAuthorizationRequestEndpoint(Customizer.withDefaults()) + .oidc(Customizer.withDefaults()) + ) + .authorizeHttpRequests((authorize) -> + authorize.anyRequest().authenticated() + ); + return http.build(); + } + // @formatter:on + + } + } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java index 467682d4..11dcc26d 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java @@ -133,8 +133,7 @@ public class OidcProviderConfigurationEndpointFilterTests { assertThat(providerConfigurationResponse).contains("\"issuer\":\"https://example.com\""); assertThat(providerConfigurationResponse) .contains("\"authorization_endpoint\":\"https://example.com/oauth2/v1/authorize\""); - assertThat(providerConfigurationResponse) - .contains("\"pushed_authorization_request_endpoint\":\"https://example.com/oauth2/v1/par\""); + assertThat(providerConfigurationResponse).doesNotContain("\"pushed_authorization_request_endpoint\""); assertThat(providerConfigurationResponse) .contains("\"token_endpoint\":\"https://example.com/oauth2/v1/token\""); assertThat(providerConfigurationResponse).contains("\"jwks_uri\":\"https://example.com/oauth2/v1/jwks\""); diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java index 34ee4307..cb2a872e 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java @@ -129,8 +129,7 @@ public class OAuth2AuthorizationServerMetadataEndpointFilterTests { assertThat(authorizationServerMetadataResponse).contains("\"issuer\":\"https://example.com\""); assertThat(authorizationServerMetadataResponse) .contains("\"authorization_endpoint\":\"https://example.com/oauth2/v1/authorize\""); - assertThat(authorizationServerMetadataResponse) - .contains("\"pushed_authorization_request_endpoint\":\"https://example.com/oauth2/v1/par\""); + assertThat(authorizationServerMetadataResponse).doesNotContain("\"pushed_authorization_request_endpoint\""); assertThat(authorizationServerMetadataResponse) .contains("\"token_endpoint\":\"https://example.com/oauth2/v1/token\""); assertThat(authorizationServerMetadataResponse).contains(