0
votes

I am creating a spring boot 2 OAuth2 client/server application. The authorization server successfully logins and redirects to the client, but when the client receives the redirect (http://localhost:8080/login/oauth2/code/xe?code=ACK4Ae&state=Jw-dCGYvJa6QV-fcoTGjgY-6FyUyJHa-HBjWdsp3HM4%3D) the client browser displays:

Your login attempt was not successful, try again.

Reason: [invalid_token_response] An error occurred parsing the Access Token response: The HTTP Content-Type header must be application/json; charset=UTF-8

The stacktrace

Authentication request failed: org.springframework.security.oauth2.core.OAuth2AuthenticationException: [invalid_token_response] An error occurred parsing the Access Token response: The HTTP Content-Type header must be application/json; charset=UTF-8
org.springframework.security.oauth2.core.OAuth2AuthenticationException: [invalid_token_response] An error occurred parsing the Access Token response: The HTTP Content-Type header must be application/json; charset=UTF-8
   at org.springframework.security.oauth2.client.endpoint.NimbusAuthorizationCodeTokenResponseClient.getTokenResponse(NimbusAuthorizationCodeTokenResponseClient.java:105) ~[spring-security-oauth2-client-5.0.0.RELEASE.jar:5.0.0.RELEASE]
   at org.springframework.security.oauth2.client.endpoint.NimbusAuthorizationCodeTokenResponseClient.getTokenResponse(NimbusAuthorizationCodeTokenResponseClient.java:67) ~[spring-security-oauth2-client-5.0.0.RELEASE.jar:5.0.0.RELEASE]
   at org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider.authenticate(OAuth2LoginAuthenticationProvider.java:113) ~[spring-security-oauth2-client-5.0.0.RELEASE.jar:5.0.0.RELEASE]
   at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:174) ~[spring-security-core-5.0.0.RELEASE.jar:5.0.0.RELEASE]
   at org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter.attemptAuthentication(OAuth2LoginAuthenticationFilter.java:129) ~[spring-security-oauth2-client-5.0.0.RELEASE.jar:5.0.0.RELEASE]
   at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
   at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
   at org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.doFilterInternal(OAuth2AuthorizationRequestRedirectFilter.java:109) [spring-security-oauth2-client-5.0.0.RELEASE.jar:5.0.0.RELEASE]
   at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
   at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
   at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
   at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
   at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:100) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
   at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
   at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
   at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
   at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
   at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
   at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
   at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
   at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
   at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
   at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
   at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
   at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
   at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
   at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
   at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.23.jar:8.5.23]
   at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.23.jar:8.5.23]
   at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
   at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
   at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.23.jar:8.5.23]
   at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.23.jar:8.5.23]
   at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:108) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
   at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
   at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.23.jar:8.5.23]
   at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.23.jar:8.5.23]
   at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
   at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
   at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.23.jar:8.5.23]
   at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.23.jar:8.5.23]
   at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
   at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
   at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.23.jar:8.5.23]
   at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.23.jar:8.5.23]
   at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199) [tomcat-embed-core-8.5.23.jar:8.5.23]
   at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-8.5.23.jar:8.5.23]
   at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:478) [tomcat-embed-core-8.5.23.jar:8.5.23]
   at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) [tomcat-embed-core-8.5.23.jar:8.5.23]
   at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) [tomcat-embed-core-8.5.23.jar:8.5.23]
   at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) [tomcat-embed-core-8.5.23.jar:8.5.23]
   at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) [tomcat-embed-core-8.5.23.jar:8.5.23]
   at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803) [tomcat-embed-core-8.5.23.jar:8.5.23]
   at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-8.5.23.jar:8.5.23]
   at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) [tomcat-embed-core-8.5.23.jar:8.5.23]
   at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459) [tomcat-embed-core-8.5.23.jar:8.5.23]
   at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.23.jar:8.5.23]
   at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_144]
   at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_144]
   at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.23.jar:8.5.23]
   at java.lang.Thread.run(Thread.java:748) [?:1.8.0_144]
Caused by: com.nimbusds.oauth2.sdk.ParseException: The HTTP Content-Type header must be application/json; charset=UTF-8
   at com.nimbusds.oauth2.sdk.util.ContentTypeUtils.ensureContentType(ContentTypeUtils.java:52) ~[oauth2-oidc-sdk-5.38.jar:5.38]
   at com.nimbusds.oauth2.sdk.http.HTTPMessage.ensureContentType(HTTPMessage.java:133) ~[oauth2-oidc-sdk-5.38.jar:5.38]
   at com.nimbusds.oauth2.sdk.http.HTTPResponse.ensureContentType(HTTPResponse.java:1) ~[oauth2-oidc-sdk-5.38.jar:5.38]
   at com.nimbusds.oauth2.sdk.http.HTTPResponse.getContentAsJSONObject(HTTPResponse.java:369) ~[oauth2-oidc-sdk-5.38.jar:5.38]
   at com.nimbusds.oauth2.sdk.AccessTokenResponse.parse(AccessTokenResponse.java:235) ~[oauth2-oidc-sdk-5.38.jar:5.38]
   at com.nimbusds.oauth2.sdk.TokenResponse.parse(TokenResponse.java:74) ~[oauth2-oidc-sdk-5.38.jar:5.38]
   at org.springframework.security.oauth2.client.endpoint.NimbusAuthorizationCodeTokenResponseClient.getTokenResponse(NimbusAuthorizationCodeTokenResponseClient.java:101) ~[spring-security-oauth2-client-5.0.0.RELEASE.jar:5.0.0.RELEASE]
   ... 60 more    

