Joe Grandja 3 rokov pred
rodič
commit
12ae92b366

+ 19 - 35
oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java

@@ -33,7 +33,6 @@ import com.nimbusds.jose.jwk.source.JWKSource;
 import org.springframework.core.annotation.AnnotationUtils;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.HttpStatus;
-import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.config.Customizer;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
 import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
@@ -44,14 +43,11 @@ import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.oauth2.core.OAuth2Token;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
-import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
-import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationProvider;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
 import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
 import org.springframework.security.oauth2.server.authorization.web.NimbusJwkSetEndpointFilter;
 import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter;
-import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenIntrospectionEndpointFilter;
-import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenRevocationEndpointFilter;
 import org.springframework.security.oauth2.server.authorization.web.ProviderContextFilter;
 import org.springframework.security.web.authentication.HttpStatusEntryPoint;
 import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
@@ -78,13 +74,12 @@ import org.springframework.util.Assert;
  * @see OAuth2ClientAuthenticationConfigurer
  * @see OAuth2AuthorizationEndpointConfigurer
  * @see OAuth2TokenEndpointConfigurer
+ * @see OAuth2TokenIntrospectionEndpointConfigurer
  * @see OAuth2TokenRevocationEndpointConfigurer
  * @see OidcConfigurer
  * @see RegisteredClientRepository
  * @see OAuth2AuthorizationService
  * @see OAuth2AuthorizationConsentService
- * @see OAuth2TokenIntrospectionEndpointFilter
- * @see OAuth2TokenRevocationEndpointFilter
  * @see NimbusJwkSetEndpointFilter
  * @see OAuth2AuthorizationServerMetadataEndpointFilter
  */
@@ -92,15 +87,14 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 		extends AbstractHttpConfigurer<OAuth2AuthorizationServerConfigurer<B>, B> {
 
 	private final Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = createConfigurers();
-	private RequestMatcher tokenIntrospectionEndpointMatcher;
 	private RequestMatcher jwkSetEndpointMatcher;
 	private RequestMatcher authorizationServerMetadataEndpointMatcher;
 	private final RequestMatcher endpointsMatcher = (request) ->
 			getRequestMatcher(OAuth2AuthorizationEndpointConfigurer.class).matches(request) ||
 			getRequestMatcher(OAuth2TokenEndpointConfigurer.class).matches(request) ||
+			getRequestMatcher(OAuth2TokenIntrospectionEndpointConfigurer.class).matches(request) ||
 			getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class).matches(request) ||
 			getRequestMatcher(OidcConfigurer.class).matches(request) ||
-			getRequestMatcher(OAuth2TokenIntrospectionConfigurer.class).matches(request) ||
 			this.jwkSetEndpointMatcher.matches(request) ||
 			this.authorizationServerMetadataEndpointMatcher.matches(request);
 
@@ -198,6 +192,18 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 		return this;
 	}
 
+	/**
+	 * Configures the OAuth 2.0 Token Introspection Endpoint.
+	 *
+	 * @param tokenIntrospectionEndpointCustomizer the {@link Customizer} providing access to the {@link OAuth2TokenIntrospectionEndpointConfigurer}
+	 * @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
+	 * @since 0.2.3
+	 */
+	public OAuth2AuthorizationServerConfigurer<B> tokenIntrospectionEndpoint(Customizer<OAuth2TokenIntrospectionEndpointConfigurer> tokenIntrospectionEndpointCustomizer) {
+		tokenIntrospectionEndpointCustomizer.customize(getConfigurer(OAuth2TokenIntrospectionEndpointConfigurer.class));
+		return this;
+	}
+
 	/**
 	 * Configures the OAuth 2.0 Token Revocation Endpoint.
 	 *
@@ -221,19 +227,6 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 		return this;
 	}
 
-	/**
-	 * Configures the OAuth 2.0 Token Introspection Endpoint.
-	 *
-	 * @param tokenIntrospectionEndpointCustomizer the {@link Customizer} providing access to the {@link OAuth2TokenIntrospectionConfigurer}
-	 * @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
-	 * @since 0.2.3
-	 */
-	public OAuth2AuthorizationServerConfigurer<B> tokenIntrospectionEndpoint(Customizer<OAuth2TokenIntrospectionConfigurer> tokenIntrospectionEndpointCustomizer) {
-		tokenIntrospectionEndpointCustomizer.customize(getConfigurer(OAuth2TokenIntrospectionConfigurer.class));
-		return this;
-	}
-
-
 	/**
 	 * Returns a {@link RequestMatcher} for the authorization server endpoints.
 	 *
@@ -251,20 +244,14 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 
 		this.configurers.values().forEach(configurer -> configurer.init(builder));
 
-		OAuth2TokenIntrospectionAuthenticationProvider tokenIntrospectionAuthenticationProvider =
-				new OAuth2TokenIntrospectionAuthenticationProvider(
-						OAuth2ConfigurerUtils.getRegisteredClientRepository(builder),
-						OAuth2ConfigurerUtils.getAuthorizationService(builder));
-		builder.authenticationProvider(postProcess(tokenIntrospectionAuthenticationProvider));
-
 		ExceptionHandlingConfigurer<B> exceptionHandling = builder.getConfigurer(ExceptionHandlingConfigurer.class);
 		if (exceptionHandling != null) {
 			exceptionHandling.defaultAuthenticationEntryPointFor(
 					new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
 					new OrRequestMatcher(
 							getRequestMatcher(OAuth2TokenEndpointConfigurer.class),
-							getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class),
-							this.tokenIntrospectionEndpointMatcher)
+							getRequestMatcher(OAuth2TokenIntrospectionEndpointConfigurer.class),
+							getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class))
 			);
 		}
 
@@ -362,8 +349,8 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 
 				List<RequestMatcher> requestMatchers = new ArrayList<>();
 				requestMatchers.add(getRequestMatcher(OAuth2TokenEndpointConfigurer.class));
+				requestMatchers.add(getRequestMatcher(OAuth2TokenIntrospectionEndpointConfigurer.class));
 				requestMatchers.add(getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class));
-				requestMatchers.add(OAuth2AuthorizationServerConfigurer.this.tokenIntrospectionEndpointMatcher);
 				return new OrRequestMatcher(requestMatchers);
 			}
 
@@ -395,7 +382,6 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 		this.configurers.values().forEach(configurer -> configurer.configure(builder));
 
 		ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
-		AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
 
 		ProviderContextFilter providerContextFilter = new ProviderContextFilter(providerSettings);
 		builder.addFilterAfter(postProcess(providerContextFilter), SecurityContextPersistenceFilter.class);
@@ -417,9 +403,9 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 		configurers.put(OAuth2ClientAuthenticationConfigurer.class, new OAuth2ClientAuthenticationConfigurer(this::postProcess));
 		configurers.put(OAuth2AuthorizationEndpointConfigurer.class, new OAuth2AuthorizationEndpointConfigurer(this::postProcess));
 		configurers.put(OAuth2TokenEndpointConfigurer.class, new OAuth2TokenEndpointConfigurer(this::postProcess));
+		configurers.put(OAuth2TokenIntrospectionEndpointConfigurer.class, new OAuth2TokenIntrospectionEndpointConfigurer(this::postProcess));
 		configurers.put(OAuth2TokenRevocationEndpointConfigurer.class, new OAuth2TokenRevocationEndpointConfigurer(this::postProcess));
 		configurers.put(OidcConfigurer.class, new OidcConfigurer(this::postProcess));
-		configurers.put(OAuth2TokenIntrospectionConfigurer.class, new OAuth2TokenIntrospectionConfigurer(this::postProcess));
 		return configurers;
 	}
 
@@ -433,8 +419,6 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 	}
 
 	private void initEndpointMatchers(ProviderSettings providerSettings) {
-		this.tokenIntrospectionEndpointMatcher = new AntPathRequestMatcher(
-				providerSettings.getTokenIntrospectionEndpoint(), HttpMethod.POST.name());
 		this.jwkSetEndpointMatcher = new AntPathRequestMatcher(
 				providerSettings.getJwkSetEndpoint(), HttpMethod.GET.name());
 		this.authorizationServerMetadataEndpointMatcher = new AntPathRequestMatcher(

+ 48 - 46
oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenIntrospectionConfigurer.java → oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenIntrospectionEndpointConfigurer.java

@@ -15,13 +15,20 @@
  */
 package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
 
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.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.Customizer;
 import org.springframework.security.config.annotation.ObjectPostProcessor;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2Error;
