Spring Security OAuth2 cannot parse scope when returned as an array

Viewed 632

I'm setting up a web server that uses Twitch authorization code flow to login a user using the authorization code flow.

The authorization step already works, but jaxson throws an exception during the step where it retrieves the token from Twitch. Digging through the oauth2 security code it looks like jaxson parses the scope as a string, but Twitch returns the scope as an array of strings causing jaxson to throw. I'm thinking maybe I could override the jaxson parser, but I'm not sure how to go about doing that.

This is what the token return from Twitch looks like:

{
   "access_token": <access-token>,
   "refresh_token": <refresh-token>,
   "expires_in": 3600,
   "scope": ["viewing_activity_read"],
   "token_type": "bearer"
}

The scope being an array is causing the problem at this point in OAuth2AccessTokenResponseHttpMessageConverter at this.jsonMessageConverter.read:

@Override
    protected OAuth2AccessTokenResponse readInternal(Class<? extends OAuth2AccessTokenResponse> clazz, HttpInputMessage inputMessage)
            throws IOException, HttpMessageNotReadableException {

        try {
            @SuppressWarnings("unchecked")
            Map<String, String> tokenResponseParameters = (Map<String, String>) 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);
        }
    }

Thanks!

1 Answers

I figured out the solution. It's two parts, first you have to override OAuth2AccessTokenResponseHttpMessageConverter and make the necessary changes to work with however the token is formatted. I found an example of the OAuth2AccessTokenResponseHttpMessageConverter override here. This is not exactly how I overrode it because I needed to parse an array, but it's a good start for anyone that needs it. Next you add the overriden class in your WebSecurityConfigurerAdapter configuration. I created a method that returns a OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> using my version of OAuth2AccessTokenResponseHttpMessageConverter.

private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient() {
        DefaultAuthorizationCodeTokenResponseClient client = new DefaultAuthorizationCodeTokenResponseClient();
        RestTemplate restTemplate = new RestTemplate(Arrays.asList(
                new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter()));
        HttpClient requestFactory = HttpClientBuilder.create().build();
        restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(requestFactory));
        restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
        client.setRestOperations(restTemplate);
        return client;
    }

Then I added the accessTokenResponseClient() to the HttpSecurity config.

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            //
            .oauth2Login()
            .tokenEndpoint()
            .accessTokenResponseClient(accessTokenResponseClient())
            //
}
Related