The server code

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory().withClient("xe").secret("password")
            .authorizedGrantTypes("authorization_code", "refresh_token", "password").scopes("user")
            .autoApprove(true);
    }

    @Override
    public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
    }

    @Override
    public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager);
    }

    @Bean
    public TokenStore tokenStore() {
        return new InMemoryTokenStore();
    }
}


@Configuration
@EnableWebSecurity
@Order(-20)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/login").permitAll().anyRequest().authenticated()
            .and().formLogin().permitAll();
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser(User.withDefaultPasswordEncoder().username("tom").password("111").roles("USER"));
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

The spring boot 2 client configuration

spring.security.oauth2.client.registration.xe.client-id=xe
spring.security.oauth2.client.registration.xe.client-secret=password
spring.security.oauth2.client.registration.xe.client-name=xe
spring.security.oauth2.client.registration.xe.provider=x-auth
spring.security.oauth2.client.registration.xe.scope=user
spring.security.oauth2.client.registration.xe.redirect-uri-template={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.xe.client-authentication-method=post
spring.security.oauth2.client.registration.xe.authorization-grant-type=authorization_code

spring.security.oauth2.client.provider.x-auth.authorization-uri=http://localhost:9090/auth/oauth/authorize
spring.security.oauth2.client.provider.x-auth.token-uri=http://localhost:9090/auth/oauth/token
spring.security.oauth2.client.provider.x-auth.user-info-uri=http://localhost:9090/auth/user
spring.security.oauth2.client.provider.x-auth.jwk-set-uri=http://localhost:9090/token_keys
spring.security.oauth2.client.provider.x-auth.user-name-attribute=username

In the server pom

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.0.M7</version>
    <relativePath />
</parent>
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.2.1.RELEASE</version>
</dependency>

And in the client pom

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.0.M7</version>
</parent>
...
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-client</artifactId>
    <version>5.0.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-jose</artifactId>
    <version>5.0.0.RELEASE</version>
</dependency>
1
Did you able to solve this problem? I tried to add controller advice like Joe suggested, but couldn’t make it work. I appreciate if you can share your solution. - turgos
The issue has been fixed in spring-security-oauth2 2.3.0.RC1. However, I suggest to debug NimbusAuthorizationCodeTokenResponseClient and com.nimbusds.oauth2.sdk.http.HTTPRequest classes since it is very likely that your auth server is replying with a login page for the token uri endpoint (html page, hence the Content-Type error) - alex
Using 2.3.0.RC1 didn’t solve the problem, I believe you are right. Can you please share the the auth server configuration files? - turgos

1 Answers

0
votes

NimbusAuthorizationCodeTokenResponseClient uses Nimbus OAuth 2.0 SDK classes internally to send the Token Request (TokenRequest) and parse the Token response (TokenResponse).

The Token Response is parsed via TokenResponse.parse() and this is where the error happens as it checks to ensure the Content-Type header is available in the response and that it's set to application/json. This is a bug with spring-security-oauth and I've logged the issue here.

Until this is fixed, a possible workaround for spring-security-oauth would be to use a @ControllerAdvice to intercept the response from the TokenEndpoint and add the Content-Type=application/json header to the response, as follows:

@ControllerAdvice(assignableTypes = TokenEndpoint.class)
public class TokenEndpointResponseAdvice<T> implements ResponseBodyAdvice<T> {

    @Override
    public boolean supports(MethodParameter returnType,
                        Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Nullable
    @Override
    public T beforeBodyWrite(@Nullable T body,
                         MethodParameter returnType,
                         MediaType selectedContentType,
                         Class<? extends HttpMessageConverter<?>> selectedConverterType,
                         ServerHttpRequest request,
                         ServerHttpResponse response) {

        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        return body;
    }
}

Here are some further resources regarding this solution:

Controller Advice

Interception

NOTE: I have not actually tried this out myself but it should work. If it doesn't, than publish your code to GitHub and post it here and I'll check it out and get it working for you. It's more efficient to troubleshoot using the same codebase where the issue is happening.