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; @@ -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; @@ -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; @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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(

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 @@ @@ -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; @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -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;
}
}

41
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/OAuth2TokenIntrospection.java

@ -34,7 +34,6 @@ import org.springframework.util.Assert; @@ -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 @@ -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 @@ -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 @@ -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);
}
}
}

78
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenIntrospectionAuthenticationProvider.java

@ -16,23 +16,25 @@ @@ -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 @@ -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 @@ -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 @@ -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 @@ -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;
}
}

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 @@ -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 @@ -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 @@ -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 @@ -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 @@ -217,5 +215,7 @@ public final class OAuth2TokenIntrospectionEndpointFilter extends OncePerRequest
return new OAuth2TokenIntrospectionAuthenticationToken(
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; @@ -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; @@ -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 @@ -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. @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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; @@ -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; @@ -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 { @@ -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 { @@ -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

121
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenIntrospectionEndpointFilterTests.java

@ -1,5 +1,5 @@ @@ -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; @@ -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 @@ -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 { @@ -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 { @@ -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 {

Loading…
Cancel
Save