diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseHttpMessageConverter.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseHttpMessageConverter.java index 36cf4a168f..51bc78ce62 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseHttpMessageConverter.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseHttpMessageConverter.java @@ -18,6 +18,7 @@ package org.springframework.security.oauth2.core.http.converter; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Map; +import java.util.stream.Collectors; import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.convert.converter.Converter; @@ -45,8 +46,8 @@ import org.springframework.util.Assert; public class OAuth2AccessTokenResponseHttpMessageConverter extends AbstractHttpMessageConverter { private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; - private static final ParameterizedTypeReference> PARAMETERIZED_RESPONSE_TYPE = - new ParameterizedTypeReference>() {}; + private static final ParameterizedTypeReference> PARAMETERIZED_RESPONSE_TYPE = + new ParameterizedTypeReference>() {}; private GenericHttpMessageConverter jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter(); @@ -70,10 +71,16 @@ public class OAuth2AccessTokenResponseHttpMessageConverter extends AbstractHttpM throws HttpMessageNotReadableException { try { + // gh-6463 + // Parse parameter values as Object in order to handle potential JSON Object and then convert values to String @SuppressWarnings("unchecked") - Map tokenResponseParameters = (Map) this.jsonMessageConverter.read( + Map tokenResponseParameters = (Map) this.jsonMessageConverter.read( PARAMETERIZED_RESPONSE_TYPE.getType(), null, inputMessage); - return this.tokenResponseConverter.convert(tokenResponseParameters); + return this.tokenResponseConverter.convert( + tokenResponseParameters.entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + entry -> entry.getValue().toString()))); } catch (Exception ex) { throw new HttpMessageNotReadableException("An error occurred reading the OAuth 2.0 Access Token Response: " + ex.getMessage(), ex, inputMessage); diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseHttpMessageConverterTests.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseHttpMessageConverterTests.java index bf74c94730..dace9010c1 100644 --- a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseHttpMessageConverterTests.java +++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/http/converter/OAuth2AccessTokenResponseHttpMessageConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2020 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. @@ -96,6 +96,39 @@ public class OAuth2AccessTokenResponseHttpMessageConverterTests { } + // gh-6463 + @Test + public void readInternalWhenSuccessfulTokenResponseWithObjectThenReadOAuth2AccessTokenResponse() { + String tokenResponse = "{\n" + + " \"access_token\": \"access-token-1234\",\n" + + " \"token_type\": \"bearer\",\n" + + " \"expires_in\": 3600,\n" + + " \"scope\": \"read write\",\n" + + " \"refresh_token\": \"refresh-token-1234\",\n" + + " \"custom_object_1\": {\"name1\": \"value1\"},\n" + + " \"custom_object_2\": [\"value1\", \"value2\"],\n" + + " \"custom_parameter_1\": \"custom-value-1\",\n" + + " \"custom_parameter_2\": \"custom-value-2\"\n" + + "}\n"; + + MockClientHttpResponse response = new MockClientHttpResponse( + tokenResponse.getBytes(), HttpStatus.OK); + + OAuth2AccessTokenResponse accessTokenResponse = this.messageConverter.readInternal( + OAuth2AccessTokenResponse.class, response); + + assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo("access-token-1234"); + assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(OAuth2AccessToken.TokenType.BEARER); + assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBeforeOrEqualTo(Instant.now().plusSeconds(3600)); + assertThat(accessTokenResponse.getAccessToken().getScopes()).containsExactly("read", "write"); + assertThat(accessTokenResponse.getRefreshToken().getTokenValue()).isEqualTo("refresh-token-1234"); + assertThat(accessTokenResponse.getAdditionalParameters()).containsExactly( + entry("custom_object_1", "{name1=value1}"), + entry("custom_object_2", "[value1, value2]"), + entry("custom_parameter_1", "custom-value-1"), + entry("custom_parameter_2", "custom-value-2")); + } + @Test public void readInternalWhenConversionFailsThenThrowHttpMessageNotReadableException() { Converter tokenResponseConverter = mock(Converter.class);