Эх сурвалжийг харах

Add authorization server metadata for OAuth 2.0 Pushed Authorization Requests (PAR)

Issue gh-1925

Closes gh-1975
Joe Grandja 4 сар өмнө
parent
commit
2dff08834c
9 өөрчлөгдсөн 81 нэмэгдсэн , 0 устгасан
  1. 23 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/AbstractOAuth2AuthorizationServerMetadata.java
  2. 13 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimAccessor.java
  3. 10 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimNames.java
  4. 2 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2AuthorizationServerMetadataHttpMessageConverter.java
  5. 2 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java
  6. 2 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java
  7. 21 0
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataTests.java
  8. 4 0
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java
  9. 4 0
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java

+ 23 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/AbstractOAuth2AuthorizationServerMetadata.java

@@ -52,6 +52,9 @@ import org.springframework.util.Assert;
  * @see <a target="_blank" href=
  * "https://datatracker.ietf.org/doc/html/rfc9449#section-5.1">5.1 OAuth 2.0 Demonstrating
  * Proof of Possession (DPoP) Metadata</a>
+ * @see <a target="_blank" href=
+ * "https://datatracker.ietf.org/doc/html/rfc9126#name-authorization-server-metada">5.
+ * OAuth 2.0 Pushed Authorization Requests Metadata</a>
  */
 public abstract class AbstractOAuth2AuthorizationServerMetadata
 		implements OAuth2AuthorizationServerMetadataClaimAccessor, Serializable {
@@ -119,6 +122,19 @@ public abstract class AbstractOAuth2AuthorizationServerMetadata
 			return claim(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT, authorizationEndpoint);
 		}
 
+		/**
+		 * Use this {@code pushed_authorization_request_endpoint} in the resulting
+		 * {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
+		 * @param pushedAuthorizationRequestEndpoint the {@code URL} of the OAuth 2.0
+		 * Pushed Authorization Request Endpoint
+		 * @return the {@link AbstractBuilder} for further configuration
+		 * @since 1.5
+		 */
+		public B pushedAuthorizationRequestEndpoint(String pushedAuthorizationRequestEndpoint) {
+			return claim(OAuth2AuthorizationServerMetadataClaimNames.PUSHED_AUTHORIZATION_REQUEST_ENDPOINT,
+					pushedAuthorizationRequestEndpoint);
+		}
+
 		/**
 		 * Use this {@code device_authorization_endpoint} in the resulting
 		 * {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
@@ -454,6 +470,13 @@ public abstract class AbstractOAuth2AuthorizationServerMetadata
 					"authorizationEndpoint cannot be null");
 			validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT),
 					"authorizationEndpoint must be a valid URL");
+			if (getClaims()
+				.get(OAuth2AuthorizationServerMetadataClaimNames.PUSHED_AUTHORIZATION_REQUEST_ENDPOINT) != null) {
+				validateURL(
+						getClaims()
+							.get(OAuth2AuthorizationServerMetadataClaimNames.PUSHED_AUTHORIZATION_REQUEST_ENDPOINT),
+						"pushedAuthorizationRequestEndpoint must be a valid URL");
+			}
 			if (getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.DEVICE_AUTHORIZATION_ENDPOINT) != null) {
 				validateURL(getClaims().get(OAuth2AuthorizationServerMetadataClaimNames.DEVICE_AUTHORIZATION_ENDPOINT),
 						"deviceAuthorizationEndpoint must be a valid URL");

+ 13 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimAccessor.java

@@ -44,6 +44,9 @@ import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
  * @see <a target="_blank" href=
  * "https://datatracker.ietf.org/doc/html/rfc9449#section-5.1">5.1 OAuth 2.0 Demonstrating
  * Proof of Possession (DPoP) Metadata</a>
+ * @see <a target="_blank" href=
+ * "https://datatracker.ietf.org/doc/html/rfc9126#name-authorization-server-metada">5.
+ * OAuth 2.0 Pushed Authorization Requests Metadata</a>
  */
 public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAccessor {
 
@@ -65,6 +68,16 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc
 		return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT);
 	}
 