-import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
 import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationProvider;
 import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken;
 import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
@@ -34,41 +41,37 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.util.Assert;
 
-import javax.servlet.http.HttpServletRequest;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
 /**
- * Configurer for OAuth 2.0 Token Introspection.
+ * Configurer for the OAuth 2.0 Token Introspection Endpoint.
  *
  * @author Gaurav Tiwari
  * @since 0.2.3
+ * @see OAuth2AuthorizationServerConfigurer#tokenIntrospectionEndpoint(Customizer)
+ * @see OAuth2TokenIntrospectionEndpointFilter
  */
-public class OAuth2TokenIntrospectionConfigurer extends AbstractOAuth2Configurer {
-
+public final class OAuth2TokenIntrospectionEndpointConfigurer extends AbstractOAuth2Configurer {
 	private RequestMatcher requestMatcher;
-	private AuthenticationConverter accessTokenRequestConverter;
+	private AuthenticationConverter introspectionRequestConverter;
 	private final List<AuthenticationProvider> authenticationProviders = new LinkedList<>();
-	private AuthenticationSuccessHandler tokenIntrospectionResponseHandler;
+	private AuthenticationSuccessHandler introspectionResponseHandler;
 	private AuthenticationFailureHandler errorResponseHandler;
 
 	/**
 	 * Restrict for internal use only.
 	 */
-	OAuth2TokenIntrospectionConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
+	OAuth2TokenIntrospectionEndpointConfigurer(ObjectPostProcessor<Object> objectPostProcessor) {
 		super(objectPostProcessor);
 	}
 
 	/**
-	 * Sets the {@link AuthenticationConverter} used when attempting to extract an Access Token Request from {@link HttpServletRequest}
-	 * to an instance of {@link OAuth2AuthorizationGrantAuthenticationToken} used for authenticating the authorization grant.
+	 * Sets the {@link AuthenticationConverter} used when attempting to extract an Introspection Request from {@link HttpServletRequest}
+	 * to an instance of {@link OAuth2TokenIntrospectionAuthenticationToken} used for authenticating the request.
 	 *
-	 * @param accessTokenRequestConverter the {@link AuthenticationConverter} used when attempting to extract an Access Token Request from {@link HttpServletRequest}
-	 * @return the {@link OAuth2TokenIntrospectionConfigurer} for further configuration
+	 * @param introspectionRequestConverter the {@link AuthenticationConverter} used when attempting to extract an Introspection Request from {@link HttpServletRequest}
+	 * @return the {@link OAuth2TokenIntrospectionEndpointConfigurer} for further configuration
 	 */
-	public OAuth2TokenIntrospectionConfigurer accessTokenRequestConverter(AuthenticationConverter accessTokenRequestConverter) {
-		this.accessTokenRequestConverter = accessTokenRequestConverter;
+	public OAuth2TokenIntrospectionEndpointConfigurer introspectionRequestConverter(AuthenticationConverter introspectionRequestConverter) {
+		this.introspectionRequestConverter = introspectionRequestConverter;
 		return this;
 	}
 
@@ -76,9 +79,9 @@ public class OAuth2TokenIntrospectionConfigurer extends AbstractOAuth2Configurer
 	 * Adds an {@link AuthenticationProvider} used for authenticating a type of {@link OAuth2TokenIntrospectionAuthenticationToken}.
 	 *
 	 * @param authenticationProvider an {@link AuthenticationProvider} used for authenticating a type of {@link OAuth2TokenIntrospectionAuthenticationToken}
-	 * @return the {@link OAuth2TokenIntrospectionConfigurer} for further configuration
+	 * @return the {@link OAuth2TokenIntrospectionEndpointConfigurer} for further configuration
 	 */
-	public OAuth2TokenIntrospectionConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) {
+	public OAuth2TokenIntrospectionEndpointConfigurer authenticationProvider(AuthenticationProvider authenticationProvider) {
 		Assert.notNull(authenticationProvider, "authenticationProvider cannot be null");
 		this.authenticationProviders.add(authenticationProvider);
 		return this;
@@ -87,22 +90,22 @@ public class OAuth2TokenIntrospectionConfigurer extends AbstractOAuth2Configurer
 	/**
 	 * Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2TokenIntrospectionAuthenticationToken}.
 	 *
-	 * @param tokenIntrospectionResponseHandler the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2TokenIntrospectionAuthenticationToken}
-	 * @return the {@link OAuth2TokenEndpointConfigurer} for further configuration
+	 * @param introspectionResponseHandler the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2TokenIntrospectionAuthenticationToken}
+	 * @return the {@link OAuth2TokenIntrospectionEndpointConfigurer} for further configuration
 	 */
-	public OAuth2TokenIntrospectionConfigurer accessTokenResponseHandler(AuthenticationSuccessHandler tokenIntrospectionResponseHandler) {
-		this.tokenIntrospectionResponseHandler = tokenIntrospectionResponseHandler;
+	public OAuth2TokenIntrospectionEndpointConfigurer introspectionResponseHandler(AuthenticationSuccessHandler introspectionResponseHandler) {
+		this.introspectionResponseHandler = introspectionResponseHandler;
 		return this;
 	}
 
 	/**
-	 * Sets the {@link AuthenticationFailureHandler} used for handling an {@link org.springframework.security.oauth2.core.OAuth2AuthenticationException}
+	 * Sets the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
 	 * and returning the {@link OAuth2Error Error Response}.
 	 *
-	 * @param errorResponseHandler the {@link AuthenticationFailureHandler} used for handling an {@link org.springframework.security.oauth2.core.OAuth2AuthenticationException}
-	 * @return the {@link OAuth2TokenIntrospectionConfigurer} for further configuration
+	 * @param errorResponseHandler the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
+	 * @return the {@link OAuth2TokenIntrospectionEndpointConfigurer} for further configuration
 	 */
-	public OAuth2TokenIntrospectionConfigurer errorResponseHandler(AuthenticationFailureHandler errorResponseHandler) {
+	public OAuth2TokenIntrospectionEndpointConfigurer errorResponseHandler(AuthenticationFailureHandler errorResponseHandler) {
 		this.errorResponseHandler = errorResponseHandler;
 		return this;
 	}
@@ -113,8 +116,12 @@ public class OAuth2TokenIntrospectionConfigurer extends AbstractOAuth2Configurer
 		this.requestMatcher = new AntPathRequestMatcher(
 				providerSettings.getTokenIntrospectionEndpoint(), HttpMethod.POST.name());
 
-		List<AuthenticationProvider> authenticationProviders = this.authenticationProviders.isEmpty() ? createDefaultAuthenticationProviders(builder) : this.authenticationProviders;
-		authenticationProviders.forEach(authenticationProvider -> builder.authenticationProvider(postProcess(authenticationProvider)));
+		List<AuthenticationProvider> authenticationProviders =
+				!this.authenticationProviders.isEmpty() ?
+						this.authenticationProviders :
+						createDefaultAuthenticationProviders(builder);
+		authenticationProviders.forEach(authenticationProvider ->
+				builder.authenticationProvider(postProcess(authenticationProvider)));
 	}
 
 	@Override
@@ -123,20 +130,17 @@ public class OAuth2TokenIntrospectionConfigurer extends AbstractOAuth2Configurer
 		ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
 
 		OAuth2TokenIntrospectionEndpointFilter introspectionEndpointFilter =
-				new OAuth2TokenIntrospectionEndpointFilter(authenticationManager, providerSettings.getTokenIntrospectionEndpoint());
-
-		if (accessTokenRequestConverter != null) {
-			introspectionEndpointFilter.setAuthenticationConverter(this.accessTokenRequestConverter);
+				new OAuth2TokenIntrospectionEndpointFilter(
+						authenticationManager, providerSettings.getTokenIntrospectionEndpoint());
+		if (this.introspectionRequestConverter != null) {
+			introspectionEndpointFilter.setAuthenticationConverter(this.introspectionRequestConverter);
 		}
-
-		if (this.tokenIntrospectionResponseHandler != null) {
-			introspectionEndpointFilter.setAuthenticationSuccessHandler(this.tokenIntrospectionResponseHandler);
+		if (this.introspectionResponseHandler != null) {
+			introspectionEndpointFilter.setAuthenticationSuccessHandler(this.introspectionResponseHandler);
 		}
-
 		if (this.errorResponseHandler != null) {
 			introspectionEndpointFilter.setAuthenticationFailureHandler(this.errorResponseHandler);
 		}
-
 		builder.addFilterAfter(postProcess(introspectionEndpointFilter), FilterSecurityInterceptor.class);
 	}
 
@@ -148,15 +152,13 @@ public class OAuth2TokenIntrospectionConfigurer extends AbstractOAuth2Configurer
 	private <B extends HttpSecurityBuilder<B>> List<AuthenticationProvider> createDefaultAuthenticationProviders(B builder) {
 		List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
 
-		OAuth2TokenIntrospectionAuthenticationProvider tokenIntrospectionAuthenticationProvider
-				= new OAuth2TokenIntrospectionAuthenticationProvider(
-				OAuth2ConfigurerUtils.getRegisteredClientRepository(builder),
-				OAuth2ConfigurerUtils.getAuthorizationService(builder)
-		);
-
+		OAuth2TokenIntrospectionAuthenticationProvider tokenIntrospectionAuthenticationProvider =
+				new OAuth2TokenIntrospectionAuthenticationProvider(
+						OAuth2ConfigurerUtils.getRegisteredClientRepository(builder),
+						OAuth2ConfigurerUtils.getAuthorizationService(builder));
 		authenticationProviders.add(tokenIntrospectionAuthenticationProvider);
 
 		return authenticationProviders;
-
 	}
+
 }

+ 9 - 32
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/OAuth2TokenIntrospection.java

@@ -34,7 +34,6 @@ import org.springframework.util.Assert;
  *
  * @author Gerardo Roza
  * @author Joe Grandja
- * @author Gaurav Tiwari
  * @since 0.1.1
  * @see OAuth2TokenIntrospectionClaimAccessor
  * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7662#section-2.2">Section 2.2 Introspection Response</a>
@@ -258,28 +257,6 @@ public final class OAuth2TokenIntrospection implements OAuth2TokenIntrospectionC
 			return this;
 		}
 
-		/**
-		 * Adds custom claims if corresponding keys don't exist in present set of claims.
-		 *
-		 * @since 0.2.3
-		 * @param presentedClaims map of all claims
-		 * @return the {@link Builder} for further configuration
-		 */
-		public Builder withCustomClaims(Map<String, Object> presentedClaims) {
-
-			if (presentedClaims != null && !presentedClaims.isEmpty()) {
-
-				presentedClaims.keySet().forEach(key -> {
-					if (!this.claims.containsKey(key)) {
-						this.claim(key, presentedClaims.get(key));
-					}
-				});
-
-			}
-
-			return this;
-		}
-
 		/**
 		 * Provides access to every {@link #claim(String, Object)} declared so far with
 		 * the possibility to add, replace, or remove.
@@ -335,6 +312,15 @@ public final class OAuth2TokenIntrospection implements OAuth2TokenIntrospectionC
 			((List<String>) this.claims.get(name)).add(value);
 		}
 
+		@SuppressWarnings("unchecked")
+		private void acceptClaimValues(String name, Consumer<List<String>> valuesConsumer) {
+			Assert.hasText(name, "name cannot be empty");
+			Assert.notNull(valuesConsumer, "valuesConsumer cannot be null");
+			this.claims.computeIfAbsent(name, k -> new LinkedList<String>());
+			List<String> values = (List<String>) this.claims.get(name);
+			valuesConsumer.accept(values);
+		}
+
 		private static void validateURL(Object url, String errorMessage) {
 			if (URL.class.isAssignableFrom(url.getClass())) {
 				return;
@@ -346,14 +332,5 @@ public final class OAuth2TokenIntrospection implements OAuth2TokenIntrospectionC
 				throw new IllegalArgumentException(errorMessage, ex);
 			}
 		}
-
-		@SuppressWarnings("unchecked")
-		private void acceptClaimValues(String name, Consumer<List<String>> valuesConsumer) {
-			Assert.hasText(name, "name cannot be empty");
-			Assert.notNull(valuesConsumer, "valuesConsumer cannot be null");
-			this.claims.computeIfAbsent(name, k -> new LinkedList<String>());
-			List<String> values = (List<String>) this.claims.get(name);
-			valuesConsumer.accept(values);
-		}
 	}
 }

+ 49 - 29
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProvider.java

@@ -16,23 +16,25 @@
 package org.springframework.security.oauth2.server.authorization.authentication;
 
 import java.net.URL;
-import java.time.Instant;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
+import org.springframework.core.convert.TypeDescriptor;
 import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.oauth2.core.AbstractOAuth2Token;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
-import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimAccessor;
 import org.springframework.security.oauth2.core.OAuth2TokenIntrospection;
+import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames;
+import org.springframework.security.oauth2.core.converter.ClaimConversionService;
 import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
 import org.springframework.util.Assert;
 import org.springframework.util.CollectionUtils;
-import org.springframework.util.StringUtils;
 
 import static org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthenticationProviderUtils.getAuthenticatedClientElseThrowInvalidClient;
 
@@ -41,7 +43,6 @@ import static org.springframework.security.oauth2.server.authorization.authentic
  *
  * @author Gerardo Roza
  * @author Joe Grandja
- * @author Gaurav Tiwari
  * @since 0.1.1
  * @see OAuth2TokenIntrospectionAuthenticationToken
  * @see RegisteredClientRepository
@@ -49,6 +50,9 @@ import static org.springframework.security.oauth2.server.authorization.authentic
  * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7662#section-2.1">Section 2.1 Introspection Request</a>
  */
 public final class OAuth2TokenIntrospectionAuthenticationProvider implements AuthenticationProvider {
+	private static final TypeDescriptor OBJECT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Object.class);
+	private static final TypeDescriptor LIST_STRING_TYPE_DESCRIPTOR =
+			TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class));
 	private final RegisteredClientRepository registeredClientRepository;
 	private final OAuth2AuthorizationService authorizationService;
 
@@ -103,8 +107,15 @@ public final class OAuth2TokenIntrospectionAuthenticationProvider implements Aut
 	private static OAuth2TokenIntrospection withActiveTokenClaims(
 			OAuth2Authorization.Token<AbstractOAuth2Token> authorizedToken, RegisteredClient authorizedClient) {
 
-		OAuth2TokenIntrospection.Builder tokenClaims = OAuth2TokenIntrospection.builder(true)
-				.clientId(authorizedClient.getClientId());
+		OAuth2TokenIntrospection.Builder tokenClaims;
+		if (!CollectionUtils.isEmpty(authorizedToken.getClaims())) {
+			Map<String, Object> claims = convertClaimsIfNecessary(authorizedToken.getClaims());
+			tokenClaims = OAuth2TokenIntrospection.withClaims(claims).active(true);
+		} else {
+			tokenClaims = OAuth2TokenIntrospection.builder(true);
+		}
+
+		tokenClaims.clientId(authorizedClient.getClientId());
 
 		// TODO Set "username"
 
@@ -118,34 +129,43 @@ public final class OAuth2TokenIntrospectionAuthenticationProvider implements Aut
 
 		if (OAuth2AccessToken.class.isAssignableFrom(token.getClass())) {
 			OAuth2AccessToken accessToken = (OAuth2AccessToken) token;
-			tokenClaims.scopes(scopes -> scopes.addAll(accessToken.getScopes()));
 			tokenClaims.tokenType(accessToken.getTokenType().getValue());
+		}
+
+		return tokenClaims.build();
+	}
+
+	private static Map<String, Object> convertClaimsIfNecessary(Map<String, Object> claims) {
+		Map<String, Object> convertedClaims = new HashMap<>(claims);
 
-			if (!CollectionUtils.isEmpty(authorizedToken.getClaims())) {
-				OAuth2TokenClaimAccessor accessTokenClaims = authorizedToken::getClaims;
-
-				Instant notBefore = accessTokenClaims.getNotBefore();
-				if (notBefore != null) {
-					tokenClaims.notBefore(notBefore);
-				}
-				tokenClaims.subject(accessTokenClaims.getSubject());
-				List<String> audience = accessTokenClaims.getAudience();
-				if (!CollectionUtils.isEmpty(audience)) {
-					tokenClaims.audiences(audiences -> audiences.addAll(audience));
-				}
-				URL issuer = accessTokenClaims.getIssuer();
-				if (issuer != null) {
-					tokenClaims.issuer(issuer.toExternalForm());
-				}
-				String jti = accessTokenClaims.getId();
-				if (StringUtils.hasText(jti)) {
-					tokenClaims.id(jti);
-				}
+		Object value = claims.get(OAuth2TokenIntrospectionClaimNames.ISS);
+		if (value != null && !(value instanceof URL)) {
+			URL convertedValue = ClaimConversionService.getSharedInstance()
+					.convert(value, URL.class);
+			if (convertedValue != null) {
+				convertedClaims.put(OAuth2TokenIntrospectionClaimNames.ISS, convertedValue);
 			}
 		}
 
-		tokenClaims.withCustomClaims(authorizedToken.getClaims());
+		value = claims.get(OAuth2TokenIntrospectionClaimNames.SCOPE);
+		if (value != null && !(value instanceof List)) {
+			Object convertedValue = ClaimConversionService.getSharedInstance()
+					.convert(value, OBJECT_TYPE_DESCRIPTOR, LIST_STRING_TYPE_DESCRIPTOR);
+			if (convertedValue != null) {
+				convertedClaims.put(OAuth2TokenIntrospectionClaimNames.SCOPE, convertedValue);
+			}
+		}
 
-		return tokenClaims.build();
+		value = claims.get(OAuth2TokenIntrospectionClaimNames.AUD);
+		if (value != null && !(value instanceof List)) {
+			Object convertedValue = ClaimConversionService.getSharedInstance()
+					.convert(value, OBJECT_TYPE_DESCRIPTOR, LIST_STRING_TYPE_DESCRIPTOR);
+			if (convertedValue != null) {
+				convertedClaims.put(OAuth2TokenIntrospectionClaimNames.AUD, convertedValue);
+			}
+		}
+
+		return convertedClaims;
 	}
+
 }

+ 26 - 26
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenIntrospectionEndpointFilter.java

@@ -70,12 +70,12 @@ public final class OAuth2TokenIntrospectionEndpointFilter extends OncePerRequest
 
 	private final AuthenticationManager authenticationManager;
 	private final RequestMatcher tokenIntrospectionEndpointMatcher;
-	private AuthenticationConverter tokenIntrospectionAuthenticationConverter =
+	private AuthenticationConverter authenticationConverter =
 			new DefaultTokenIntrospectionAuthenticationConverter();
 	private final HttpMessageConverter<OAuth2TokenIntrospection> tokenIntrospectionHttpResponseConverter =
 			new OAuth2TokenIntrospectionHttpMessageConverter();
 	private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter();
-	private AuthenticationSuccessHandler authenticationSuccessHandler = this::sendTokenIntrospectionResponse;;
+	private AuthenticationSuccessHandler authenticationSuccessHandler = this::sendIntrospectionResponse;
 	private AuthenticationFailureHandler authenticationFailureHandler = this::sendErrorResponse;
 
 	/**
@@ -112,14 +112,10 @@ public final class OAuth2TokenIntrospectionEndpointFilter extends OncePerRequest
 		}
 
 		try {
-			OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthentication =
-					(OAuth2TokenIntrospectionAuthenticationToken) this.tokenIntrospectionAuthenticationConverter.convert(request);
-
-			OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthenticationResult =
-					(OAuth2TokenIntrospectionAuthenticationToken) this.authenticationManager.authenticate(tokenIntrospectionAuthentication);
-
+			Authentication tokenIntrospectionAuthentication = this.authenticationConverter.convert(request);
+			Authentication tokenIntrospectionAuthenticationResult =
+					this.authenticationManager.authenticate(tokenIntrospectionAuthentication);
 			this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, tokenIntrospectionAuthenticationResult);
-
 		} catch (OAuth2AuthenticationException ex) {
 			SecurityContextHolder.clearContext();
 			this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex);
@@ -127,50 +123,52 @@ public final class OAuth2TokenIntrospectionEndpointFilter extends OncePerRequest
 	}
 
 	/**
-	 * Sets the {@link AuthenticationConverter} used when attempting to extract a Token Introspection Request from
-	 * {@link HttpServletRequest} to an instance of {@link OAuth2TokenIntrospectionAuthenticationToken} used for authenticating the request.
+	 * Sets the {@link AuthenticationConverter} used when attempting to extract an Introspection Request from {@link HttpServletRequest}
+	 * to an instance of {@link OAuth2TokenIntrospectionAuthenticationToken} used for authenticating the request.
 	 *
-	 * @param authenticationConverter the {@link AuthenticationConverter} used when attempting to extract a Token Introspection Request from {@link HttpServletRequest}
+	 * @param authenticationConverter the {@link AuthenticationConverter} used when attempting to extract an Introspection Request from {@link HttpServletRequest}
 	 * @since 0.2.3
 	 */
 	public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) {
-		Assert.notNull(authenticationConverter, "authenticationConverter cannot be null.");
-		this.tokenIntrospectionAuthenticationConverter = authenticationConverter;
+		Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
+		this.authenticationConverter = authenticationConverter;
 	}
 
 	/**
-	 * Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2TokenIntrospectionAuthenticationToken}
+	 * Sets the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2TokenIntrospectionAuthenticationToken}.
 	 *
 	 * @param authenticationSuccessHandler the {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2TokenIntrospectionAuthenticationToken}
 	 * @since 0.2.3
 	 */
 	public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler authenticationSuccessHandler) {
-		Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null.");
+		Assert.notNull(authenticationSuccessHandler, "authenticationSuccessHandler cannot be null");
 		this.authenticationSuccessHandler = authenticationSuccessHandler;
 	}
 
 	/**
-	 * Sets the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException} and
-	 * returning {@link OAuth2Error Error Resonse}.
+	 * Sets the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
+	 * and returning the {@link OAuth2Error Error Resonse}.
 	 *
-	 * @param authenticationFailureHandler the {@link .AuthenticationFailureHandler} used for handling {@link OAuth2AuthenticationException}
+	 * @param authenticationFailureHandler the {@link AuthenticationFailureHandler} used for handling an {@link OAuth2AuthenticationException}
 	 * @since 0.2.3
 	 */
 	public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
