Skip to content

Commit a4d29eb

Browse files
author
Steve Riesenberg
committed
Add support for a more complex access token response
Changed the converter used to convert a map into an OAuth2AccessTokenResponse to support any object as the value, including json numbers and nested objects. Also deprecated old setters/constructors and added new setters/factory methods. Closes gh-9685
1 parent 488683f commit a4d29eb

10 files changed

+549
-205
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,81 +16,28 @@
1616

1717
package org.springframework.security.oauth2.core.endpoint;
1818

19-
import java.util.Arrays;
20-
import java.util.Collections;
21-
import java.util.HashSet;
22-
import java.util.LinkedHashMap;
2319
import java.util.Map;
24-
import java.util.Set;
2520

2621
import org.springframework.core.convert.converter.Converter;
27-
import org.springframework.security.oauth2.core.OAuth2AccessToken;
28-
import org.springframework.util.StringUtils;
2922

3023
/**
3124
* A {@link Converter} that converts the provided OAuth 2.0 Access Token Response
3225
* parameters to an {@link OAuth2AccessTokenResponse}.
3326
*
27+
* @deprecated Use {@link OAuth2AccessTokenResponseConverter} instead
3428
* @author Joe Grandja
3529
* @author Nikita Konev
3630
* @since 5.3
3731
*/
32+
@Deprecated
3833
public final class MapOAuth2AccessTokenResponseConverter
3934
implements Converter<Map<String, String>, OAuth2AccessTokenResponse> {
4035

41-
private static final Set<String> TOKEN_RESPONSE_PARAMETER_NAMES = new HashSet<>(
42-
Arrays.asList(OAuth2ParameterNames.ACCESS_TOKEN, OAuth2ParameterNames.EXPIRES_IN,
43-
OAuth2ParameterNames.REFRESH_TOKEN, OAuth2ParameterNames.SCOPE, OAuth2ParameterNames.TOKEN_TYPE));
36+
private final Converter<Map<String, ?>, OAuth2AccessTokenResponse> delegate = new OAuth2AccessTokenResponseConverter();
4437

4538
@Override
4639
public OAuth2AccessTokenResponse convert(Map<String, String> tokenResponseParameters) {
47-
String accessToken = tokenResponseParameters.get(OAuth2ParameterNames.ACCESS_TOKEN);
48-
OAuth2AccessToken.TokenType accessTokenType = getAccessTokenType(tokenResponseParameters);
49-
long expiresIn = getExpiresIn(tokenResponseParameters);
50-
Set<String> scopes = getScopes(tokenResponseParameters);
51-
String refreshToken = tokenResponseParameters.get(OAuth2ParameterNames.REFRESH_TOKEN);
52-
Map<String, Object> additionalParameters = new LinkedHashMap<>();
53-
for (Map.Entry<String, String> entry : tokenResponseParameters.entrySet()) {
54-
if (!TOKEN_RESPONSE_PARAMETER_NAMES.contains(entry.getKey())) {
55-
additionalParameters.put(entry.getKey(), entry.getValue());
56-
}
57-
}
58-
// @formatter:off
59-
return OAuth2AccessTokenResponse.withToken(accessToken)
60-
.tokenType(accessTokenType)
61-
.expiresIn(expiresIn)
62-
.scopes(scopes)
63-
.refreshToken(refreshToken)
64-
.additionalParameters(additionalParameters)
65-
.build();
66-
// @formatter:on
67-
}
68-
69-
private OAuth2AccessToken.TokenType getAccessTokenType(Map<String, String> tokenResponseParameters) {
70-
if (OAuth2AccessToken.TokenType.BEARER.getValue()
71-
.equalsIgnoreCase(tokenResponseParameters.get(OAuth2ParameterNames.TOKEN_TYPE))) {
72-
return OAuth2AccessToken.TokenType.BEARER;
73-
}
74-
return null;
75-
}
76-
77-
private long getExpiresIn(Map<String, String> tokenResponseParameters) {
78-
if (tokenResponseParameters.containsKey(OAuth2ParameterNames.EXPIRES_IN)) {
79-
try {
80-
return Long.parseLong(tokenResponseParameters.get(OAuth2ParameterNames.EXPIRES_IN));
81-
}
82-
catch (NumberFormatException ex) {
83-
}
84-
}
85-
return 0;
86-
}
87-
88-
private Set<String> getScopes(Map<String, String> tokenResponseParameters) {
89-
if (tokenResponseParameters.containsKey(OAuth2ParameterNames.SCOPE)) {
90-
String scope = tokenResponseParameters.get(OAuth2ParameterNames.SCOPE);
91-
return new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
92-
}
93-
return Collections.emptySet();
40+
return this.delegate.convert(tokenResponseParameters);
9441
}
9542

9643
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright 2002-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.oauth2.core.endpoint;
18+
19+
import java.util.Arrays;
20+
import java.util.Collections;
21+
import java.util.HashSet;
22+
import java.util.LinkedHashMap;
23+
import java.util.Map;
24+
import java.util.Set;
25+
26+
import org.springframework.core.convert.converter.Converter;
27+
import org.springframework.security.oauth2.core.OAuth2AccessToken;
28+
import org.springframework.util.StringUtils;
29+
30+
/**
31+
* A {@link Converter} that converts the provided OAuth 2.0 Access Token Response
32+
* parameters to an {@link OAuth2AccessTokenResponse}.
33+
*
34+
* @author Steve Riesenberg
35+
* @since 5.5
36+
*/
37+
public class OAuth2AccessTokenResponseConverter implements Converter<Map<String, ?>, OAuth2AccessTokenResponse> {
38+
39+
private static final Set<String> TOKEN_RESPONSE_PARAMETER_NAMES = new HashSet<>(
40+
Arrays.asList(OAuth2ParameterNames.ACCESS_TOKEN, OAuth2ParameterNames.EXPIRES_IN,
41+
OAuth2ParameterNames.REFRESH_TOKEN, OAuth2ParameterNames.SCOPE, OAuth2ParameterNames.TOKEN_TYPE));
42+
43+
@Override
44+
public OAuth2AccessTokenResponse convert(Map<String, ?> source) {
45+
String accessToken = getParameterValue(source, OAuth2ParameterNames.ACCESS_TOKEN);
46+
OAuth2AccessToken.TokenType accessTokenType = getAccessTokenType(source);
47+
long expiresIn = getExpiresIn(source);
48+
Set<String> scopes = getScopes(source);
49+
String refreshToken = getParameterValue(source, OAuth2ParameterNames.REFRESH_TOKEN);
50+
Map<String, Object> additionalParameters = new LinkedHashMap<>();
51+
for (Map.Entry<String, ?> entry : source.entrySet()) {
52+
if (!TOKEN_RESPONSE_PARAMETER_NAMES.contains(entry.getKey())) {
53+
additionalParameters.put(entry.getKey(), entry.getValue());
54+
}
55+
}
56+
// @formatter:off
57+
return OAuth2AccessTokenResponse.withToken(accessToken)
58+
.tokenType(accessTokenType)
59+
.expiresIn(expiresIn)
60+
.scopes(scopes)
61+
.refreshToken(refreshToken)
62+
.additionalParameters(additionalParameters)
63+
.build();
64+
// @formatter:on
65+
}
66+
67+
private OAuth2AccessToken.TokenType getAccessTokenType(Map<String, ?> tokenResponseParameters) {
68+
if (OAuth2AccessToken.TokenType.BEARER.getValue()
69+
.equalsIgnoreCase(getParameterValue(tokenResponseParameters, OAuth2ParameterNames.TOKEN_TYPE))) {
70+
return OAuth2AccessToken.TokenType.BEARER;
71+
}
72+
return null;
73+
}
74+
75+
private long getExpiresIn(Map<String, ?> tokenResponseParameters) {
76+
return getParameterValue(tokenResponseParameters, OAuth2ParameterNames.EXPIRES_IN, 0L);
77+
}
78+
79+
private Set<String> getScopes(Map<String, ?> tokenResponseParameters) {
80+
if (tokenResponseParameters.containsKey(OAuth2ParameterNames.SCOPE)) {
81+
String scope = getParameterValue(tokenResponseParameters, OAuth2ParameterNames.SCOPE);
82+
return new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
83+
}
84+
return Collections.emptySet();
85+
}
86+
87+
private String getParameterValue(Map<String, ?> tokenResponseParameters, String parameterName) {
88+
Object obj = tokenResponseParameters.get(parameterName);
89+
return (obj != null) ? obj.toString() : null;
90+
}
91+
92+
private long getParameterValue(Map<String, ?> tokenResponseParameters, String parameterName, long defaultValue) {
93+
long parameterValue = defaultValue;
94+
95+
Object obj = tokenResponseParameters.get(parameterName);
96+
if (obj != null) {
97+
// Final classes Long and Integer do not need to be coerced
98+
if (obj.getClass() == Long.class) {
99+
parameterValue = (Long) obj;
100+
}
101+
else if (obj.getClass() == Integer.class) {
102+
parameterValue = (Integer) obj;
103+
}
104+
else {
105+
// Attempt to coerce to a long (typically from a String)
106+
try {
107+
parameterValue = Long.parseLong(obj.toString());
108+
}
109+
catch (NumberFormatException ignored) {
110+
}
111+
}
112+
}
113+
114+
return parameterValue;
115+
}
116+
117+
}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,52 +16,30 @@
1616

1717
package org.springframework.security.oauth2.core.endpoint;
1818

19-
import java.time.Instant;
20-
import java.time.temporal.ChronoUnit;
21-
import java.util.HashMap;
2219
import java.util.Map;
20+
import java.util.stream.Collectors;
2321

2422
import org.springframework.core.convert.converter.Converter;
25-
import org.springframework.util.CollectionUtils;
26-
import org.springframework.util.StringUtils;
2723

2824
/**
2925
* A {@link Converter} that converts the provided {@link OAuth2AccessTokenResponse} to a
3026
* {@code Map} representation of the OAuth 2.0 Access Token Response parameters.
3127
*
28+
* @deprecated Use {@link OAuth2AccessTokenResponseParametersConverter} instead
3229
* @author Joe Grandja
3330
* @author Nikita Konev
3431
* @since 5.3
3532
*/
33+
@Deprecated
3634
public final class OAuth2AccessTokenResponseMapConverter
3735
implements Converter<OAuth2AccessTokenResponse, Map<String, String>> {
3836

37+
private final Converter<OAuth2AccessTokenResponse, Map<String, Object>> objectValuesConverter = new OAuth2AccessTokenResponseParametersConverter();
38+
3939
@Override
4040
public Map<String, String> convert(OAuth2AccessTokenResponse tokenResponse) {
41-
Map<String, String> parameters = new HashMap<>();
42-
parameters.put(OAuth2ParameterNames.ACCESS_TOKEN, tokenResponse.getAccessToken().getTokenValue());
43-
parameters.put(OAuth2ParameterNames.TOKEN_TYPE, tokenResponse.getAccessToken().getTokenType().getValue());
44-
parameters.put(OAuth2ParameterNames.EXPIRES_IN, String.valueOf(getExpiresIn(tokenResponse)));
45-
if (!CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
46-
parameters.put(OAuth2ParameterNames.SCOPE,
47-
StringUtils.collectionToDelimitedString(tokenResponse.getAccessToken().getScopes(), " "));
48-
}
49-
if (tokenResponse.getRefreshToken() != null) {
50-
parameters.put(OAuth2ParameterNames.REFRESH_TOKEN, tokenResponse.getRefreshToken().getTokenValue());
51-
}
52-
if (!CollectionUtils.isEmpty(tokenResponse.getAdditionalParameters())) {
53-
for (Map.Entry<String, Object> entry : tokenResponse.getAdditionalParameters().entrySet()) {
54-
parameters.put(entry.getKey(), entry.getValue().toString());
55-
}
56-
}
57-
return parameters;
58-
}
59-
60-
private long getExpiresIn(OAuth2AccessTokenResponse tokenResponse) {
61-
if (tokenResponse.getAccessToken().getExpiresAt() != null) {
62-
return ChronoUnit.SECONDS.between(Instant.now(), tokenResponse.getAccessToken().getExpiresAt());
63-
}
64-
return -1;
41+
return this.objectValuesConverter.convert(tokenResponse).entrySet().stream()
42+
.collect(Collectors.toMap(Map.Entry::getKey, (entry) -> entry.getValue().toString()));
6543
}
6644

6745
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2002-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.oauth2.core.endpoint;
18+
19+
import java.time.Instant;
20+
import java.time.temporal.ChronoUnit;
21+
import java.util.HashMap;
22+
import java.util.Map;
23+
24+
import org.springframework.core.convert.converter.Converter;
25+
import org.springframework.util.CollectionUtils;
26+
import org.springframework.util.StringUtils;
27+
28+
/**
29+
* A {@link Converter} that converts the provided {@link OAuth2AccessTokenResponse} to a
30+
* {@code Map} representation of the OAuth 2.0 Access Token Response parameters.
31+
*
32+
* @author Steve Riesenberg
33+
* @since 5.5
34+
*/
35+
public class OAuth2AccessTokenResponseParametersConverter
36+
implements Converter<OAuth2AccessTokenResponse, Map<String, Object>> {
37+
38+
@Override
39+
public Map<String, Object> convert(OAuth2AccessTokenResponse tokenResponse) {
40+
Map<String, Object> parameters = new HashMap<>();
41+
parameters.put(OAuth2ParameterNames.ACCESS_TOKEN, tokenResponse.getAccessToken().getTokenValue());
42+
parameters.put(OAuth2ParameterNames.TOKEN_TYPE, tokenResponse.getAccessToken().getTokenType().getValue());
43+
parameters.put(OAuth2ParameterNames.EXPIRES_IN, getExpiresIn(tokenResponse));
44+
if (!CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
45+
parameters.put(OAuth2ParameterNames.SCOPE,
46+
StringUtils.collectionToDelimitedString(tokenResponse.getAccessToken().getScopes(), " "));
47+
}
48+
if (tokenResponse.getRefreshToken() != null) {
49+
parameters.put(OAuth2ParameterNames.REFRESH_TOKEN, tokenResponse.getRefreshToken().getTokenValue());
50+
}
51+
if (!CollectionUtils.isEmpty(tokenResponse.getAdditionalParameters())) {
52+
for (Map.Entry<String, Object> entry : tokenResponse.getAdditionalParameters().entrySet()) {
53+
parameters.put(entry.getKey(), entry.getValue());
54+
}
55+
}
56+
return parameters;
57+
}
58+
59+
private long getExpiresIn(OAuth2AccessTokenResponse tokenResponse) {
60+
if (tokenResponse.getAccessToken().getExpiresAt() != null) {
61+
return ChronoUnit.SECONDS.between(Instant.now(), tokenResponse.getAccessToken().getExpiresAt());
62+
}
63+
return -1;
64+
}
65+
66+
}

0 commit comments

Comments
 (0)