Browse Source

Polish gh-630

pull/679/head
Joe Grandja 4 years ago
parent
commit
12ae92b366
  1. 54
      oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java
  2. 94
      oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenIntrospectionEndpointConfigurer.java
  3. 41
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/OAuth2TokenIntrospection.java
  4. 78
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProvider.java
  5. 52
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenIntrospectionEndpointFilter.java
  6. 103
      oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenIntrospectionTests.java
  7. 64
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProviderTests.java
  8. 121
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenIntrospectionEndpointFilterTests.java

54
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.core.annotation.AnnotationUtils;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer; import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; 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.core.OAuth2Token;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; 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.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; 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.NimbusJwkSetEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter; 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.oauth2.server.authorization.web.ProviderContextFilter;
import org.springframework.security.web.authentication.HttpStatusEntryPoint; import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
@ -78,13 +74,12 @@ import org.springframework.util.Assert;
* @see OAuth2ClientAuthenticationConfigurer * @see OAuth2ClientAuthenticationConfigurer
* @see OAuth2AuthorizationEndpointConfigurer * @see OAuth2AuthorizationEndpointConfigurer
* @see OAuth2TokenEndpointConfigurer * @see OAuth2TokenEndpointConfigurer
* @see OAuth2TokenIntrospectionEndpointConfigurer
* @see OAuth2TokenRevocationEndpointConfigurer * @see OAuth2TokenRevocationEndpointConfigurer
* @see OidcConfigurer * @see OidcConfigurer
* @see RegisteredClientRepository * @see RegisteredClientRepository
* @see OAuth2AuthorizationService * @see OAuth2AuthorizationService
* @see OAuth2AuthorizationConsentService * @see OAuth2AuthorizationConsentService
* @see OAuth2TokenIntrospectionEndpointFilter
* @see OAuth2TokenRevocationEndpointFilter
* @see NimbusJwkSetEndpointFilter * @see NimbusJwkSetEndpointFilter
* @see OAuth2AuthorizationServerMetadataEndpointFilter * @see OAuth2AuthorizationServerMetadataEndpointFilter
*/ */
@ -92,15 +87,14 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
extends AbstractHttpConfigurer<OAuth2AuthorizationServerConfigurer<B>, B> { extends AbstractHttpConfigurer<OAuth2AuthorizationServerConfigurer<B>, B> {
private final Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = createConfigurers(); private final Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> configurers = createConfigurers();
private RequestMatcher tokenIntrospectionEndpointMatcher;
private RequestMatcher jwkSetEndpointMatcher; private RequestMatcher jwkSetEndpointMatcher;
private RequestMatcher authorizationServerMetadataEndpointMatcher; private RequestMatcher authorizationServerMetadataEndpointMatcher;
private final RequestMatcher endpointsMatcher = (request) -> private final RequestMatcher endpointsMatcher = (request) ->
getRequestMatcher(OAuth2AuthorizationEndpointConfigurer.class).matches(request) || getRequestMatcher(OAuth2AuthorizationEndpointConfigurer.class).matches(request) ||
getRequestMatcher(OAuth2TokenEndpointConfigurer.class).matches(request) || getRequestMatcher(OAuth2TokenEndpointConfigurer.class).matches(request) ||
getRequestMatcher(OAuth2TokenIntrospectionEndpointConfigurer.class).matches(request) ||
getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class).matches(request) || getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class).matches(request) ||
getRequestMatcher(OidcConfigurer.class).matches(request) || getRequestMatcher(OidcConfigurer.class).matches(request) ||
getRequestMatcher(OAuth2TokenIntrospectionConfigurer.class).matches(request) ||
this.jwkSetEndpointMatcher.matches(request) || this.jwkSetEndpointMatcher.matches(request) ||
this.authorizationServerMetadataEndpointMatcher.matches(request); this.authorizationServerMetadataEndpointMatcher.matches(request);
@ -198,6 +192,18 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
return this; 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. * Configures the OAuth 2.0 Token Revocation Endpoint.
* *
@ -221,19 +227,6 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
return this; 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. * 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)); 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); ExceptionHandlingConfigurer<B> exceptionHandling = builder.getConfigurer(ExceptionHandlingConfigurer.class);
if (exceptionHandling != null) { if (exceptionHandling != null) {
exceptionHandling.defaultAuthenticationEntryPointFor( exceptionHandling.defaultAuthenticationEntryPointFor(
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
new OrRequestMatcher( new OrRequestMatcher(
getRequestMatcher(OAuth2TokenEndpointConfigurer.class), getRequestMatcher(OAuth2TokenEndpointConfigurer.class),
getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class), getRequestMatcher(OAuth2TokenIntrospectionEndpointConfigurer.class),
this.tokenIntrospectionEndpointMatcher) getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class))
); );
} }
@ -362,8 +349,8 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
List<RequestMatcher> requestMatchers = new ArrayList<>(); List<RequestMatcher> requestMatchers = new ArrayList<>();
requestMatchers.add(getRequestMatcher(OAuth2TokenEndpointConfigurer.class)); requestMatchers.add(getRequestMatcher(OAuth2TokenEndpointConfigurer.class));
requestMatchers.add(getRequestMatcher(OAuth2TokenIntrospectionEndpointConfigurer.class));
requestMatchers.add(getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class)); requestMatchers.add(getRequestMatcher(OAuth2TokenRevocationEndpointConfigurer.class));
requestMatchers.add(OAuth2AuthorizationServerConfigurer.this.tokenIntrospectionEndpointMatcher);
return new OrRequestMatcher(requestMatchers); return new OrRequestMatcher(requestMatchers);
} }
@ -395,7 +382,6 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
this.configurers.values().forEach(configurer -> configurer.configure(builder)); this.configurers.values().forEach(configurer -> configurer.configure(builder));
ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder); ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
ProviderContextFilter providerContextFilter = new ProviderContextFilter(providerSettings); ProviderContextFilter providerContextFilter = new ProviderContextFilter(providerSettings);
builder.addFilterAfter(postProcess(providerContextFilter), SecurityContextPersistenceFilter.class); 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(OAuth2ClientAuthenticationConfigurer.class, new OAuth2ClientAuthenticationConfigurer(this::postProcess));
configurers.put(OAuth2AuthorizationEndpointConfigurer.class, new OAuth2AuthorizationEndpointConfigurer(this::postProcess)); configurers.put(OAuth2AuthorizationEndpointConfigurer.class, new OAuth2AuthorizationEndpointConfigurer(this::postProcess));
configurers.put(OAuth2TokenEndpointConfigurer.class, new OAuth2TokenEndpointConfigurer(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(OAuth2TokenRevocationEndpointConfigurer.class, new OAuth2TokenRevocationEndpointConfigurer(this::postProcess));
configurers.put(OidcConfigurer.class, new OidcConfigurer(this::postProcess)); configurers.put(OidcConfigurer.class, new OidcConfigurer(this::postProcess));
configurers.put(OAuth2TokenIntrospectionConfigurer.class, new OAuth2TokenIntrospectionConfigurer(this::postProcess));
return configurers; return configurers;
} }
@ -433,8 +419,6 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
} }
private void initEndpointMatchers(ProviderSettings providerSettings) { private void initEndpointMatchers(ProviderSettings providerSettings) {
this.tokenIntrospectionEndpointMatcher = new AntPathRequestMatcher(
providerSettings.getTokenIntrospectionEndpoint(), HttpMethod.POST.name());
this.jwkSetEndpointMatcher = new AntPathRequestMatcher( this.jwkSetEndpointMatcher = new AntPathRequestMatcher(
providerSettings.getJwkSetEndpoint(), HttpMethod.GET.name()); providerSettings.getJwkSetEndpoint(), HttpMethod.GET.name());
this.authorizationServerMetadataEndpointMatcher = new AntPathRequestMatcher( this.authorizationServerMetadataEndpointMatcher = new AntPathRequestMatcher(

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

41
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 Gerardo Roza
* @author Joe Grandja * @author Joe Grandja
* @author Gaurav Tiwari
* @since 0.1.1 * @since 0.1.1
* @see OAuth2TokenIntrospectionClaimAccessor * @see OAuth2TokenIntrospectionClaimAccessor
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7662#section-2.2">Section 2.2 Introspection Response</a> * @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; 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 * Provides access to every {@link #claim(String, Object)} declared so far with
* the possibility to add, replace, or remove. * 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); ((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) { private static void validateURL(Object url, String errorMessage) {
if (URL.class.isAssignableFrom(url.getClass())) { if (URL.class.isAssignableFrom(url.getClass())) {
return; return;
@ -346,14 +332,5 @@ public final class OAuth2TokenIntrospection implements OAuth2TokenIntrospectionC
throw new IllegalArgumentException(errorMessage, ex); 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);
}
} }
} }

78
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; package org.springframework.security.oauth2.server.authorization.authentication;
import java.net.URL; import java.net.URL;
import java.time.Instant; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.core.AbstractOAuth2Token; import org.springframework.security.oauth2.core.AbstractOAuth2Token;
import org.springframework.security.oauth2.core.OAuth2AccessToken; 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.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.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; 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.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import static org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthenticationProviderUtils.getAuthenticatedClientElseThrowInvalidClient; 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 Gerardo Roza
* @author Joe Grandja * @author Joe Grandja
* @author Gaurav Tiwari
* @since 0.1.1 * @since 0.1.1
* @see OAuth2TokenIntrospectionAuthenticationToken * @see OAuth2TokenIntrospectionAuthenticationToken
* @see RegisteredClientRepository * @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> * @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 { 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 RegisteredClientRepository registeredClientRepository;
private final OAuth2AuthorizationService authorizationService; private final OAuth2AuthorizationService authorizationService;
@ -103,8 +107,15 @@ public final class OAuth2TokenIntrospectionAuthenticationProvider implements Aut
private static OAuth2TokenIntrospection withActiveTokenClaims( private static OAuth2TokenIntrospection withActiveTokenClaims(
OAuth2Authorization.Token<AbstractOAuth2Token> authorizedToken, RegisteredClient authorizedClient) { OAuth2Authorization.Token<AbstractOAuth2Token> authorizedToken, RegisteredClient authorizedClient) {
OAuth2TokenIntrospection.Builder tokenClaims = OAuth2TokenIntrospection.builder(true) OAuth2TokenIntrospection.Builder tokenClaims;
.clientId(authorizedClient.getClientId()); 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" // TODO Set "username"
@ -118,34 +129,43 @@ public final class OAuth2TokenIntrospectionAuthenticationProvider implements Aut
if (OAuth2AccessToken.class.isAssignableFrom(token.getClass())) { if (OAuth2AccessToken.class.isAssignableFrom(token.getClass())) {
OAuth2AccessToken accessToken = (OAuth2AccessToken) token; OAuth2AccessToken accessToken = (OAuth2AccessToken) token;
tokenClaims.scopes(scopes -> scopes.addAll(accessToken.getScopes()));
tokenClaims.tokenType(accessToken.getTokenType().getValue()); 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())) { Object value = claims.get(OAuth2TokenIntrospectionClaimNames.ISS);
OAuth2TokenClaimAccessor accessTokenClaims = authorizedToken::getClaims; if (value != null && !(value instanceof URL)) {
URL convertedValue = ClaimConversionService.getSharedInstance()
Instant notBefore = accessTokenClaims.getNotBefore(); .convert(value, URL.class);
if (notBefore != null) { if (convertedValue != null) {
tokenClaims.notBefore(notBefore); convertedClaims.put(OAuth2TokenIntrospectionClaimNames.ISS, convertedValue);
}
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);
}
} }
} }
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;
} }
} }

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

