From 0bc60dca69c85b61203d9abb03546cafe260e528 Mon Sep 17 00:00:00 2001 From: MD Sayem Ahmed Date: Tue, 14 May 2019 05:55:59 +0200 Subject: [PATCH] Add custom parameters to token introspection requests Added support for providing custom parameters to an OAuth 2.0 token introspection request. This is done by explicitly instantiating a NimbusOAuth2TokenIntrospectionClient instance and then setting a custom Converter implementation. Fixes gh-6798 --- .../NimbusOAuth2TokenIntrospectionClient.java | 33 +++++++++++++----- ...usOAuth2TokenIntrospectionClientTests.java | 34 +++++++++++++++++++ 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOAuth2TokenIntrospectionClient.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOAuth2TokenIntrospectionClient.java index 7c4c437a7f..bbf81772a8 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOAuth2TokenIntrospectionClient.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOAuth2TokenIntrospectionClient.java @@ -30,6 +30,7 @@ import com.nimbusds.oauth2.sdk.TokenIntrospectionSuccessResponse; import com.nimbusds.oauth2.sdk.http.HTTPResponse; import com.nimbusds.oauth2.sdk.id.Audience; +import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; @@ -54,10 +55,11 @@ import static org.springframework.security.oauth2.server.resource.introspection. * A Nimbus implementation of {@link OAuth2TokenIntrospectionClient}. * * @author Josh Cummings + * @author MD Sayem Ahmed * @since 5.2 */ public class NimbusOAuth2TokenIntrospectionClient implements OAuth2TokenIntrospectionClient { - private URI introspectionUri; + private Converter> requestEntityConverter; private RestOperations restOperations; /** @@ -72,7 +74,7 @@ public class NimbusOAuth2TokenIntrospectionClient implements OAuth2TokenIntrospe Assert.notNull(clientId, "clientId cannot be null"); Assert.notNull(clientSecret, "clientSecret cannot be null"); - this.introspectionUri = URI.create(introspectionUri); + this.requestEntityConverter = this.defaultRequestEntityConverter(introspectionUri); RestTemplate restTemplate = new RestTemplate(); restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor(clientId, clientSecret)); this.restOperations = restTemplate; @@ -91,7 +93,7 @@ public class NimbusOAuth2TokenIntrospectionClient implements OAuth2TokenIntrospe Assert.notNull(introspectionUri, "introspectionUri cannot be null"); Assert.notNull(restOperations, "restOperations cannot be null"); - this.introspectionUri = URI.create(introspectionUri); + this.requestEntityConverter = this.defaultRequestEntityConverter(introspectionUri); this.restOperations = restOperations; } @@ -101,7 +103,7 @@ public class NimbusOAuth2TokenIntrospectionClient implements OAuth2TokenIntrospe @Override public Map introspect(String token) { TokenIntrospectionSuccessResponse response = Optional.of(token) - .map(this::buildRequest) + .map(this.requestEntityConverter::convert) .map(this::makeRequest) .map(this::adaptToNimbusResponse) .map(this::parseNimbusResponse) @@ -112,10 +114,25 @@ public class NimbusOAuth2TokenIntrospectionClient implements OAuth2TokenIntrospe return convertClaimsSet(response); } - private RequestEntity> buildRequest(String token) { - HttpHeaders headers = requestHeaders(); - MultiValueMap body = requestBody(token); - return new RequestEntity<>(body, headers, HttpMethod.POST, this.introspectionUri); + /** + * Sets the {@link Converter} used for converting the OAuth 2.0 access token to a {@link RequestEntity} + * representation of the OAuth 2.0 token introspection request. + * + * @param requestEntityConverter the {@link Converter} used for converting to a {@link RequestEntity} representation + * of the token introspection request + */ + public void setRequestEntityConverter(Converter> requestEntityConverter) { + Assert.notNull(requestEntityConverter, "requestEntityConverter cannot be null"); + + this.requestEntityConverter = requestEntityConverter; + } + + private Converter> defaultRequestEntityConverter(String introspectionUri) { + return token -> { + HttpHeaders headers = requestHeaders(); + MultiValueMap body = requestBody(token); + return new RequestEntity<>(body, headers, HttpMethod.POST, URI.create(introspectionUri)); + }; } private HttpHeaders requestHeaders() { diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOAuth2TokenIntrospectionClientTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOAuth2TokenIntrospectionClientTests.java index 48b130c97a..98daf6625a 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOAuth2TokenIntrospectionClientTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOAuth2TokenIntrospectionClientTests.java @@ -32,6 +32,7 @@ import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; import org.junit.Test; +import org.springframework.core.convert.converter.Converter; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -45,9 +46,11 @@ import org.springframework.web.client.RestOperations; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; 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.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.AUDIENCE; import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.EXPIRES_AT; @@ -254,6 +257,37 @@ public class NimbusOAuth2TokenIntrospectionClientTests { .isInstanceOf(IllegalArgumentException.class); } + @Test + public void setRequestEntityConverterWhenConverterIsNullThenExceptionIsThrown() { + RestOperations restOperations = mock(RestOperations.class); + + NimbusOAuth2TokenIntrospectionClient introspectionClient = new NimbusOAuth2TokenIntrospectionClient( + INTROSPECTION_URL, restOperations + ); + + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> introspectionClient.setRequestEntityConverter(null)); + } + + @SuppressWarnings("unchecked") + @Test + public void setRequestEntityConverterWhenNonNullConverterGivenThenConverterUsed() { + RestOperations restOperations = mock(RestOperations.class); + Converter> requestEntityConverter = mock(Converter.class); + RequestEntity requestEntity = mock(RequestEntity.class); + String tokenToIntrospect = "some token"; + when(requestEntityConverter.convert(tokenToIntrospect)).thenReturn(requestEntity); + when(restOperations.exchange(requestEntity, String.class)).thenReturn(ACTIVE); + NimbusOAuth2TokenIntrospectionClient introspectionClient = new NimbusOAuth2TokenIntrospectionClient( + INTROSPECTION_URL, restOperations + ); + introspectionClient.setRequestEntityConverter(requestEntityConverter); + + introspectionClient.introspect(tokenToIntrospect); + + verify(requestEntityConverter).convert(tokenToIntrospect); + } + private static ResponseEntity response(String content) { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON);