Browse Source

Add documentation for OAuth 2.0 Pushed Authorization Requests (PAR)

Closes gh-2014
Joe Grandja 3 tháng trước cách đây
mục cha
commit
90e6a795c4

+ 24 - 20
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")

+ 3 - 0
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]

+ 123 - 0
docs/modules/ROOT/pages/protocol-endpoints.adoc

@@ -126,6 +126,129 @@ static class CustomRedirectUriValidator implements Consumer<OAuth2AuthorizationC
 }
 ----
 
+[[oauth2-pushed-authorization-request-endpoint]]
+== OAuth2 Pushed Authorization Request Endpoint
+
+`OAuth2PushedAuthorizationRequestEndpointConfigurer` provides the ability to customize the https://datatracker.ietf.org/doc/html/rfc9126#section-2[OAuth2 Pushed Authorization Request endpoint].
+It defines extension points that let you customize the pre-processing, main processing, and post-processing logic for https://datatracker.ietf.org/doc/html/rfc9126#section-2.1[OAuth2 Pushed Authorization requests].
+
+`OAuth2PushedAuthorizationRequestEndpointConfigurer` provides the following configuration options:
+
+[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
+        				.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<OAuth2AuthorizationCodeRequestAuthenticationContext>` 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<List<AuthenticationProvider>> configureAuthenticationValidator() {
+	return (authenticationProviders) ->
+		authenticationProviders.forEach((authenticationProvider) -> {
+			if (authenticationProvider instanceof OAuth2PushedAuthorizationRequestAuthenticationProvider) {
+				Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> 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<OAuth2AuthorizationCodeRequestAuthenticationContext> {
+
+	@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
 

+ 13 - 2
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;

+ 3 - 0
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
  */