authenticationValidator = new OAuth2AuthorizationCodeRequestAuthenticationValidator();
+
+ /**
+ * Constructs an {@code OAuth2PushedAuthorizationRequestAuthenticationProvider} using
+ * the provided parameters.
+ * @param authorizationService the authorization service
+ */
+ public OAuth2PushedAuthorizationRequestAuthenticationProvider(OAuth2AuthorizationService authorizationService) {
+ Assert.notNull(authorizationService, "authorizationService cannot be null");
+ this.authorizationService = authorizationService;
+ }
+
+ @Override
+ public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+ OAuth2PushedAuthorizationRequestAuthenticationToken pushedAuthorizationRequestAuthentication = (OAuth2PushedAuthorizationRequestAuthenticationToken) authentication;
+
+ OAuth2ClientAuthenticationToken clientPrincipal = OAuth2AuthenticationProviderUtils
+ .getAuthenticatedClientElseThrowInvalidClient(pushedAuthorizationRequestAuthentication);
+ RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
+
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("Retrieved registered client");
+ }
+
+ OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext = OAuth2AuthorizationCodeRequestAuthenticationContext
+ .with(toAuthorizationCodeRequestAuthentication(pushedAuthorizationRequestAuthentication))
+ .registeredClient(registeredClient)
+ .build();
+
+ // grant_type
+ OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_AUTHORIZATION_GRANT_TYPE_VALIDATOR
+ .accept(authenticationContext);
+
+ // redirect_uri and scope
+ this.authenticationValidator.accept(authenticationContext);
+
+ // code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE)
+ OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_CODE_CHALLENGE_VALIDATOR
+ .accept(authenticationContext);
+
+ // prompt (OPTIONAL for OpenID Connect 1.0 Authentication Request)
+ OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_PROMPT_VALIDATOR.accept(authenticationContext);
+
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("Validated pushed authorization request parameters");
+ }
+
+ // @formatter:off
+ OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
+ .authorizationUri(pushedAuthorizationRequestAuthentication.getAuthorizationUri())
+ .clientId(registeredClient.getClientId())
+ .redirectUri(pushedAuthorizationRequestAuthentication.getRedirectUri())
+ .scopes(pushedAuthorizationRequestAuthentication.getScopes())
+ .state(pushedAuthorizationRequestAuthentication.getState())
+ .additionalParameters(pushedAuthorizationRequestAuthentication.getAdditionalParameters())
+ .build();
+ // @formatter:on
+
+ OAuth2PushedAuthorizationRequestUri pushedAuthorizationRequestUri = OAuth2PushedAuthorizationRequestUri
+ .create();
+
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("Generated pushed authorization request uri");
+ }
+
+ // @formatter:off
+ OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient)
+ .principalName(clientPrincipal.getName())
+ .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
+ .attribute(OAuth2AuthorizationRequest.class.getName(), authorizationRequest)
+ .attribute(OAuth2ParameterNames.STATE, pushedAuthorizationRequestUri.getState())
+ .build();
+ // @formatter:on
+ this.authorizationService.save(authorization);
+
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("Saved authorization");
+ }
+
+ if (this.logger.isTraceEnabled()) {
+ this.logger.trace("Authenticated pushed authorization request");
+ }
+
+ return new OAuth2PushedAuthorizationRequestAuthenticationToken(authorizationRequest.getAuthorizationUri(),
+ authorizationRequest.getClientId(), clientPrincipal, pushedAuthorizationRequestUri.getRequestUri(),
+ pushedAuthorizationRequestUri.getExpiresAt(), authorizationRequest.getRedirectUri(),
+ authorizationRequest.getState(), authorizationRequest.getScopes());
+ }
+
+ @Override
+ public boolean supports(Class> authentication) {
+ return OAuth2PushedAuthorizationRequestAuthenticationToken.class.isAssignableFrom(authentication);
+ }
+
+ /**
+ * Sets the {@code Consumer} providing access to the
+ * {@link OAuth2AuthorizationCodeRequestAuthenticationContext} and is responsible for
+ * validating specific OAuth 2.0 Pushed Authorization Request parameters associated in
+ * the {@link OAuth2AuthorizationCodeRequestAuthenticationToken}. The default
+ * authentication validator is
+ * {@link OAuth2AuthorizationCodeRequestAuthenticationValidator}.
+ *
+ *
+ * NOTE: The authentication validator MUST throw
+ * {@link OAuth2AuthorizationCodeRequestAuthenticationException} if validation fails.
+ * @param authenticationValidator the {@code Consumer} providing access to the
+ * {@link OAuth2AuthorizationCodeRequestAuthenticationContext} and is responsible for
+ * validating specific OAuth 2.0 Pushed Authorization Request parameters
+ */
+ public void setAuthenticationValidator(
+ Consumer authenticationValidator) {
+ Assert.notNull(authenticationValidator, "authenticationValidator cannot be null");
+ this.authenticationValidator = authenticationValidator;
+ }
+
+ private static OAuth2AuthorizationCodeRequestAuthenticationToken toAuthorizationCodeRequestAuthentication(
+ OAuth2PushedAuthorizationRequestAuthenticationToken pushedAuthorizationCodeRequestAuthentication) {
+ return new OAuth2AuthorizationCodeRequestAuthenticationToken(
+ pushedAuthorizationCodeRequestAuthentication.getAuthorizationUri(),
+ pushedAuthorizationCodeRequestAuthentication.getClientId(),
+ (Authentication) pushedAuthorizationCodeRequestAuthentication.getPrincipal(),
+ pushedAuthorizationCodeRequestAuthentication.getRedirectUri(),
+ pushedAuthorizationCodeRequestAuthentication.getState(),
+ pushedAuthorizationCodeRequestAuthentication.getScopes(),
+ pushedAuthorizationCodeRequestAuthentication.getAdditionalParameters());
+ }
+
+}
diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationToken.java
new file mode 100644
index 000000000..91547633b
--- /dev/null
+++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestAuthenticationToken.java
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.oauth2.server.authorization.authentication;
+
+import java.time.Instant;
+import java.util.Map;
+import java.util.Set;
+
+import org.springframework.lang.Nullable;
+import org.springframework.security.core.Authentication;
+import org.springframework.util.Assert;
+
+/**
+ * An {@link Authentication} implementation for the OAuth 2.0 Pushed Authorization Request
+ * used in the Authorization Code Grant.
+ *
+ * @author Joe Grandja
+ * @since 1.5
+ * @see OAuth2PushedAuthorizationRequestAuthenticationProvider
+ */
+public class OAuth2PushedAuthorizationRequestAuthenticationToken
+ extends AbstractOAuth2AuthorizationCodeRequestAuthenticationToken {
+
+ private final String requestUri;
+
+ private final Instant requestUriExpiresAt;
+
+ /**
+ * Constructs an {@code OAuth2PushedAuthorizationRequestAuthenticationToken} using the
+ * provided parameters.
+ * @param authorizationUri the authorization URI
+ * @param clientId the client identifier
+ * @param principal the authenticated client principal
+ * @param redirectUri the redirect uri
+ * @param state the state
+ * @param scopes the requested scope(s)
+ * @param additionalParameters the additional parameters
+ */
+ public OAuth2PushedAuthorizationRequestAuthenticationToken(String authorizationUri, String clientId,
+ Authentication principal, @Nullable String redirectUri, @Nullable String state,
+ @Nullable Set scopes, @Nullable Map additionalParameters) {
+ super(authorizationUri, clientId, principal, redirectUri, state, scopes, additionalParameters);
+ this.requestUri = null;
+ this.requestUriExpiresAt = null;
+ }
+
+ /**
+ * Constructs an {@code OAuth2PushedAuthorizationRequestAuthenticationToken} using the
+ * provided parameters.
+ * @param authorizationUri the authorization URI
+ * @param clientId the client identifier
+ * @param principal the authenticated client principal
+ * @param requestUri the request URI corresponding to the authorization request posted
+ * @param requestUriExpiresAt the expiration time on or after which the
+ * {@code requestUri} MUST NOT be accepted
+ * @param redirectUri the redirect uri
+ * @param state the state
+ * @param scopes the authorized scope(s)
+ */
+ public OAuth2PushedAuthorizationRequestAuthenticationToken(String authorizationUri, String clientId,
+ Authentication principal, String requestUri, Instant requestUriExpiresAt, @Nullable String redirectUri,
+ @Nullable String state, @Nullable Set scopes) {
+ super(authorizationUri, clientId, principal, redirectUri, state, scopes, null);
+ Assert.hasText(requestUri, "requestUri cannot be empty");
+ Assert.notNull(requestUriExpiresAt, "requestUriExpiresAt cannot be null");
+ this.requestUri = requestUri;
+ this.requestUriExpiresAt = requestUriExpiresAt;
+ setAuthenticated(true);
+ }
+
+ @Nullable
+ public String getRequestUri() {
+ return this.requestUri;
+ }
+
+ @Nullable
+ public Instant getRequestUriExpiresAt() {
+ return this.requestUriExpiresAt;
+ }
+
+}
diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestUri.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestUri.java
new file mode 100644
index 000000000..538cc8c5a
--- /dev/null
+++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2PushedAuthorizationRequestUri.java
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.oauth2.server.authorization.authentication;
+
+import java.time.Instant;
+import java.util.Base64;
+
+import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
+import org.springframework.security.crypto.keygen.StringKeyGenerator;
+
+/**
+ * @author Joe Grandja
+ * @since 1.5
+ */
+final class OAuth2PushedAuthorizationRequestUri {
+
+ private static final String REQUEST_URI_PREFIX = "urn:ietf:params:oauth:request_uri:";
+
+ private static final String REQUEST_URI_DELIMITER = "___";
+
+ private static final StringKeyGenerator DEFAULT_STATE_GENERATOR = new Base64StringKeyGenerator(
+ Base64.getUrlEncoder());
+
+ private String requestUri;
+
+ private String state;
+
+ private Instant expiresAt;
+
+ static OAuth2PushedAuthorizationRequestUri create() {
+ String state = DEFAULT_STATE_GENERATOR.generateKey();
+ Instant expiresAt = Instant.now().plusSeconds(30);
+ OAuth2PushedAuthorizationRequestUri pushedAuthorizationRequestUri = new OAuth2PushedAuthorizationRequestUri();
+ pushedAuthorizationRequestUri.requestUri = REQUEST_URI_PREFIX + state + REQUEST_URI_DELIMITER
+ + expiresAt.toEpochMilli();
+ pushedAuthorizationRequestUri.state = state + REQUEST_URI_DELIMITER + expiresAt.toEpochMilli();
+ pushedAuthorizationRequestUri.expiresAt = expiresAt;
+ return pushedAuthorizationRequestUri;
+ }
+
+ static OAuth2PushedAuthorizationRequestUri parse(String requestUri) {
+ int stateStartIndex = REQUEST_URI_PREFIX.length();
+ int expiresAtStartIndex = requestUri.indexOf(REQUEST_URI_DELIMITER) + REQUEST_URI_DELIMITER.length();
+ OAuth2PushedAuthorizationRequestUri pushedAuthorizationRequestUri = new OAuth2PushedAuthorizationRequestUri();
+ pushedAuthorizationRequestUri.requestUri = requestUri;
+ pushedAuthorizationRequestUri.state = requestUri.substring(stateStartIndex);
+ pushedAuthorizationRequestUri.expiresAt = Instant
+ .ofEpochMilli(Long.parseLong(requestUri.substring(expiresAtStartIndex)));
+ return pushedAuthorizationRequestUri;
+ }
+
+ String getRequestUri() {
+ return this.requestUri;
+ }
+
+ String getState() {
+ return this.state;
+ }
+
+ Instant getExpiresAt() {
+ return this.expiresAt;
+ }
+
+ private OAuth2PushedAuthorizationRequestUri() {
+ }
+
+}
diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OidcPrompt.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OidcPrompt.java
new file mode 100644
index 000000000..2af3b9d2b
--- /dev/null
+++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OidcPrompt.java
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.oauth2.server.authorization.authentication;
+
+/**
+ * The values defined for the "prompt" parameter for the OpenID Connect 1.0 Authentication
+ * Request.
+ *
+ * @author Joe Grandja
+ * @since 1.5
+ */
+final class OidcPrompt {
+
+ static final String NONE = "none";
+
+ static final String LOGIN = "login";
+
+ static final String CONSENT = "consent";
+
+ static final String SELECT_ACCOUNT = "select_account";
+
+ private OidcPrompt() {
+ }
+
+}
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 73d4b224a..094fc5813 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
@@ -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.
@@ -20,6 +20,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.Consumer;
import com.nimbusds.jose.jwk.source.JWKSource;
@@ -42,6 +43,7 @@
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationContext;
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;
@@ -69,6 +71,7 @@
* @see OAuth2ClientAuthenticationConfigurer
* @see OAuth2AuthorizationServerMetadataEndpointConfigurer
* @see OAuth2AuthorizationEndpointConfigurer
+ * @see OAuth2PushedAuthorizationRequestEndpointConfigurer
* @see OAuth2TokenEndpointConfigurer
* @see OAuth2TokenIntrospectionEndpointConfigurer
* @see OAuth2TokenRevocationEndpointConfigurer
@@ -196,6 +199,27 @@ public OAuth2AuthorizationServerConfigurer authorizationEndpoint(
return this;
}
+ /**
+ * Configures the OAuth 2.0 Pushed Authorization Request Endpoint.
+ * @param pushedAuthorizationRequestEndpointCustomizer the {@link Customizer}
+ * providing access to the {@link OAuth2PushedAuthorizationRequestEndpointConfigurer}
+ * @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
+ * @since 1.5
+ */
+ public OAuth2AuthorizationServerConfigurer pushedAuthorizationRequestEndpoint(
+ Customizer pushedAuthorizationRequestEndpointCustomizer) {
+ OAuth2PushedAuthorizationRequestEndpointConfigurer pushedAuthorizationRequestEndpointConfigurer = getConfigurer(
+ OAuth2PushedAuthorizationRequestEndpointConfigurer.class);
+ if (pushedAuthorizationRequestEndpointConfigurer == null) {
+ addConfigurer(OAuth2PushedAuthorizationRequestEndpointConfigurer.class,
+ new OAuth2PushedAuthorizationRequestEndpointConfigurer(this::postProcess));
+ pushedAuthorizationRequestEndpointConfigurer = getConfigurer(
+ OAuth2PushedAuthorizationRequestEndpointConfigurer.class);
+ }
+ pushedAuthorizationRequestEndpointCustomizer.customize(pushedAuthorizationRequestEndpointConfigurer);
+ return this;
+ }
+
/**
* Configures the OAuth 2.0 Token Endpoint.
* @param tokenEndpointCustomizer the {@link Customizer} providing access to the
@@ -314,20 +338,28 @@ public void init(HttpSecurity httpSecurity) throws Exception {
else {
// OpenID Connect is disabled.
// Add an authentication validator that rejects authentication requests.
+ Consumer oidcAuthenticationRequestValidator = (
+ authenticationContext) -> {
+ OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = authenticationContext
+ .getAuthentication();
+ if (authorizationCodeRequestAuthentication.getScopes().contains(OidcScopes.OPENID)) {
+ OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_SCOPE,
+ "OpenID Connect 1.0 authentication requests are restricted.",
+ "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1");
+ throw new OAuth2AuthorizationCodeRequestAuthenticationException(error,
+ authorizationCodeRequestAuthentication);
+ }
+ };
OAuth2AuthorizationEndpointConfigurer authorizationEndpointConfigurer = getConfigurer(
OAuth2AuthorizationEndpointConfigurer.class);
authorizationEndpointConfigurer
- .addAuthorizationCodeRequestAuthenticationValidator((authenticationContext) -> {
- OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = authenticationContext
- .getAuthentication();
- if (authorizationCodeRequestAuthentication.getScopes().contains(OidcScopes.OPENID)) {
- OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_SCOPE,
- "OpenID Connect 1.0 authentication requests are restricted.",
- "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1");
- throw new OAuth2AuthorizationCodeRequestAuthenticationException(error,
- authorizationCodeRequestAuthentication);
- }
- });
+ .addAuthorizationCodeRequestAuthenticationValidator(oidcAuthenticationRequestValidator);
+ OAuth2PushedAuthorizationRequestEndpointConfigurer pushedAuthorizationRequestEndpointConfigurer = getConfigurer(
+ OAuth2PushedAuthorizationRequestEndpointConfigurer.class);
+ if (pushedAuthorizationRequestEndpointConfigurer != null) {
+ pushedAuthorizationRequestEndpointConfigurer
+ .addAuthorizationCodeRequestAuthenticationValidator(oidcAuthenticationRequestValidator);
+ }
}
List requestMatchers = new ArrayList<>();
@@ -344,11 +376,18 @@ public void init(HttpSecurity httpSecurity) throws Exception {
ExceptionHandlingConfigurer exceptionHandling = httpSecurity
.getConfigurer(ExceptionHandlingConfigurer.class);
if (exceptionHandling != null) {
+ List preferredMatchers = new ArrayList<>();
+ preferredMatchers.add(getRequestMatcher(OAuth2TokenEndpointConfigurer.class));
+ preferredMatchers.add(getRequestMatcher(OAuth2TokenIntrospectionEndpointConfigurer.class));
+ preferredMatchers.add(getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class));
+ preferredMatchers.add(getRequestMatcher(OAuth2DeviceAuthorizationEndpointConfigurer.class));
+ RequestMatcher preferredMatcher = getRequestMatcher(
+ OAuth2PushedAuthorizationRequestEndpointConfigurer.class);
+ if (preferredMatcher != null) {
+ preferredMatchers.add(preferredMatcher);
+ }
exceptionHandling.defaultAuthenticationEntryPointFor(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
- new OrRequestMatcher(getRequestMatcher(OAuth2TokenEndpointConfigurer.class),
- getRequestMatcher(OAuth2TokenIntrospectionEndpointConfigurer.class),
- getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class),
- getRequestMatcher(OAuth2DeviceAuthorizationEndpointConfigurer.class)));
+ new OrRequestMatcher(preferredMatchers));
}
httpSecurity.csrf((csrf) -> csrf.ignoringRequestMatchers(this.endpointsMatcher));
diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientAuthenticationConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientAuthenticationConfigurer.java
index 44b252256..bf7f38d36 100644
--- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientAuthenticationConfigurer.java
+++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientAuthenticationConfigurer.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.
@@ -196,10 +196,15 @@ void init(HttpSecurity httpSecurity) {
? OAuth2ConfigurerUtils
.withMultipleIssuersPattern(authorizationServerSettings.getDeviceAuthorizationEndpoint())
: authorizationServerSettings.getDeviceAuthorizationEndpoint();
+ String pushedAuthorizationRequestEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
+ ? OAuth2ConfigurerUtils
+ .withMultipleIssuersPattern(authorizationServerSettings.getPushedAuthorizationRequestEndpoint())
+ : authorizationServerSettings.getPushedAuthorizationRequestEndpoint();
this.requestMatcher = new OrRequestMatcher(new AntPathRequestMatcher(tokenEndpointUri, HttpMethod.POST.name()),
new AntPathRequestMatcher(tokenIntrospectionEndpointUri, HttpMethod.POST.name()),
new AntPathRequestMatcher(tokenRevocationEndpointUri, HttpMethod.POST.name()),
- new AntPathRequestMatcher(deviceAuthorizationEndpointUri, HttpMethod.POST.name()));
+ new AntPathRequestMatcher(deviceAuthorizationEndpointUri, HttpMethod.POST.name()),
+ new AntPathRequestMatcher(pushedAuthorizationRequestEndpointUri, HttpMethod.POST.name()));
List authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
if (!this.authenticationProviders.isEmpty()) {
authenticationProviders.addAll(0, this.authenticationProviders);
diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2PushedAuthorizationRequestEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2PushedAuthorizationRequestEndpointConfigurer.java
new file mode 100644
index 000000000..e51b5f675
--- /dev/null
+++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2PushedAuthorizationRequestEndpointConfigurer.java
@@ -0,0 +1,265 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.config.annotation.ObjectPostProcessor;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.oauth2.core.OAuth2Error;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationContext;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationValidator;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2PushedAuthorizationRequestAuthenticationProvider;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2PushedAuthorizationRequestAuthenticationToken;
+import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
+import org.springframework.security.oauth2.server.authorization.web.OAuth2PushedAuthorizationRequestEndpointFilter;
+import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter;
+import org.springframework.security.web.access.intercept.AuthorizationFilter;
+import org.springframework.security.web.authentication.AuthenticationConverter;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
+import org.springframework.security.web.authentication.DelegatingAuthenticationConverter;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+import org.springframework.security.web.util.matcher.RequestMatcher;
+import org.springframework.util.Assert;
+
+/**
+ * Configurer for the OAuth 2.0 Pushed Authorization Request Endpoint.
+ *
+ * @author Joe Grandja
+ * @since 1.5
+ * @see OAuth2AuthorizationServerConfigurer#pushedAuthorizationRequestEndpoint
+ * @see OAuth2PushedAuthorizationRequestEndpointFilter
+ */
+public final class OAuth2PushedAuthorizationRequestEndpointConfigurer extends AbstractOAuth2Configurer {
+
+ private RequestMatcher requestMatcher;
+
+ private final List pushedAuthorizationRequestConverters = new ArrayList<>();
+
+ private Consumer> pushedAuthorizationRequestConvertersConsumer = (
+ authorizationRequestConverters) -> {
+ };
+
+ private final List authenticationProviders = new ArrayList<>();
+
+ private Consumer> authenticationProvidersConsumer = (authenticationProviders) -> {
+ };
+
+ private AuthenticationSuccessHandler pushedAuthorizationResponseHandler;
+
+ private AuthenticationFailureHandler errorResponseHandler;
+
+ private Consumer authorizationCodeRequestAuthenticationValidator;
+
+ /**
+ * Restrict for internal use only.
+ * @param objectPostProcessor an {@code ObjectPostProcessor}
+ */
+ OAuth2PushedAuthorizationRequestEndpointConfigurer(ObjectPostProcessor