-		Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null.");
+		Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null");
 		this.authenticationFailureHandler = authenticationFailureHandler;
 	}
 
-	private void sendTokenIntrospectionResponse(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
-
-		OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthenticationResult = (OAuth2TokenIntrospectionAuthenticationToken) authentication;
-		OAuth2TokenIntrospection tokenClaims = tokenIntrospectionAuthenticationResult.getTokenClaims();
+	private void sendIntrospectionResponse(HttpServletRequest request, HttpServletResponse response,
+			Authentication authentication) throws IOException {
 
+		OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthentication =
+				(OAuth2TokenIntrospectionAuthenticationToken) authentication;
+		OAuth2TokenIntrospection tokenClaims = tokenIntrospectionAuthentication.getTokenClaims();
 		ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
 		this.tokenIntrospectionHttpResponseConverter.write(tokenClaims, null, httpResponse);
 	}
 
-	private void sendErrorResponse(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
+	private void sendErrorResponse(HttpServletRequest request, HttpServletResponse response,
+			AuthenticationException exception) throws IOException {
 		OAuth2Error error = ((OAuth2AuthenticationException) exception).getError();
 		ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
 		httpResponse.setStatusCode(HttpStatus.BAD_REQUEST);
@@ -179,7 +177,7 @@ public final class OAuth2TokenIntrospectionEndpointFilter extends OncePerRequest
 
 	private static void throwError(String errorCode, String parameterName) {
 		OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Token Introspection Parameter: " + parameterName,
-				"https://tools.ietf.org/html/rfc7662#section-2.1");
+				"https://datatracker.ietf.org/doc/html/rfc7662#section-2.1");
 		throw new OAuth2AuthenticationException(error);
 	}
 
@@ -217,5 +215,7 @@ public final class OAuth2TokenIntrospectionEndpointFilter extends OncePerRequest
 			return new OAuth2TokenIntrospectionAuthenticationToken(
 					token, clientPrincipal, tokenTypeHint, additionalParameters);
 		}
