Created
September 13, 2019 10:33
-
-
Save ansidev/8f4291d9a177ca2dd5d6fce8660c1f43 to your computer and use it in GitHub Desktop.
OAuth2 Access Token Nested Json Response Http Message Converter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2002-2018 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. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package xyz.ansidev.oauth2; | |
import org.springframework.core.ParameterizedTypeReference; | |
import org.springframework.core.convert.converter.Converter; | |
import org.springframework.http.HttpInputMessage; | |
import org.springframework.http.HttpOutputMessage; | |
import org.springframework.http.MediaType; | |
import org.springframework.http.converter.*; | |
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; | |
import org.springframework.security.oauth2.core.OAuth2AccessToken; | |
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; | |
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; | |
import org.springframework.util.Assert; | |
import org.springframework.util.CollectionUtils; | |
import org.springframework.util.StringUtils; | |
import java.io.IOException; | |
import java.nio.charset.Charset; | |
import java.nio.charset.StandardCharsets; | |
import java.time.Instant; | |
import java.time.temporal.ChronoUnit; | |
import java.util.*; | |
import java.util.stream.Collectors; | |
import java.util.stream.Stream; | |
/** | |
* A {@link HttpMessageConverter} for an {@link OAuth2AccessTokenResponse OAuth 2.0 Access Token Response}. | |
* | |
* @author Joe Grandja | |
* @see AbstractHttpMessageConverter | |
* @see OAuth2AccessTokenResponse | |
* @since 5.1 | |
*/ | |
public class OAuth2AccessTokenNestedJsonResponseHttpMessageConverter | |
extends AbstractHttpMessageConverter<OAuth2AccessTokenResponse> { | |
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; | |
private static final ParameterizedTypeReference<Map<String, Object>> PARAMETERIZED_RESPONSE_TYPE = | |
new ParameterizedTypeReference<>() {}; | |
protected Converter<Map<String, Object>, OAuth2AccessTokenResponse> tokenResponseConverter = | |
new OAuth2AccessTokenResponseConverter(); | |
protected Converter<OAuth2AccessTokenResponse, Map<String, String>> tokenResponseParametersConverter = | |
new OAuth2AccessTokenResponseParametersConverter(); | |
private GenericHttpMessageConverter<Object> jsonMessageConverter = new MappingJackson2HttpMessageConverter(); | |
public OAuth2AccessTokenNestedJsonResponseHttpMessageConverter() { | |
super(DEFAULT_CHARSET, MediaType.APPLICATION_JSON, new MediaType("application", "*+json")); | |
} | |
@Override | |
protected boolean supports(Class<?> clazz) { | |
return OAuth2AccessTokenResponse.class.isAssignableFrom(clazz); | |
} | |
@Override | |
protected OAuth2AccessTokenResponse readInternal(Class<? extends OAuth2AccessTokenResponse> clazz, | |
HttpInputMessage inputMessage) | |
throws IOException, HttpMessageNotReadableException { | |
try { | |
@SuppressWarnings("unchecked") Map<String, Object> tokenResponseParameters = | |
(Map<String, Object>) this.jsonMessageConverter.read(PARAMETERIZED_RESPONSE_TYPE.getType(), null, | |
inputMessage); | |
return this.tokenResponseConverter.convert(tokenResponseParameters); | |
} catch (Exception ex) { | |
throw new HttpMessageNotReadableException( | |
"An error occurred reading the OAuth 2.0 Access Token Response: " + ex.getMessage(), ex, inputMessage); | |
} | |
} | |
@Override | |
protected void writeInternal(OAuth2AccessTokenResponse tokenResponse, HttpOutputMessage outputMessage) | |
throws IOException, HttpMessageNotWritableException { | |
try { | |
Map<String, String> tokenResponseParameters = this.tokenResponseParametersConverter.convert(tokenResponse); | |
this.jsonMessageConverter.write(tokenResponseParameters, PARAMETERIZED_RESPONSE_TYPE.getType(), | |
MediaType.APPLICATION_JSON, outputMessage); | |
} catch (Exception ex) { | |
throw new HttpMessageNotWritableException( | |
"An error occurred writing the OAuth 2.0 Access Token Response: " + ex.getMessage(), ex); | |
} | |
} | |
/** | |
* Sets the {@link Converter} used for converting the OAuth 2.0 Access Token Response parameters | |
* to an {@link OAuth2AccessTokenResponse}. | |
* | |
* @param tokenResponseConverter the {@link Converter} used for converting to an {@link OAuth2AccessTokenResponse} | |
*/ | |
public final void setTokenResponseConverter( | |
Converter<Map<String, Object>, OAuth2AccessTokenResponse> tokenResponseConverter) { | |
Assert.notNull(tokenResponseConverter, "tokenResponseConverter cannot be null"); | |
this.tokenResponseConverter = tokenResponseConverter; | |
} | |
/** | |
* Sets the {@link Converter} used for converting the {@link OAuth2AccessTokenResponse} | |
* to a {@code Map} representation of the OAuth 2.0 Access Token Response parameters. | |
* | |
* @param tokenResponseParametersConverter the {@link Converter} used for converting to a {@code Map} representation of the Access Token Response parameters | |
*/ | |
public final void setTokenResponseParametersConverter( | |
Converter<OAuth2AccessTokenResponse, Map<String, String>> tokenResponseParametersConverter) { | |
Assert.notNull(tokenResponseParametersConverter, "tokenResponseParametersConverter cannot be null"); | |
this.tokenResponseParametersConverter = tokenResponseParametersConverter; | |
} | |
/** | |
* A {@link Converter} that converts the provided | |
* OAuth 2.0 Access Token Response parameters to an {@link OAuth2AccessTokenResponse}. | |
*/ | |
private static class OAuth2AccessTokenResponseConverter | |
implements Converter<Map<String, Object>, OAuth2AccessTokenResponse> { | |
private static final Set<String> TOKEN_RESPONSE_PARAMETER_NAMES = Stream.of(OAuth2ParameterNames.ACCESS_TOKEN, | |
OAuth2ParameterNames.TOKEN_TYPE, | |
OAuth2ParameterNames.EXPIRES_IN, | |
OAuth2ParameterNames.REFRESH_TOKEN, | |
OAuth2ParameterNames.SCOPE) | |
.collect(Collectors.toSet()); | |
@Override | |
public OAuth2AccessTokenResponse convert(Map<String, Object> tokenResponseParameters) { | |
String accessToken = tokenResponseParameters.get(OAuth2ParameterNames.ACCESS_TOKEN) | |
.toString(); | |
OAuth2AccessToken.TokenType accessTokenType = null; | |
if (OAuth2AccessToken.TokenType.BEARER.getValue() | |
.equalsIgnoreCase( | |
tokenResponseParameters.get(OAuth2ParameterNames.TOKEN_TYPE) | |
.toString())) { | |
accessTokenType = OAuth2AccessToken.TokenType.BEARER; | |
} | |
long expiresIn = 0; | |
if (tokenResponseParameters.containsKey(OAuth2ParameterNames.EXPIRES_IN)) { | |
try { | |
expiresIn = Long.valueOf(tokenResponseParameters.get(OAuth2ParameterNames.EXPIRES_IN) | |
.toString()); | |
} catch (NumberFormatException ex) { } | |
} | |
Set<String> scopes = Collections.emptySet(); | |
if (tokenResponseParameters.containsKey(OAuth2ParameterNames.SCOPE)) { | |
String scope = tokenResponseParameters.get(OAuth2ParameterNames.SCOPE) | |
.toString(); | |
scopes = Arrays.stream(StringUtils.delimitedListToStringArray(scope, " ")) | |
.collect(Collectors.toSet()); | |
} | |
String refreshToken = String.valueOf(tokenResponseParameters.get(OAuth2ParameterNames.REFRESH_TOKEN)); | |
Map<String, Object> additionalParameters = new LinkedHashMap<>(); | |
tokenResponseParameters.entrySet() | |
.stream() | |
.filter(e -> !TOKEN_RESPONSE_PARAMETER_NAMES.contains(e.getKey())) | |
.forEach(e -> additionalParameters.put(e.getKey(), e.getValue())); | |
return OAuth2AccessTokenResponse.withToken(accessToken) | |
.tokenType(accessTokenType) | |
.expiresIn(expiresIn) | |
.scopes(scopes) | |
.refreshToken(refreshToken) | |
.additionalParameters(additionalParameters) | |
.build(); | |
} | |
} | |
/** | |
* A {@link Converter} that converts the provided {@link OAuth2AccessTokenResponse} | |
* to a {@code Map} representation of the OAuth 2.0 Access Token Response parameters. | |
*/ | |
private static class OAuth2AccessTokenResponseParametersConverter | |
implements Converter<OAuth2AccessTokenResponse, Map<String, String>> { | |
@Override | |
public Map<String, String> convert(OAuth2AccessTokenResponse tokenResponse) { | |
Map<String, String> parameters = new HashMap<>(); | |
long expiresIn = -1; | |
if (tokenResponse.getAccessToken() | |
.getExpiresAt() != null) { | |
expiresIn = ChronoUnit.SECONDS.between(Instant.now(), tokenResponse.getAccessToken() | |
.getExpiresAt()); | |
} | |
parameters.put(OAuth2ParameterNames.ACCESS_TOKEN, tokenResponse.getAccessToken() | |
.getTokenValue()); | |
parameters.put(OAuth2ParameterNames.TOKEN_TYPE, tokenResponse.getAccessToken() | |
.getTokenType() | |
.getValue()); | |
parameters.put(OAuth2ParameterNames.EXPIRES_IN, String.valueOf(expiresIn)); | |
if (!CollectionUtils.isEmpty(tokenResponse.getAccessToken() | |
.getScopes())) { | |
parameters.put(OAuth2ParameterNames.SCOPE, StringUtils.collectionToDelimitedString( | |
tokenResponse.getAccessToken() | |
.getScopes(), " ")); | |
} | |
if (tokenResponse.getRefreshToken() != null) { | |
parameters.put(OAuth2ParameterNames.REFRESH_TOKEN, tokenResponse.getRefreshToken() | |
.getTokenValue()); | |
} | |
if (!CollectionUtils.isEmpty(tokenResponse.getAdditionalParameters())) { | |
tokenResponse.getAdditionalParameters() | |
.entrySet() | |
.stream() | |
.forEach(e -> parameters.put(e.getKey(), e.getValue() | |
.toString())); | |
} | |
return parameters; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment