Spring Bigcommerce OAuth2 调试记录

(1)断点切入点

1
2
3
4
5
6
7
8
9
public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
...
OAuth2LoginAuthenticationToken authenticationResult =
(OAuth2LoginAuthenticationToken) this.getAuthenticationManager().authenticate(authenticationRequest);
...
}

(2)进入this.getAuthenticationManager().authenticate(authenticationRequest)方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
...
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
...

while(itor.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider) itor.next();
if (provider.supports(toTest)) {
if (debug) {
logger.debug("Authentication attempt using " + provider.getClass().getName());
}

try {
result = provider.authenticate(authentication);

(3)进入provider.authenticate(authentication)方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider {

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
...

OAuth2AccessTokenResponse accessTokenResponse;
try {
OAuth2AuthorizationExchangeValidator.validate(
authorizationCodeAuthentication.getAuthorizationExchange());

accessTokenResponse = this.accessTokenResponseClient.getTokenResponse(
new OAuth2AuthorizationCodeGrantRequest(
authorizationCodeAuthentication.getClientRegistration(),
authorizationCodeAuthentication.getAuthorizationExchange()));

(4)跳转到自定义的OAuth2AccessTokenResponseClient.getTokenResponse

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class BigcommerceAuthorizationCodeTokenResponseClient implements OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {

private DefaultAuthorizationCodeTokenResponseClient oAuth2AccessTokenResponseClient;
...

@Override
public OAuth2AccessTokenResponse getTokenResponse(OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest) {
....

oAuth2AccessTokenResponseClient.setRequestEntityConverter(new OAuth2AuthorizationCodeGrantRequestEntityConverter() {
@Override
public RequestEntity<?> convert(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
RequestEntity requestEntity = super.convert(authorizationCodeGrantRequest);
MultiValueMap<String, String> body = (MultiValueMap<String, String>) requestEntity.getBody();
body.add("context", shopName);
body.add("scope", StringUtils.collectionToDelimitedString(currentRegistration.getScopes(), " "));
return requestEntity;
}
});

OAuth2AccessTokenResponse resp = oAuth2AccessTokenResponseClient.getTokenResponse(newGrantReq);

(5)进入oAuth2AccessTokenResponseClient.getTokenResponse(newGrantReq)方法

1
2
3
4
5
6
7
8
9
10
11
public final class DefaultAuthorizationCodeTokenResponseClient implements OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {

@Override
public OAuth2AccessTokenResponse getTokenResponse(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
Assert.notNull(authorizationCodeGrantRequest, "authorizationCodeGrantRequest cannot be null");

RequestEntity<?> request = this.requestEntityConverter.convert(authorizationCodeGrantRequest);

ResponseEntity<OAuth2AccessTokenResponse> response;
try {
response = this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);

(6)进入this.requestEntityConverter.convert(authorizationCodeGrantRequest)方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class OAuth2AuthorizationCodeGrantRequestEntityConverter implements Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> {

/**
* Returns the {@link RequestEntity} used for the Access Token Request.
*
* @param authorizationCodeGrantRequest the authorization code grant request
* @return the {@link RequestEntity} used for the Access Token Request
*/
@Override
public RequestEntity<?> convert(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
ClientRegistration clientRegistration = authorizationCodeGrantRequest.getClientRegistration();

HttpHeaders headers = OAuth2AuthorizationGrantRequestEntityUtils.getTokenRequestHeaders(clientRegistration);
MultiValueMap<String, String> formParameters = this.buildFormParameters(authorizationCodeGrantRequest);
URI uri = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getTokenUri())
.build()
.toUri();

return new RequestEntity<>(formParameters, headers, HttpMethod.POST, uri);
}

/**
* Returns a {@link MultiValueMap} of the form parameters used for the Access Token Request body.
*
* @param authorizationCodeGrantRequest the authorization code grant request
* @return a {@link MultiValueMap} of the form parameters used for the Access Token Request body
*/
private MultiValueMap<String, String> buildFormParameters(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
ClientRegistration clientRegistration = authorizationCodeGrantRequest.getClientRegistration();
OAuth2AuthorizationExchange authorizationExchange = authorizationCodeGrantRequest.getAuthorizationExchange();

MultiValueMap<String, String> formParameters = new LinkedMultiValueMap<>();
formParameters.add(OAuth2ParameterNames.GRANT_TYPE, authorizationCodeGrantRequest.getGrantType().getValue());
formParameters.add(OAuth2ParameterNames.CODE, authorizationExchange.getAuthorizationResponse().getCode());
formParameters.add(OAuth2ParameterNames.REDIRECT_URI, authorizationExchange.getAuthorizationRequest().getRedirectUri());
if (ClientAuthenticationMethod.POST.equals(clientRegistration.getClientAuthenticationMethod())) {
formParameters.add(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());
formParameters.add(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret());
}

return formParameters;
}
}

注意:正因为requestEntityConverter.convert并未转换contextscope字段,所以需要在复写OAuth2AccessTokenResponseClientgetTokenResponse方法中重新设置oAuth2AccessTokenResponseClient.setRequestEntityConverter

(7)另外,自定义的BigcommerceOAuth2AccessTokenResponseHttpMessageConverter设置的BigcommerceOAuth2AccessTokenResponseConverter如果实现的泛型是Converter<Map<String, String>, OAuth2AccessTokenResponse>,则无法转换内嵌JSON对象,导致Response反序列化失败,需要转换为Converter<Map<String, Object>, OAuth2AccessTokenResponse>

1
2
3
4
5
6
7
8
9
{
"access_token": "ACCESS_TOKEN",
"scope": "store_v2_orders",
"user": {
"id": 28888,
"email": "bigcommerce@appblog.cn"
},
"context": "stores/STORE_HASH"
}
1
2
3
org.springframework.security.oauth2.core.OAuth2AuthenticationException: [invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: Error while extracting response for type [class org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse] and content type [application/json;charset=utf-8]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: An error occurred reading the OAuth 2.0 Access Token Response: JSON parse error: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
at [Source: (PushbackInputStream); line: 1, column: 71] (through reference chain: java.util.LinkedHashMap["user"]); nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
at [Source: (PushbackInputStream); line: 1, column: 71] (through reference chain: java.util.LinkedHashMap["user"])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class BigcommerceOAuth2AccessTokenResponseHttpMessageConverter extends MyOAuth2AccessTokenResponseHttpMessageConverter {

public CustomBigcommerceOAuth2AccessTokenResponseHttpMessageConverter() {
super();
super.setTokenResponseConverter(new BigcommerceOAuth2AccessTokenResponseConverter());
}

/**
* Identical to the OAuth2AccessTokenResponseConverter provided in OAuth2AccessTokenResponseHttpMessageConverter.
* The major difference is that this converter will not fail if "token_type" is not provided. It defaults to "bearer".
*/
private static class BigcommerceOAuth2AccessTokenResponseConverter 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 = (String) tokenResponseParameters.get(OAuth2ParameterNames.ACCESS_TOKEN);

OAuth2AccessToken.TokenType accessTokenType = null;
if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(
(String) tokenResponseParameters.get(OAuth2ParameterNames.TOKEN_TYPE))) {
accessTokenType = OAuth2AccessToken.TokenType.BEARER;
}

if (accessTokenType == null) {
accessTokenType = OAuth2AccessToken.TokenType.BEARER;
}

long expiresIn = 0;
if (tokenResponseParameters.containsKey(OAuth2ParameterNames.EXPIRES_IN)) {
try {
expiresIn = Long.valueOf((String) tokenResponseParameters.get(OAuth2ParameterNames.EXPIRES_IN));
} catch (NumberFormatException ex) {
}
}

Set<String> scopes = Collections.emptySet();
if (tokenResponseParameters.containsKey(OAuth2ParameterNames.SCOPE)) {
String scope = (String) tokenResponseParameters.get(OAuth2ParameterNames.SCOPE);
scopes = Arrays.stream(StringUtils.delimitedListToStringArray(scope, ",")).collect(Collectors.toSet());
}

String refreshToken = (String) 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();
}
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
public class MyOAuth2AccessTokenResponseHttpMessageConverter extends AbstractHttpMessageConverter<OAuth2AccessTokenResponse> {
private static final Charset DEFAULT_CHARSET;
private static final ParameterizedTypeReference<Map<String, Object>> PARAMETERIZED_RESPONSE_TYPE;
private GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter();
protected Converter<Map<String, Object>, OAuth2AccessTokenResponse> tokenResponseConverter = new OAuth2AccessTokenResponseConverter();
protected Converter<OAuth2AccessTokenResponse, Map<String, Object>> tokenResponseParametersConverter = new OAuth2AccessTokenResponseParametersConverter();

public MyOAuth2AccessTokenResponseHttpMessageConverter() {
super(DEFAULT_CHARSET, new MediaType[]{MediaType.APPLICATION_JSON, new MediaType("application", "*+json")});
}

protected boolean supports(Class<?> clazz) {
return OAuth2AccessTokenResponse.class.isAssignableFrom(clazz);
}

protected OAuth2AccessTokenResponse readInternal(Class<? extends OAuth2AccessTokenResponse> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
try {
Map<String, Object> tokenResponseParameters = (Map)this.jsonMessageConverter.read(PARAMETERIZED_RESPONSE_TYPE.getType(), (Class)null, inputMessage);
return (OAuth2AccessTokenResponse)this.tokenResponseConverter.convert(tokenResponseParameters);
} catch (Exception var4) {
throw new HttpMessageNotReadableException("An error occurred reading the OAuth 2.0 Access Token Response: " + var4.getMessage(), var4, inputMessage);
}
}

protected void writeInternal(OAuth2AccessTokenResponse tokenResponse, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
try {
Map<String, String> tokenResponseParameters = (Map)this.tokenResponseParametersConverter.convert(tokenResponse);
this.jsonMessageConverter.write(tokenResponseParameters, PARAMETERIZED_RESPONSE_TYPE.getType(), MediaType.APPLICATION_JSON, outputMessage);
} catch (Exception var4) {
throw new HttpMessageNotWritableException("An error occurred writing the OAuth 2.0 Access Token Response: " + var4.getMessage(), var4);
}
}

public final void setTokenResponseConverter(Converter<Map<String, Object>, OAuth2AccessTokenResponse> tokenResponseConverter) {
Assert.notNull(tokenResponseConverter, "tokenResponseConverter cannot be null");
this.tokenResponseConverter = tokenResponseConverter;
}

public final void setTokenResponseParametersConverter(Converter<OAuth2AccessTokenResponse, Map<String, Object>> tokenResponseParametersConverter) {
Assert.notNull(tokenResponseParametersConverter, "tokenResponseParametersConverter cannot be null");
this.tokenResponseParametersConverter = tokenResponseParametersConverter;
}

static {
DEFAULT_CHARSET = StandardCharsets.UTF_8;
PARAMETERIZED_RESPONSE_TYPE = new ParameterizedTypeReference<Map<String, Object>>() {
};
}

private static class OAuth2AccessTokenResponseParametersConverter implements Converter<OAuth2AccessTokenResponse, Map<String, Object>> {
private OAuth2AccessTokenResponseParametersConverter() {
}

public Map<String, Object> convert(OAuth2AccessTokenResponse tokenResponse) {
Map<String, Object> parameters = new HashMap();
long expiresIn = -1L;
if (tokenResponse.getAccessToken().getExpiresAt() != null) {
expiresIn = ChronoUnit.SECONDS.between(Instant.now(), tokenResponse.getAccessToken().getExpiresAt());
}

parameters.put("access_token", tokenResponse.getAccessToken().getTokenValue());
parameters.put("token_type", tokenResponse.getAccessToken().getTokenType().getValue());
parameters.put("expires_in", String.valueOf(expiresIn));
if (!CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
parameters.put("scope", StringUtils.collectionToDelimitedString(tokenResponse.getAccessToken().getScopes(), " "));
}

if (tokenResponse.getRefreshToken() != null) {
parameters.put("refresh_token", tokenResponse.getRefreshToken().getTokenValue());
}

if (!CollectionUtils.isEmpty(tokenResponse.getAdditionalParameters())) {
tokenResponse.getAdditionalParameters().entrySet().stream().forEach((e) -> {
String var10000 = (String)parameters.put(e.getKey(), e.getValue().toString());
});
}

return parameters;
}
}

private static class OAuth2AccessTokenResponseConverter implements Converter<Map<String, Object>, OAuth2AccessTokenResponse> {
private static final Set<String> TOKEN_RESPONSE_PARAMETER_NAMES = (Set) Stream.of("access_token", "token_type", "expires_in", "refresh_token", "scope").collect(Collectors.toSet());

private OAuth2AccessTokenResponseConverter() {
}

public OAuth2AccessTokenResponse convert(Map<String, Object> tokenResponseParameters) {
String accessToken = (String)tokenResponseParameters.get("access_token");
OAuth2AccessToken.TokenType accessTokenType = null;
if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase((String)tokenResponseParameters.get("token_type"))) {
accessTokenType = OAuth2AccessToken.TokenType.BEARER;
}

long expiresIn = 0L;
if (tokenResponseParameters.containsKey("expires_in")) {
try {
expiresIn = Long.valueOf((String)tokenResponseParameters.get("expires_in"));
} catch (NumberFormatException var9) {
}
}

Set<String> scopes = Collections.emptySet();
String refreshToken;
if (tokenResponseParameters.containsKey("scope")) {
refreshToken = (String)tokenResponseParameters.get("scope");
scopes = (Set) Arrays.stream(StringUtils.delimitedListToStringArray(refreshToken, " ")).collect(Collectors.toSet());
}

refreshToken = (String)tokenResponseParameters.get("refresh_token");
Map<String, Object> additionalParameters = new LinkedHashMap();
tokenResponseParameters.entrySet().stream().filter((e) -> {
return !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();
}
}
}

Powered by AppBlog.CN     浙ICP备14037229号

Copyright © 2012 - 2021 APP开发技术博客 All Rights Reserved.

访客数 : | 访问量 :