+
 	}
+
 }

+ 94 - 9
oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenIntrospectionTests.java

@@ -26,9 +26,6 @@ import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 
-import com.nimbusds.jose.jwk.JWKSet;
-import com.nimbusds.jose.jwk.source.JWKSource;
-import com.nimbusds.jose.proc.SecurityContext;
 import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
@@ -49,34 +46,38 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
 import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
 import org.springframework.mock.http.client.MockClientHttpResponse;
 import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
 import org.springframework.security.config.test.SpringTestRule;
+import org.springframework.security.core.Authentication;
 import org.springframework.security.crypto.password.NoOpPasswordEncoder;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.oauth2.core.AbstractOAuth2Token;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AuthorizationCode;
 import org.springframework.security.oauth2.core.OAuth2RefreshToken;
-import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsSet;
 import org.springframework.security.oauth2.core.OAuth2TokenFormat;
 import org.springframework.security.oauth2.core.OAuth2TokenIntrospection;
+import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames;
 import org.springframework.security.oauth2.core.OAuth2TokenType;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
 import org.springframework.security.oauth2.core.http.converter.OAuth2TokenIntrospectionHttpMessageConverter;
-import org.springframework.security.oauth2.jose.TestJwks;
 import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
 import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
 import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
-import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext;
 import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
 import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken;
 import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
 import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