+	/**
+	 * Returns the {@code URL} of the OAuth 2.0 Pushed Authorization Request Endpoint
+	 * {@code (pushed_authorization_request_endpoint)}.
+	 * @return the {@code URL} of the OAuth 2.0 Pushed Authorization Request Endpoint
+	 * @since 1.5
+	 */
+	default URL getPushedAuthorizationRequestEndpoint() {
+		return getClaimAsURL(OAuth2AuthorizationServerMetadataClaimNames.PUSHED_AUTHORIZATION_REQUEST_ENDPOINT);
+	}
+
 	/**
 	 * Returns the {@code URL} of the OAuth 2.0 Device Authorization Endpoint
 	 * {@code (device_authorization_endpoint)}.

+ 10 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataClaimNames.java

@@ -37,6 +37,9 @@ import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
  * @see <a target="_blank" href=
  * "https://datatracker.ietf.org/doc/html/rfc9449#section-5.1">5.1 OAuth 2.0 Demonstrating
  * Proof of Possession (DPoP) Metadata</a>
+ * @see <a target="_blank" href=
+ * "https://datatracker.ietf.org/doc/html/rfc9126#name-authorization-server-metada">5.
+ * OAuth 2.0 Pushed Authorization Requests Metadata</a>
  */
 public class OAuth2AuthorizationServerMetadataClaimNames {
 
@@ -52,6 +55,13 @@ public class OAuth2AuthorizationServerMetadataClaimNames {
 	 */
 	public static final String AUTHORIZATION_ENDPOINT = "authorization_endpoint";
 
+	/**
+	 * {@code pushed_authorization_request_endpoint} - the {@code URL} of the OAuth 2.0
+	 * Pushed Authorization Request Endpoint
+	 * @since 1.5
+	 */
+	public static final String PUSHED_AUTHORIZATION_REQUEST_ENDPOINT = "pushed_authorization_request_endpoint";
+
 	/**
 	 * {@code device_authorization_endpoint} - the {@code URL} of the OAuth 2.0 Device
 	 * Authorization Endpoint

+ 2 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2AuthorizationServerMetadataHttpMessageConverter.java

@@ -148,6 +148,8 @@ public class OAuth2AuthorizationServerMetadataHttpMessageConverter
 			Map<String, Converter<Object, ?>> claimConverters = new HashMap<>();
 			claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.ISSUER, urlConverter);
 			claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT, urlConverter);
+			claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.PUSHED_AUTHORIZATION_REQUEST_ENDPOINT,
+					urlConverter);
 			claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.DEVICE_AUTHORIZATION_ENDPOINT,
 					urlConverter);
 			claimConverters.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT, urlConverter);

+ 2 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java

@@ -101,6 +101,8 @@ 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())

+ 2 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java

@@ -101,6 +101,8 @@ 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())

+ 21 - 0
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/OAuth2AuthorizationServerMetadataTests.java

@@ -51,6 +51,7 @@ public class OAuth2AuthorizationServerMetadataTests {
 		OAuth2AuthorizationServerMetadata authorizationServerMetadata = OAuth2AuthorizationServerMetadata.builder()
 			.issuer("https://example.com")
 			.authorizationEndpoint("https://example.com/oauth2/authorize")
+			.pushedAuthorizationRequestEndpoint("https://example.com/oauth2/par")
 			.tokenEndpoint("https://example.com/oauth2/token")
 			.tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue())
 			.jwkSetUrl("https://example.com/oauth2/jwks")
@@ -72,6 +73,8 @@ public class OAuth2AuthorizationServerMetadataTests {
 		assertThat(authorizationServerMetadata.getIssuer()).isEqualTo(url("https://example.com"));
 		assertThat(authorizationServerMetadata.getAuthorizationEndpoint())
 			.isEqualTo(url("https://example.com/oauth2/authorize"));
+		assertThat(authorizationServerMetadata.getPushedAuthorizationRequestEndpoint())
+			.isEqualTo(url("https://example.com/oauth2/par"));
 		assertThat(authorizationServerMetadata.getTokenEndpoint()).isEqualTo(url("https://example.com/oauth2/token"));
 		assertThat(authorizationServerMetadata.getTokenEndpointAuthenticationMethods())
 			.containsExactly(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue());
@@ -107,6 +110,7 @@ public class OAuth2AuthorizationServerMetadataTests {
 		assertThat(authorizationServerMetadata.getIssuer()).isEqualTo(url("https://example.com"));
 		assertThat(authorizationServerMetadata.getAuthorizationEndpoint())
 			.isEqualTo(url("https://example.com/oauth2/authorize"));
+		assertThat(authorizationServerMetadata.getPushedAuthorizationRequestEndpoint()).isNull();
 		assertThat(authorizationServerMetadata.getTokenEndpoint()).isEqualTo(url("https://example.com/oauth2/token"));
 		assertThat(authorizationServerMetadata.getTokenEndpointAuthenticationMethods()).isNull();
 		assertThat(authorizationServerMetadata.getJwkSetUrl()).isNull();
@@ -127,6 +131,8 @@ public class OAuth2AuthorizationServerMetadataTests {
 		claims.put(OAuth2AuthorizationServerMetadataClaimNames.ISSUER, "https://example.com");
 		claims.put(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT,
 				"https://example.com/oauth2/authorize");
+		claims.put(OAuth2AuthorizationServerMetadataClaimNames.PUSHED_AUTHORIZATION_REQUEST_ENDPOINT,
+				"https://example.com/oauth2/par");
 		claims.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT, "https://example.com/oauth2/token");
 		claims.put(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI, "https://example.com/oauth2/jwks");
 		claims.put(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, Collections.singletonList("openid"));
@@ -145,6 +151,8 @@ public class OAuth2AuthorizationServerMetadataTests {
 		assertThat(authorizationServerMetadata.getIssuer()).isEqualTo(url("https://example.com"));
 		assertThat(authorizationServerMetadata.getAuthorizationEndpoint())
 			.isEqualTo(url("https://example.com/oauth2/authorize"));
+		assertThat(authorizationServerMetadata.getPushedAuthorizationRequestEndpoint())
+			.isEqualTo(url("https://example.com/oauth2/par"));
 		assertThat(authorizationServerMetadata.getTokenEndpoint()).isEqualTo(url("https://example.com/oauth2/token"));
 		assertThat(authorizationServerMetadata.getTokenEndpointAuthenticationMethods()).isNull();
 		assertThat(authorizationServerMetadata.getJwkSetUrl()).isEqualTo(url("https://example.com/oauth2/jwks"));
@@ -168,6 +176,8 @@ public class OAuth2AuthorizationServerMetadataTests {
 		claims.put(OAuth2AuthorizationServerMetadataClaimNames.ISSUER, url("https://example.com"));
 		claims.put(OAuth2AuthorizationServerMetadataClaimNames.AUTHORIZATION_ENDPOINT,
 				url("https://example.com/oauth2/authorize"));
+		claims.put(OAuth2AuthorizationServerMetadataClaimNames.PUSHED_AUTHORIZATION_REQUEST_ENDPOINT,
+				url("https://example.com/oauth2/par"));
 		claims.put(OAuth2AuthorizationServerMetadataClaimNames.TOKEN_ENDPOINT, url("https://example.com/oauth2/token"));
 		claims.put(OAuth2AuthorizationServerMetadataClaimNames.JWKS_URI, url("https://example.com/oauth2/jwks"));
 		claims.put(OAuth2AuthorizationServerMetadataClaimNames.RESPONSE_TYPES_SUPPORTED,
@@ -185,6 +195,8 @@ public class OAuth2AuthorizationServerMetadataTests {
 		assertThat(authorizationServerMetadata.getIssuer()).isEqualTo(url("https://example.com"));
 		assertThat(authorizationServerMetadata.getAuthorizationEndpoint())
 			.isEqualTo(url("https://example.com/oauth2/authorize"));
+		assertThat(authorizationServerMetadata.getPushedAuthorizationRequestEndpoint())
+			.isEqualTo(url("https://example.com/oauth2/par"));
 		assertThat(authorizationServerMetadata.getTokenEndpoint()).isEqualTo(url("https://example.com/oauth2/token"));
 		assertThat(authorizationServerMetadata.getTokenEndpointAuthenticationMethods()).isNull();
 		assertThat(authorizationServerMetadata.getJwkSetUrl()).isEqualTo(url("https://example.com/oauth2/jwks"));
@@ -264,6 +276,15 @@ public class OAuth2AuthorizationServerMetadataTests {
 			.withMessage("authorizationEndpoint must be a valid URL");
 	}
 
+	@Test
+	public void buildWhenPushedAuthorizationRequestEndpointNotUrlThenThrowIllegalArgumentException() {
+		Builder builder = this.minimalBuilder.claims((claims) -> claims
+			.put(OAuth2AuthorizationServerMetadataClaimNames.PUSHED_AUTHORIZATION_REQUEST_ENDPOINT, "not an url"));
+
+		assertThatIllegalArgumentException().isThrownBy(builder::build)
+			.withMessage("pushedAuthorizationRequestEndpoint must be a valid URL");
+	}
+
 	@Test
 	public void buildWhenMissingTokenEndpointThenThrowsIllegalArgumentException() {
 		Builder builder = this.minimalBuilder

+ 4 - 0
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java

@@ -96,6 +96,7 @@ public class OidcProviderConfigurationEndpointFilterTests {
 	public void doFilterWhenConfigurationRequestThenConfigurationResponse() throws Exception {
 		String issuer = "https://example.com";
 		String authorizationEndpoint = "/oauth2/v1/authorize";
+		String pushedAuthorizationRequestEndpoint = "/oauth2/v1/par";
 		String tokenEndpoint = "/oauth2/v1/token";
 		String jwkSetEndpoint = "/oauth2/v1/jwks";
 		String userInfoEndpoint = "/userinfo";
@@ -106,6 +107,7 @@ public class OidcProviderConfigurationEndpointFilterTests {
 		AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder()
 			.issuer(issuer)
 			.authorizationEndpoint(authorizationEndpoint)
+			.pushedAuthorizationRequestEndpoint(pushedAuthorizationRequestEndpoint)
 			.tokenEndpoint(tokenEndpoint)
 			.jwkSetEndpoint(jwkSetEndpoint)
 			.oidcUserInfoEndpoint(userInfoEndpoint)
@@ -131,6 +133,8 @@ 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)
 			.contains("\"token_endpoint\":\"https://example.com/oauth2/v1/token\"");
 		assertThat(providerConfigurationResponse).contains("\"jwks_uri\":\"https://example.com/oauth2/v1/jwks\"");

+ 4 - 0
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java

@@ -96,6 +96,7 @@ public class OAuth2AuthorizationServerMetadataEndpointFilterTests {
 	public void doFilterWhenAuthorizationServerMetadataRequestThenMetadataResponse() throws Exception {
 		String issuer = "https://example.com";
 		String authorizationEndpoint = "/oauth2/v1/authorize";
+		String pushedAuthorizationRequestEndpoint = "/oauth2/v1/par";
 		String tokenEndpoint = "/oauth2/v1/token";
 		String jwkSetEndpoint = "/oauth2/v1/jwks";
 		String tokenRevocationEndpoint = "/oauth2/v1/revoke";
@@ -104,6 +105,7 @@ public class OAuth2AuthorizationServerMetadataEndpointFilterTests {
 		AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder()
 			.issuer(issuer)
 			.authorizationEndpoint(authorizationEndpoint)
+			.pushedAuthorizationRequestEndpoint(pushedAuthorizationRequestEndpoint)
 			.tokenEndpoint(tokenEndpoint)
 			.jwkSetEndpoint(jwkSetEndpoint)
 			.tokenRevocationEndpoint(tokenRevocationEndpoint)
@@ -127,6 +129,8 @@ 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)
 			.contains("\"token_endpoint\":\"https://example.com/oauth2/v1/token\"");
 		assertThat(authorizationServerMetadataResponse).contains(