diff --git a/docs/modules/ROOT/pages/configuration-model.adoc b/docs/modules/ROOT/pages/configuration-model.adoc index 67480923..08c01622 100644 --- a/docs/modules/ROOT/pages/configuration-model.adoc +++ b/docs/modules/ROOT/pages/configuration-model.adoc @@ -118,17 +118,18 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h .tokenGenerator(tokenGenerator) <5> .clientAuthentication(clientAuthentication -> { }) <6> .authorizationEndpoint(authorizationEndpoint -> { }) <7> - .deviceAuthorizationEndpoint(deviceAuthorizationEndpoint -> { }) <8> - .deviceVerificationEndpoint(deviceVerificationEndpoint -> { }) <9> - .tokenEndpoint(tokenEndpoint -> { }) <10> - .tokenIntrospectionEndpoint(tokenIntrospectionEndpoint -> { }) <11> - .tokenRevocationEndpoint(tokenRevocationEndpoint -> { }) <12> - .authorizationServerMetadataEndpoint(authorizationServerMetadataEndpoint -> { }) <13> + .pushedAuthorizationRequestEndpoint(pushedAuthorizationRequestEndpoint -> { }) <8> + .deviceAuthorizationEndpoint(deviceAuthorizationEndpoint -> { }) <9> + .deviceVerificationEndpoint(deviceVerificationEndpoint -> { }) <10> + .tokenEndpoint(tokenEndpoint -> { }) <11> + .tokenIntrospectionEndpoint(tokenIntrospectionEndpoint -> { }) <12> + .tokenRevocationEndpoint(tokenRevocationEndpoint -> { }) <13> + .authorizationServerMetadataEndpoint(authorizationServerMetadataEndpoint -> { }) <14> .oidc(oidc -> oidc - .providerConfigurationEndpoint(providerConfigurationEndpoint -> { }) <14> - .logoutEndpoint(logoutEndpoint -> { }) <15> - .userInfoEndpoint(userInfoEndpoint -> { }) <16> - .clientRegistrationEndpoint(clientRegistrationEndpoint -> { }) <17> + .providerConfigurationEndpoint(providerConfigurationEndpoint -> { }) <15> + .logoutEndpoint(logoutEndpoint -> { }) <16> + .userInfoEndpoint(userInfoEndpoint -> { }) <17> + .clientRegistrationEndpoint(clientRegistrationEndpoint -> { }) <18> ) ); @@ -142,16 +143,17 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h <5> `tokenGenerator()`: The xref:core-model-components.adoc#oauth2-token-generator[`OAuth2TokenGenerator`] for generating tokens supported by the OAuth2 authorization server. <6> `clientAuthentication()`: The configurer for xref:configuration-model.adoc#configuring-client-authentication[OAuth2 Client Authentication]. <7> `authorizationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-authorization-endpoint[OAuth2 Authorization endpoint]. -<8> `deviceAuthorizationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-device-authorization-endpoint[OAuth2 Device Authorization endpoint]. -<9> `deviceVerificationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-device-verification-endpoint[OAuth2 Device Verification endpoint]. -<10> `tokenEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-token-endpoint[OAuth2 Token endpoint]. -<11> `tokenIntrospectionEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-token-introspection-endpoint[OAuth2 Token Introspection endpoint]. -<12> `tokenRevocationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-token-revocation-endpoint[OAuth2 Token Revocation endpoint]. -<13> `authorizationServerMetadataEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-authorization-server-metadata-endpoint[OAuth2 Authorization Server Metadata endpoint]. -<14> `providerConfigurationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-provider-configuration-endpoint[OpenID Connect 1.0 Provider Configuration endpoint]. -<15> `logoutEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-logout-endpoint[OpenID Connect 1.0 Logout endpoint]. -<16> `userInfoEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-user-info-endpoint[OpenID Connect 1.0 UserInfo endpoint]. -<17> `clientRegistrationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[OpenID Connect 1.0 Client Registration endpoint]. +<8> `pushedAuthorizationRequestEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-pushed-authorization-request-endpoint[OAuth2 Pushed Authorization Request endpoint]. +<9> `deviceAuthorizationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-device-authorization-endpoint[OAuth2 Device Authorization endpoint]. +<10> `deviceVerificationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-device-verification-endpoint[OAuth2 Device Verification endpoint]. +<11> `tokenEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-token-endpoint[OAuth2 Token endpoint]. +<12> `tokenIntrospectionEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-token-introspection-endpoint[OAuth2 Token Introspection endpoint]. +<13> `tokenRevocationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-token-revocation-endpoint[OAuth2 Token Revocation endpoint]. +<14> `authorizationServerMetadataEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oauth2-authorization-server-metadata-endpoint[OAuth2 Authorization Server Metadata endpoint]. +<15> `providerConfigurationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-provider-configuration-endpoint[OpenID Connect 1.0 Provider Configuration endpoint]. +<16> `logoutEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-logout-endpoint[OpenID Connect 1.0 Logout endpoint]. +<17> `userInfoEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-user-info-endpoint[OpenID Connect 1.0 UserInfo endpoint]. +<18> `clientRegistrationEndpoint()`: The configurer for the xref:protocol-endpoints.adoc#oidc-client-registration-endpoint[OpenID Connect 1.0 Client Registration endpoint]. [[configuring-authorization-server-settings]] == Configuring Authorization Server Settings @@ -169,6 +171,7 @@ public final class AuthorizationServerSettings extends AbstractSettings { public static Builder builder() { return new Builder() .authorizationEndpoint("/oauth2/authorize") + .pushedAuthorizationRequestEndpoint("/oauth2/par") .deviceAuthorizationEndpoint("/oauth2/device_authorization") .deviceVerificationEndpoint("/oauth2/device_verification") .tokenEndpoint("/oauth2/token") @@ -200,6 +203,7 @@ public AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder() .issuer("https://example.com") .authorizationEndpoint("/oauth2/v1/authorize") + .pushedAuthorizationRequestEndpoint("/oauth2/v1/par") .deviceAuthorizationEndpoint("/oauth2/v1/device_authorization") .deviceVerificationEndpoint("/oauth2/v1/device_verification") .tokenEndpoint("/oauth2/v1/token") diff --git a/docs/modules/ROOT/pages/overview.adoc b/docs/modules/ROOT/pages/overview.adoc index 07887993..7ba67354 100644 --- a/docs/modules/ROOT/pages/overview.adoc +++ b/docs/modules/ROOT/pages/overview.adoc @@ -82,6 +82,7 @@ Spring Authorization Server supports the following features: |xref:protocol-endpoints.adoc[Protocol Endpoints] | * xref:protocol-endpoints.adoc#oauth2-authorization-endpoint[OAuth2 Authorization Endpoint] +* xref:protocol-endpoints.adoc#oauth2-pushed-authorization-request-endpoint[OAuth2 Pushed Authorization Request Endpoint] * xref:protocol-endpoints.adoc#oauth2-device-authorization-endpoint[OAuth2 Device Authorization Endpoint] * xref:protocol-endpoints.adoc#oauth2-device-verification-endpoint[OAuth2 Device Verification Endpoint] * xref:protocol-endpoints.adoc#oauth2-token-endpoint[OAuth2 Token Endpoint] @@ -97,6 +98,8 @@ Spring Authorization Server supports the following features: * The OAuth 2.1 Authorization Framework (https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07[draft]) ** https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07#section-3.1[Authorization Endpoint] ** https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-07#section-3.2[Token Endpoint] +* OAuth 2.0 Pushed Authorization Requests (https://datatracker.ietf.org/doc/html/rfc9126[RFC 9126]) +** https://datatracker.ietf.org/doc/html/rfc9126#section-2[Pushed Authorization Request Endpoint] * OAuth 2.0 Device Authorization Grant (https://tools.ietf.org/html/rfc8628[RFC 8628]) ** https://tools.ietf.org/html/rfc8628#section-3.1[Device Authorization Endpoint] ** https://tools.ietf.org/html/rfc8628#section-3.3[Device Verification Endpoint] diff --git a/docs/modules/ROOT/pages/protocol-endpoints.adoc b/docs/modules/ROOT/pages/protocol-endpoints.adoc index 95a31441..78dc6389 100644 --- a/docs/modules/ROOT/pages/protocol-endpoints.adoc +++ b/docs/modules/ROOT/pages/protocol-endpoints.adoc @@ -126,6 +126,129 @@ static class CustomRedirectUriValidator implements Consumer + authorizationServer + .pushedAuthorizationRequestEndpoint(pushedAuthorizationRequestEndpoint -> + pushedAuthorizationRequestEndpoint + .pushedAuthorizationRequestConverter(pushedAuthorizationRequestConverter) <1> + .pushedAuthorizationRequestConverters(pushedAuthorizationRequestConvertersConsumer) <2> + .authenticationProvider(authenticationProvider) <3> + .authenticationProviders(authenticationProvidersConsumer) <4> + .pushedAuthorizationResponseHandler(pushedAuthorizationResponseHandler) <5> + .errorResponseHandler(errorResponseHandler) <6> + ) + ); + + return http.build(); +} +---- +<1> `pushedAuthorizationRequestConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc9126#section-2.1[OAuth2 pushed authorization request] from `HttpServletRequest` to an instance of `OAuth2PushedAuthorizationRequestAuthenticationToken`. +<2> `pushedAuthorizationRequestConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`. +<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2PushedAuthorizationRequestAuthenticationToken`. +<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`. +<5> `pushedAuthorizationResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OAuth2PushedAuthorizationRequestAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc9126#section-2.2[OAuth2 pushed authorization response]. +<6> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc9126#section-2.3[OAuth2Error response]. + +`OAuth2PushedAuthorizationRequestEndpointConfigurer` configures the `OAuth2PushedAuthorizationRequestEndpointFilter` and registers it with the OAuth2 authorization server `SecurityFilterChain` `@Bean`. +`OAuth2PushedAuthorizationRequestEndpointFilter` is the `Filter` that processes OAuth2 pushed authorization requests. + +`OAuth2PushedAuthorizationRequestEndpointFilter` is configured with the following defaults: + +* `*AuthenticationConverter*` -- A `DelegatingAuthenticationConverter` composed of `OAuth2AuthorizationCodeRequestAuthenticationConverter`. +* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OAuth2PushedAuthorizationRequestAuthenticationProvider`. +* `*AuthenticationSuccessHandler*` -- An internal implementation that handles an "`authenticated`" `OAuth2PushedAuthorizationRequestAuthenticationToken` and returns the OAuth2 pushed authorization response. +* `*AuthenticationFailureHandler*` -- An `OAuth2ErrorAuthenticationFailureHandler`. + +[[oauth2-pushed-authorization-request-endpoint-customizing-authorization-request-validation]] +=== Customizing Pushed Authorization Request Validation + +`OAuth2AuthorizationCodeRequestAuthenticationValidator` is the default validator used for validating specific OAuth2 pushed authorization request parameters used in the Authorization Code Grant. +The default implementation validates the `redirect_uri` and `scope` parameters. +If validation fails, an `OAuth2AuthorizationCodeRequestAuthenticationException` is thrown. + +`OAuth2PushedAuthorizationRequestAuthenticationProvider` provides the ability to override the default pushed authorization request validation by supplying a custom authentication validator of type `Consumer` to `setAuthenticationValidator()`. + +[TIP] +`OAuth2AuthorizationCodeRequestAuthenticationContext` holds the `OAuth2AuthorizationCodeRequestAuthenticationToken`, which contains the OAuth2 pushed authorization request parameters. + +[IMPORTANT] +If validation fails, the authentication validator *MUST* throw `OAuth2AuthorizationCodeRequestAuthenticationException`. + +A common use case during the development life cycle phase is to allow for `localhost` in the `redirect_uri` parameter. + +The following example shows how to configure `OAuth2PushedAuthorizationRequestAuthenticationProvider` with a custom authentication validator that allows for `localhost` in the `redirect_uri` parameter: + +[source,java] +---- +@Bean +public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { + OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = + OAuth2AuthorizationServerConfigurer.authorizationServer(); + + http + .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) + .with(authorizationServerConfigurer, (authorizationServer) -> + authorizationServer + .pushedAuthorizationRequestEndpoint(pushedAuthorizationRequestEndpoint -> + pushedAuthorizationRequestEndpoint + .authenticationProviders(configureAuthenticationValidator()) + ) + ); + + return http.build(); +} + +private Consumer> configureAuthenticationValidator() { + return (authenticationProviders) -> + authenticationProviders.forEach((authenticationProvider) -> { + if (authenticationProvider instanceof OAuth2PushedAuthorizationRequestAuthenticationProvider) { + Consumer authenticationValidator = + // Override default redirect_uri validator + new CustomRedirectUriValidator() + // Reuse default scope validator + .andThen(OAuth2AuthorizationCodeRequestAuthenticationValidator.DEFAULT_SCOPE_VALIDATOR); + + ((OAuth2PushedAuthorizationRequestAuthenticationProvider) authenticationProvider) + .setAuthenticationValidator(authenticationValidator); + } + }); +} + +static class CustomRedirectUriValidator implements Consumer { + + @Override + public void accept(OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) { + OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication = + authenticationContext.getAuthentication(); + RegisteredClient registeredClient = authenticationContext.getRegisteredClient(); + String requestedRedirectUri = authorizationCodeRequestAuthentication.getRedirectUri(); + + // Use exact string matching when comparing client redirect URIs against pre-registered URIs + if (!registeredClient.getRedirectUris().contains(requestedRedirectUri)) { + OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST); + throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null); + } + } +} +---- + [[oauth2-device-authorization-endpoint]] == OAuth2 Device Authorization Endpoint 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 index 91547633..6fbf85cc 100644 --- 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 @@ -63,9 +63,10 @@ public class OAuth2PushedAuthorizationRequestAuthenticationToken * @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 requestUri the {@code request_uri} corresponding to the authorization + * request posted * @param requestUriExpiresAt the expiration time on or after which the - * {@code requestUri} MUST NOT be accepted + * {@code request_uri} MUST NOT be accepted * @param redirectUri the redirect uri * @param state the state * @param scopes the authorized scope(s) @@ -81,11 +82,21 @@ public class OAuth2PushedAuthorizationRequestAuthenticationToken setAuthenticated(true); } + /** + * Returns the {@code request_uri} corresponding to the authorization request posted. + * @return the {@code request_uri} corresponding to the authorization request posted + */ @Nullable public String getRequestUri() { return this.requestUri; } + /** + * Returns the expiration time on or after which the {@code request_uri} MUST NOT be + * accepted. + * @return the expiration time on or after which the {@code request_uri} MUST NOT be + * accepted + */ @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 index 084e8791..036c41f3 100644 --- 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 @@ -22,6 +22,9 @@ import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; import org.springframework.security.crypto.keygen.StringKeyGenerator; /** + * A representation of a {@code request_uri} used in OAuth 2.0 Pushed Authorization + * Requests. + * * @author Joe Grandja * @since 1.5 */