@@ -85,14 +86,24 @@ import org.springframework.security.oauth2.server.authorization.client.TestRegis
 import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
 import org.springframework.security.oauth2.server.authorization.config.TokenSettings;
 import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsSet;
+import org.springframework.security.web.SecurityFilterChain;
+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.util.matcher.RequestMatcher;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.MvcResult;
 import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
@@ -104,9 +115,12 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
  */
 public class OAuth2TokenIntrospectionTests {
 	private static EmbeddedDatabase db;
-	private static JWKSource<SecurityContext> jwkSource;
 	private static ProviderSettings providerSettings;
 	private static OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer;
+	private static AuthenticationConverter authenticationConverter;
+	private static AuthenticationProvider authenticationProvider;
+	private static AuthenticationSuccessHandler authenticationSuccessHandler;
+	private static AuthenticationFailureHandler authenticationFailureHandler;
 	private static final HttpMessageConverter<OAuth2TokenIntrospection> tokenIntrospectionHttpResponseConverter =
 			new OAuth2TokenIntrospectionHttpMessageConverter();
 	private static final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter =
@@ -129,9 +143,11 @@ public class OAuth2TokenIntrospectionTests {
 
 	@BeforeClass
 	public static void init() {
-		JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK);
-		jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
 		providerSettings = ProviderSettings.builder().tokenIntrospectionEndpoint("/test/introspect").build();
+		authenticationConverter = mock(AuthenticationConverter.class);
+		authenticationProvider = mock(AuthenticationProvider.class);
+		authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class);
+		authenticationFailureHandler = mock(AuthenticationFailureHandler.class);
 		accessTokenCustomizer = mock(OAuth2TokenCustomizer.class);
 		db = new EmbeddedDatabaseBuilder()
 				.generateUniqueName(true)
@@ -175,6 +191,7 @@ public class OAuth2TokenIntrospectionTests {
 				.issuedAt(issuedAt)
 				.notBefore(issuedAt)
 				.expiresAt(expiresAt)
+				.claim(OAuth2TokenIntrospectionClaimNames.SCOPE, accessToken.getScopes())
 				.id("id")
 				.build();
 		// @formatter:on
@@ -314,6 +331,43 @@ public class OAuth2TokenIntrospectionTests {
 		assertThat(tokenIntrospectionResponse.getId()).isEqualTo(accessTokenClaims.getId());
 	}
 
+	@Test
+	public void requestWhenTokenIntrospectionEndpointCustomizedThenUsed() throws Exception {
+		this.spring.register(AuthorizationServerConfigurationCustomTokenIntrospectionEndpoint.class).autowire();
+
+		RegisteredClient introspectRegisteredClient = TestRegisteredClients.registeredClient2().build();
+		this.registeredClientRepository.save(introspectRegisteredClient);
+
+		RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient().build();
+		this.registeredClientRepository.save(authorizedRegisteredClient);
+
+		OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(authorizedRegisteredClient).build();
+		this.authorizationService.save(authorization);
+
+		OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
+
+		Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(
+				introspectRegisteredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, introspectRegisteredClient.getClientSecret());
+		OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthentication =
+				new OAuth2TokenIntrospectionAuthenticationToken(
+						accessToken.getTokenValue(), clientPrincipal, null, null);
+
+		when(authenticationConverter.convert(any())).thenReturn(tokenIntrospectionAuthentication);
+		when(authenticationProvider.supports(eq(OAuth2TokenIntrospectionAuthenticationToken.class))).thenReturn(true);
+		when(authenticationProvider.authenticate(any())).thenReturn(tokenIntrospectionAuthentication);
+
+		// @formatter:off
+		this.mvc.perform(post(providerSettings.getTokenIntrospectionEndpoint())
+				.params(getTokenIntrospectionRequestParameters(accessToken, OAuth2TokenType.ACCESS_TOKEN))
+				.header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient)))
+				.andExpect(status().isOk());
+		// @formatter:on
+
+		verify(authenticationConverter).convert(any());
+		verify(authenticationProvider).authenticate(eq(tokenIntrospectionAuthentication));
+		verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), eq(tokenIntrospectionAuthentication));
+	}
+
 	private static MultiValueMap<String, String> getTokenIntrospectionRequestParameters(AbstractOAuth2Token token,
 			OAuth2TokenType tokenType) {
 		MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
@@ -420,4 +474,35 @@ public class OAuth2TokenIntrospectionTests {
 		}
 
 	}