103
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.HashSet;
import java.util.List; 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.After;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.BeforeClass; 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.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.mock.http.client.MockClientHttpResponse; import org.springframework.mock.http.client.MockClientHttpResponse;
import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.TestingAuthenticationToken; 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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.config.test.SpringTestRule; 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.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AbstractOAuth2Token; import org.springframework.security.oauth2.core.AbstractOAuth2Token;
import org.springframework.security.oauth2.core.AuthorizationGrantType; 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.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthorizationCode; import org.springframework.security.oauth2.core.OAuth2AuthorizationCode;
import org.springframework.security.oauth2.core.OAuth2RefreshToken; 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.OAuth2TokenFormat;
import org.springframework.security.oauth2.core.OAuth2TokenIntrospection; 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.OAuth2TokenType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; 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.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.security.oauth2.core.http.converter.OAuth2TokenIntrospectionHttpMessageConverter; 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.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; 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.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; 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;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; 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.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.config.TokenSettings; 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.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.MockMvc;
import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.MvcResult;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import static org.assertj.core.api.Assertions.assertThat; 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.mock;
import static org.mockito.Mockito.verify; 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.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 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 { public class OAuth2TokenIntrospectionTests {
private static EmbeddedDatabase db; private static EmbeddedDatabase db;
private static JWKSource<SecurityContext> jwkSource;
private static ProviderSettings providerSettings; private static ProviderSettings providerSettings;
private static OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer; 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 = private static final HttpMessageConverter<OAuth2TokenIntrospection> tokenIntrospectionHttpResponseConverter =
new OAuth2TokenIntrospectionHttpMessageConverter(); new OAuth2TokenIntrospectionHttpMessageConverter();
private static final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter = private static final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter =
@ -129,9 +143,11 @@ public class OAuth2TokenIntrospectionTests {
@BeforeClass @BeforeClass
public static void init() { 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(); 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); accessTokenCustomizer = mock(OAuth2TokenCustomizer.class);
db = new EmbeddedDatabaseBuilder() db = new EmbeddedDatabaseBuilder()
.generateUniqueName(true) .generateUniqueName(true)
@ -175,6 +191,7 @@ public class OAuth2TokenIntrospectionTests {
.issuedAt(issuedAt) .issuedAt(issuedAt)
.notBefore(issuedAt) .notBefore(issuedAt)
.expiresAt(expiresAt) .expiresAt(expiresAt)
.claim(OAuth2TokenIntrospectionClaimNames.SCOPE, accessToken.getScopes())
.id("id") .id("id")
.build(); .build();
// @formatter:on // @formatter:on
@ -314,6 +331,43 @@ public class OAuth2TokenIntrospectionTests {
assertThat(tokenIntrospectionResponse.getId()).isEqualTo(accessTokenClaims.getId()); 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, private static MultiValueMap<String, String> getTokenIntrospectionRequestParameters(AbstractOAuth2Token token,
OAuth2TokenType tokenType) { OAuth2TokenType tokenType) {
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(); 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
}
} }

64
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.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2RefreshToken; 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.OAuth2TokenIntrospection;
import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; 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.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; 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.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.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThatThrownBy;
@ -54,7 +55,6 @@ import static org.mockito.Mockito.when;
* *
* @author Gerardo Roza * @author Gerardo Roza
* @author Joe Grandja * @author Joe Grandja
* @author Gaurav Tiwari
*/ */
public class OAuth2TokenIntrospectionAuthenticationProviderTests { public class OAuth2TokenIntrospectionAuthenticationProviderTests {
private RegisteredClientRepository registeredClientRepository; private RegisteredClientRepository registeredClientRepository;
@ -227,6 +227,8 @@ public class OAuth2TokenIntrospectionAuthenticationProviderTests {
.notBefore(issuedAt) .notBefore(issuedAt)
.expiresAt(expiresAt) .expiresAt(expiresAt)
.id("id") .id("id")
.claim(OAuth2TokenIntrospectionClaimNames.SCOPE, accessToken.getScopes())
.claim("custom-claim", "custom-value")
.build(); .build();
// @formatter:on // @formatter:on
@ -253,68 +255,14 @@ public class OAuth2TokenIntrospectionAuthenticationProviderTests {
assertThat(tokenClaims.getClientId()).isEqualTo(authorizedClient.getClientId()); assertThat(tokenClaims.getClientId()).isEqualTo(authorizedClient.getClientId());
assertThat(tokenClaims.getIssuedAt()).isEqualTo(accessToken.getIssuedAt()); assertThat(tokenClaims.getIssuedAt()).isEqualTo(accessToken.getIssuedAt());
assertThat(tokenClaims.getExpiresAt()).isEqualTo(accessToken.getExpiresAt()); assertThat(tokenClaims.getExpiresAt()).isEqualTo(accessToken.getExpiresAt());
assertThat(tokenClaims.getScopes()).containsExactlyInAnyOrderElementsOf(accessToken.getScopes());
assertThat(tokenClaims.getTokenType()).isEqualTo(accessToken.getTokenType().getValue()); assertThat(tokenClaims.getTokenType()).isEqualTo(accessToken.getTokenType().getValue());
assertThat(tokenClaims.getNotBefore()).isEqualTo(claimsSet.getNotBefore()); assertThat(tokenClaims.getNotBefore()).isEqualTo(claimsSet.getNotBefore());
assertThat(tokenClaims.getSubject()).isEqualTo(claimsSet.getSubject()); assertThat(tokenClaims.getSubject()).isEqualTo(claimsSet.getSubject());
assertThat(tokenClaims.getAudience()).containsExactlyInAnyOrderElementsOf(claimsSet.getAudience()); assertThat(tokenClaims.getAudience()).containsExactlyInAnyOrderElementsOf(claimsSet.getAudience());
assertThat(tokenClaims.getIssuer()).isEqualTo(claimsSet.getIssuer()); assertThat(tokenClaims.getIssuer()).isEqualTo(claimsSet.getIssuer());
assertThat(tokenClaims.getId()).isEqualTo(claimsSet.getId()); 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.getScopes()).containsExactlyInAnyOrderElementsOf(accessToken.getScopes());
assertThat(tokenClaims.getTokenType()).isEqualTo(accessToken.getTokenType().getValue()); assertThat(tokenClaims.<String>getClaim("custom-claim")).isEqualTo("custom-value");
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");
} }
@Test @Test

121
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AccessToken; 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.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes; import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2TokenIntrospection; 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.authentication.OAuth2TokenIntrospectionAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; 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.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThatThrownBy;
@ -101,6 +105,27 @@ public class OAuth2TokenIntrospectionEndpointFilterTests {
.hasMessage("tokenIntrospectionEndpointUri cannot be empty"); .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 @Test
public void doFilterWhenNotTokenIntrospectionRequestThenNotProcessed() throws Exception { public void doFilterWhenNotTokenIntrospectionRequestThenNotProcessed() throws Exception {
String requestUri = "/path"; String requestUri = "/path";
@ -231,6 +256,100 @@ public class OAuth2TokenIntrospectionEndpointFilterTests {
assertThat(tokenIntrospectionResponse.getId()).isEqualTo(tokenClaims.getId()); 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, private void doFilterWhenTokenIntrospectionRequestInvalidParameterThenError(String parameterName, String errorCode,
MockHttpServletRequest request) throws Exception { MockHttpServletRequest request) throws Exception {

Loading…
Cancel
Save