+
+	@EnableWebSecurity
+	static class AuthorizationServerConfigurationCustomTokenIntrospectionEndpoint extends AuthorizationServerConfiguration {
+
+		// @formatter:off
+		@Bean
+		public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
+			OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
+					new OAuth2AuthorizationServerConfigurer<>();
+			authorizationServerConfigurer
+					.tokenIntrospectionEndpoint(tokenIntrospectionEndpoint ->
+							tokenIntrospectionEndpoint
+									.introspectionRequestConverter(authenticationConverter)
+									.authenticationProvider(authenticationProvider)
+									.introspectionResponseHandler(authenticationSuccessHandler)
+									.errorResponseHandler(authenticationFailureHandler));
+			RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
+
+			http
+					.requestMatcher(endpointsMatcher)
+					.authorizeRequests(authorizeRequests ->
+							authorizeRequests.anyRequest().authenticated()
+					)
+					.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
+					.apply(authorizationServerConfigurer);
+			return http.build();
+		}
+		// @formatter:on
+
+	}
+
 }

+ 6 - 58
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProviderTests.java

@@ -31,15 +31,16 @@ import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
 import org.springframework.security.oauth2.core.OAuth2RefreshToken;
-import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimNames;
-import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsSet;
 import org.springframework.security.oauth2.core.OAuth2TokenIntrospection;
+import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames;
 import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
 import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
 import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimNames;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsSet;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -54,7 +55,6 @@ import static org.mockito.Mockito.when;
  *
  * @author Gerardo Roza
  * @author Joe Grandja
- * @author Gaurav Tiwari
  */
 public class OAuth2TokenIntrospectionAuthenticationProviderTests {
 	private RegisteredClientRepository registeredClientRepository;
@@ -227,6 +227,8 @@ public class OAuth2TokenIntrospectionAuthenticationProviderTests {
 				.notBefore(issuedAt)
 				.expiresAt(expiresAt)
 				.id("id")
+				.claim(OAuth2TokenIntrospectionClaimNames.SCOPE, accessToken.getScopes())
+				.claim("custom-claim", "custom-value")
 				.build();
 		// @formatter:on
 
@@ -253,68 +255,14 @@ public class OAuth2TokenIntrospectionAuthenticationProviderTests {
 		assertThat(tokenClaims.getClientId()).isEqualTo(authorizedClient.getClientId());
 		assertThat(tokenClaims.getIssuedAt()).isEqualTo(accessToken.getIssuedAt());
 		assertThat(tokenClaims.getExpiresAt()).isEqualTo(accessToken.getExpiresAt());
-		assertThat(tokenClaims.getScopes()).containsExactlyInAnyOrderElementsOf(accessToken.getScopes());
 		assertThat(tokenClaims.getTokenType()).isEqualTo(accessToken.getTokenType().getValue());
 		assertThat(tokenClaims.getNotBefore()).isEqualTo(claimsSet.getNotBefore());
 		assertThat(tokenClaims.getSubject()).isEqualTo(claimsSet.getSubject());
 		assertThat(tokenClaims.getAudience()).containsExactlyInAnyOrderElementsOf(claimsSet.getAudience());
 		assertThat(tokenClaims.getIssuer()).isEqualTo(claimsSet.getIssuer());
 		assertThat(tokenClaims.getId()).isEqualTo(claimsSet.getId());
-	}
-
-	@Test
-	public void authenticateWhenValidAccessTokenAndCustomClaimThenActiveAndCustomClaimInResponse() {
-		RegisteredClient authorizedClient = TestRegisteredClients.registeredClient().build();
-		Instant issuedAt = Instant.now();
-		Instant expiresAt = issuedAt.plus(Duration.ofHours(1));
-		OAuth2AccessToken accessToken = new OAuth2AccessToken(
-				OAuth2AccessToken.TokenType.BEARER, "access-token", issuedAt, expiresAt,
-				new HashSet<>(Arrays.asList("scope1", "scope2")));
-
-		// @formatter:off
-		OAuth2TokenClaimsSet claimsSet = OAuth2TokenClaimsSet.builder()
-				.issuer("https://provider.com")
-				.subject("subject")
-				.audience(Collections.singletonList(authorizedClient.getClientId()))
-				.issuedAt(issuedAt)
-				.notBefore(issuedAt)
-				.expiresAt(expiresAt)
-				.claim("custom-claim", "custom-claim-value")
-				.id("id")
-				.build();
-		// @formatter:on
-
-		OAuth2Authorization authorization = TestOAuth2Authorizations
-				.authorization(authorizedClient, accessToken, claimsSet.getClaims())
-				.build();
-		when(this.authorizationService.findByToken(eq(accessToken.getTokenValue()), isNull()))
-				.thenReturn(authorization);
-		when(this.registeredClientRepository.findById(eq(authorizedClient.getId()))).thenReturn(authorizedClient);
-		RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build();
-		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
-				registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
-
-		OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken(
-				accessToken.getTokenValue(), clientPrincipal, null, null);
-		OAuth2TokenIntrospectionAuthenticationToken authenticationResult =
-				(OAuth2TokenIntrospectionAuthenticationToken) this.authenticationProvider.authenticate(authentication);
-
-		verify(this.authorizationService).findByToken(eq(authentication.getToken()), isNull());
-		verify(this.registeredClientRepository).findById(eq(authorizedClient.getId()));
-		assertThat(authenticationResult.isAuthenticated()).isTrue();
-		OAuth2TokenIntrospection tokenClaims = authenticationResult.getTokenClaims();
-		assertThat(tokenClaims.isActive()).isTrue();
-		assertThat(tokenClaims.getClientId()).isEqualTo(authorizedClient.getClientId());
-		assertThat(tokenClaims.getIssuedAt()).isEqualTo(accessToken.getIssuedAt());
-		assertThat(tokenClaims.getExpiresAt()).isEqualTo(accessToken.getExpiresAt());
 		assertThat(tokenClaims.getScopes()).containsExactlyInAnyOrderElementsOf(accessToken.getScopes());
-		assertThat(tokenClaims.getTokenType()).isEqualTo(accessToken.getTokenType().getValue());
-		assertThat(tokenClaims.getNotBefore()).isEqualTo(claimsSet.getNotBefore());
-		assertThat(tokenClaims.getSubject()).isEqualTo(claimsSet.getSubject());
-		assertThat(tokenClaims.getAudience()).containsExactlyInAnyOrderElementsOf(claimsSet.getAudience());
-		assertThat(tokenClaims.getIssuer()).isEqualTo(claimsSet.getIssuer());
-		assertThat(tokenClaims.getId()).isEqualTo(claimsSet.getId());
-		assertThat((String) tokenClaims.getClaim("custom-claim")).isEqualTo("custom-claim-value");
+		assertThat(tokenClaims.<String>getClaim("custom-claim")).isEqualTo("custom-value");
 	}
 
 	@Test

+ 120 - 1
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenIntrospectionEndpointFilterTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2020-2021 the original author or authors.
+ * Copyright 2020-2022 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.
@@ -40,6 +40,7 @@ import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
 import org.springframework.security.oauth2.core.OAuth2TokenIntrospection;
@@ -51,6 +52,9 @@ import org.springframework.security.oauth2.server.authorization.authentication.O
 import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
 import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
+import org.springframework.security.web.authentication.AuthenticationConverter;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -101,6 +105,27 @@ public class OAuth2TokenIntrospectionEndpointFilterTests {
 				.hasMessage("tokenIntrospectionEndpointUri cannot be empty");
 	}
 
+	@Test
+	public void setAuthenticationConverterWhenNullThenThrowIllegalArgumentException() {
+		assertThatThrownBy(() -> this.filter.setAuthenticationConverter(null))
+				.isInstanceOf(IllegalArgumentException.class)
+				.hasMessage("authenticationConverter cannot be null");
+	}
+
+	@Test
+	public void setAuthenticationSuccessHandlerWhenNullThenThrowIllegalArgumentException() {
+		assertThatThrownBy(() -> this.filter.setAuthenticationSuccessHandler(null))
+				.isInstanceOf(IllegalArgumentException.class)
+				.hasMessage("authenticationSuccessHandler cannot be null");
+	}
+
+	@Test
+	public void setAuthenticationFailureHandlerWhenNullThenThrowIllegalArgumentException() {
+		assertThatThrownBy(() -> this.filter.setAuthenticationFailureHandler(null))
+				.isInstanceOf(IllegalArgumentException.class)
+				.hasMessage("authenticationFailureHandler cannot be null");
+	}
+
 	@Test
 	public void doFilterWhenNotTokenIntrospectionRequestThenNotProcessed() throws Exception {
 		String requestUri = "/path";
@@ -231,6 +256,100 @@ public class OAuth2TokenIntrospectionEndpointFilterTests {
 		assertThat(tokenIntrospectionResponse.getId()).isEqualTo(tokenClaims.getId());
 	}
 
+	@Test
+	public void doFilterWhenCustomAuthenticationConverterThenUsed() throws Exception {
+		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
+		Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(
+				registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
+		OAuth2AccessToken accessToken = new OAuth2AccessToken(
+				OAuth2AccessToken.TokenType.BEARER, "token",
+				Instant.now(), Instant.now().plus(Duration.ofHours(1)),
+				new HashSet<>(Arrays.asList("scope1", "scope2")));
+		OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthentication =
+				new OAuth2TokenIntrospectionAuthenticationToken(
+						accessToken.getTokenValue(), clientPrincipal, OAuth2TokenType.ACCESS_TOKEN.getValue(), null);
+
+		AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class);
+		when(authenticationConverter.convert(any())).thenReturn(tokenIntrospectionAuthentication);
+		this.filter.setAuthenticationConverter(authenticationConverter);
+
+		when(this.authenticationManager.authenticate(any())).thenReturn(tokenIntrospectionAuthentication);
+
+		SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
+		securityContext.setAuthentication(clientPrincipal);
+		SecurityContextHolder.setContext(securityContext);
+
+		MockHttpServletRequest request = createTokenIntrospectionRequest(
+				accessToken.getTokenValue(), OAuth2TokenType.ACCESS_TOKEN.getValue());
+		MockHttpServletResponse response = new MockHttpServletResponse();
+		FilterChain filterChain = mock(FilterChain.class);
+
+		this.filter.doFilter(request, response, filterChain);
+
+		verify(authenticationConverter).convert(any());
+	}
+
+	@Test
+	public void doFilterWhenCustomAuthenticationSuccessHandlerThenUsed() throws Exception {
+		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
+		Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(
+				registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
+		OAuth2AccessToken accessToken = new OAuth2AccessToken(
+				OAuth2AccessToken.TokenType.BEARER, "token",
+				Instant.now(), Instant.now().plus(Duration.ofHours(1)),
+				new HashSet<>(Arrays.asList("scope1", "scope2")));
+		OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthentication =
+				new OAuth2TokenIntrospectionAuthenticationToken(
+						accessToken.getTokenValue(), clientPrincipal, OAuth2TokenType.ACCESS_TOKEN.getValue(), null);
+
+		AuthenticationSuccessHandler authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class);
+		this.filter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
+
+		when(this.authenticationManager.authenticate(any())).thenReturn(tokenIntrospectionAuthentication);
+
+		SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
+		securityContext.setAuthentication(clientPrincipal);
+		SecurityContextHolder.setContext(securityContext);
+
+		MockHttpServletRequest request = createTokenIntrospectionRequest(
+				accessToken.getTokenValue(), OAuth2TokenType.ACCESS_TOKEN.getValue());
+		MockHttpServletResponse response = new MockHttpServletResponse();
+		FilterChain filterChain = mock(FilterChain.class);
+
+		this.filter.doFilter(request, response, filterChain);
+
+		verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), any());
+	}
+
+	@Test
+	public void doFilterWhenCustomAuthenticationFailureHandlerThenUsed() throws Exception {
+		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
+		Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(
+				registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
+		OAuth2AccessToken accessToken = new OAuth2AccessToken(
+				OAuth2AccessToken.TokenType.BEARER, "token",
+				Instant.now(), Instant.now().plus(Duration.ofHours(1)),
+				new HashSet<>(Arrays.asList("scope1", "scope2")));
+
+		AuthenticationFailureHandler authenticationFailureHandler = mock(AuthenticationFailureHandler.class);
+		this.filter.setAuthenticationFailureHandler(authenticationFailureHandler);
+
+		when(this.authenticationManager.authenticate(any())).thenThrow(OAuth2AuthenticationException.class);
+
+		SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
+		securityContext.setAuthentication(clientPrincipal);
+		SecurityContextHolder.setContext(securityContext);
+
+		MockHttpServletRequest request = createTokenIntrospectionRequest(
+				accessToken.getTokenValue(), OAuth2TokenType.ACCESS_TOKEN.getValue());
+		MockHttpServletResponse response = new MockHttpServletResponse();
+		FilterChain filterChain = mock(FilterChain.class);
+
+		this.filter.doFilter(request, response, filterChain);
+
+		verify(authenticationFailureHandler).onAuthenticationFailure(any(), any(), any());
+	}
+
 	private void doFilterWhenTokenIntrospectionRequestInvalidParameterThenError(String parameterName, String errorCode,
 			MockHttpServletRequest request) throws Exception {