Spring-security-oauth: RestClientException client shows empty response body for Spring Client authentication (possible bug)

Created on 24 Mar 2015  路  17Comments  路  Source: spring-projects/spring-security-oauth

I am using Spring Oauth client setup on my frontend. I'm authentication against my API, which returns this

  {
        error: "unauthorized"
        error_description: "User does not exist"
   }

using my rest client (Google Plug: Advance RestClient); which is expected.When I use Spring Oauth Client setup, I was expecting that the error object RestClientException would have that JSON result in the response body however it is empty. See attached image (Watch Console shows what's in the response body).

error

stackoverflow

Most helpful comment

I have similar issue.
When rest call returns UNAUTHORIZED then response body is lost/ignored by RestTemplate.
However when I do the same request using any gui-client, then UNAUTHORIZED status AND not-empty response body are returned.

I did also another test: temporarily changed return status to OK on the endpoint and then response body is not-empty as expected. Reverting to UNAUTHORIZED again returns empty body.

For sure when response status is UNAUTHORIZED RestTemplate loses response body.
For me it's a bug.

All 17 comments

Can you please be more specific about the request and response details (headers and bodies)?

@dsyer The request grant type used was custom c_password which extends the password grant type. I'm using the Oauth Client setup to make the request. I was posting the incorrect credential and noting what gets return in the body from my API (secured by spring oauth).

Now the response header, I get back from the API is 401 Unauthorized however, the response body is empty. As you can see from my first post, I get that JSON response when making the request using my Advance RestClient however, using my RestTemplate (through Spring Oauth Client configuration), the response body is empty (HttpClientErrorException->getResponseBodyAsString is empty though simulating the exact request through Advance RestClient, return a JSON error response body)

Can you show the code you use on the client please (maybe a test case)? Is it only a problem with your custom grant type or is it something that affects built in grants as well?

@dsyer Sure. This is my custom grant type on the client side (I haven't test for the other built-in grant types):

   public class CustomResourceOwnerPasswordProviderToken extends ResourceOwnerPasswordAccessTokenProvider implements AccessTokenProvider {

    @Override
     public boolean supportsResource(OAuth2ProtectedResourceDetails resource) {
         return resource instanceof ResourceOwnerPasswordResourceDetails && "c_password".equals(resource.getGrantType());
      }

    @Override
     public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest request)
        throws UserRedirectRequiredException, AccessDeniedException, OAuth2AccessDeniedException {

      ResourceOwnerPasswordResourceDetails resource =   (ResourceOwnerPasswordResourceDetails) details;
      return retrieveToken(request, resource, getParametersForTokenRequest(resource, request), new HttpHeaders());

    }

     private MultiValueMap<String, String>  getParametersForTokenRequest(ResourceOwnerPasswordResourceDetails resource,    AccessTokenRequest request) {

      MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
      form.set("grant_type", "c_password");

      form.set("username", resource.getUsername());
      form.set("password", resource.getPassword());
      form.putAll(request);

       if (resource.isScoped()) {

            StringBuilder builder = new StringBuilder();
            List<String> scope = resource.getScope();

            if (scope != null) {
               Iterator<String> scopeIt = scope.iterator();
                while (scopeIt.hasNext()) {
                    builder.append(scopeIt.next());
                    if (scopeIt.hasNext()) {
                          builder.append(' ');
                     }
                 }
            }

            form.set("scope", builder.toString());
       }

        return form;

     }


  }

This is my xml configuration

    <bean id="cPassword" class="org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails">
    <property name="grantType" value="c_password"></property>
    <property name="clientAuthenticationScheme" value="header"></property>
    <property name="clientId" value="test"></property>
    <property name="clientSecret" value="test"></property>
    <property name="accessTokenUri" value="${oauth.accessTokenUri}"></property>
</bean>:



    <oauth:rest-template id="cPasswordRest" resource="cPassword" access-token-provider="accessTokenProvider">
</oauth:rest-template>

And I make the request through my controller:

 @Controller
 public class LoginController {

   private OAuth2RestTemplate loginRest;

   @Resource(name="cPasswordRest")
    public void setLoginRest(OAuth2RestTemplate loginRest) {
          this.loginRest = loginRest;
   }

@RequestMapping(method=RequestMethod.GET)
 public @ResponseBody Boolean verifyCredential()
 {
    Boolean result = false;

          try {
               OAuth2AccessToken token = this.loginRest.getAccessToken();
               if (token != null)
                   result = true;
           }
          catch(Exception e)
          {

          }
          return result;
  }
 }

I didn't set the username and password for the user because, I wanted to see what the error response would be; the error happens at the call in the controller for _getAccessToken_. My endpoint is available here http://coin-traders-api.herokuapp.com/oauth/token

Sorry, but it's still really hard to see what you think is wrong. The call to getAccessToken() fails because of a 401? And what?

@dsyer OK, the issue is when I call getAccessToken with the wrong credentials, the response is 401 error (which is expected) however, the response body is empty (that is the issue - no JSON error response). I was expected a JSON response but, nothing exist for me to show to my users what happened.

When I evoke the same endpoint with the same setup using the Advance Rest Client, I get the response body (JSON response noting the error). I'm not sure why on the client side it doesn't show the JSON response.

When I debug the code all the way up to Oauth2AccessTokenSupport->retrieveToken, there is a catch statement with catch (RestClientException rce) (from the image in the first post). When I look at the responsebody in the rce variable, it is empty.

If you send the wrong user credentials you actually get a 400 (InvalidGrantException), which I think is per the spec. You must be sending the wrong client credentials? Or else your token endpoint is not secured in the usual way. In either case can you please write a test case, e.g. by modifying one of the existing integration tests? Example (add this to https://github.com/spring-projects/spring-security-oauth/blob/master/tests/xml/common/src/main/java/sparklr/common/AbstractResourceOwnerPasswordProviderTests.java) :

    @Test
    @OAuth2ContextConfiguration(resource=ResourceOwner.class, initialize=false)
    public void testInvalidUserCredentials() throws Exception {
        ((ResourceOwnerPasswordResourceDetails) context.getResource()).setPassword("no-the-password");
        try {
            context.getAccessToken();
        }
        catch (InvalidGrantException e) {
            assertEquals("Bad credentials", e.getMessage());
        }
    }

@dsyer I'll modify the test case in a bit but, not sure if this would help. In my case, I am indeed sending the wrong credential however, the client credential is correct but, the user credential is not (resource owner). Again, when I try to send the request, I got the RestClientException on the client site.

All I was saying, I would've expected to see

  {
    error: "unauthorized"
    error_description: "User does not exist"
}

In the ((HttpClientErrorExceptin)RestClientException)->getResponseBodyAsString(), as to give my users some sensible error feedback.

Also, I think your test scenario is happening on the data provider side and not the client site. I believe it does throws InvalidGrantException there and not the client site.

Thanks.

I haven't quite figured this out yet, but the behaviour depends on the HTTP client request implementation that you use in your AccessTokenProvider. By default you get a SimpleHttpClientRequestFactory and that one always throws an HttpClientErrorException with an empty body. If you use the (generally far superior) HttpComponentsClientHttpRequestFactory you get an OAuth2Exception with the error message from the server (and the wrong status code, which is probably a bug). In both cases I could only reproduce the problem with your custom grant type though, so unless we can narrow it down to a simpler test case it's hard to make more progress. Also I think you might have changed the client password because the 401 is not the same as you described (it says the client is unknown not the user).

The SimpleHttpClientRequestFactory doesn't really deal very well with 401s. The underlying com.sun.* HTTP client cannot access the body of a request that failed (it seems), so you get this kind of thing, instead of the "real" exception:

org.springframework.web.client.ResourceAccessException:  I/O error on POST request for "http://localhost:33276/oauth/token": 
cannot retry due to server authentication, in streaming mode; 
nested exception is java.net.HttpRetryException: cannot retry due to server authentication, in streaming mode

I'll keep digging a bit on the OAuth2Exception.

The spec says that bad user credentials in a password grant should return a 400, and that's what the default behaviour is for Spring OAuth (client and server seem to behave as expected). Your custom token granter is allowed to do anything it likes of course, so if you want a 401, that seems reasonable to me on the server side.

The empty body on the client side is purely an artifact of the client, which is a combination of Spring Web RestTemplate and the java.net.* HTTP client, so if you believe there is a problem you would need to take it up there (there's nothing we can do in Spring OAuth to influence either one).

I have similar issue.
When rest call returns UNAUTHORIZED then response body is lost/ignored by RestTemplate.
However when I do the same request using any gui-client, then UNAUTHORIZED status AND not-empty response body are returned.

I did also another test: temporarily changed return status to OK on the endpoint and then response body is not-empty as expected. Reverting to UNAUTHORIZED again returns empty body.

For sure when response status is UNAUTHORIZED RestTemplate loses response body.
For me it's a bug.

Same issue here, only occurs on a 401 with a valid response body.

Using HttpComponentsClientHttpRequestFactory fixes this as was suggested

I was getting the following exception when trying to run a unit test which expects a 401 Unauthorized exception as the response code.

org.springframework.web.client.ResourceAccessException: I/O error on POST request for "<removed>": cannot retry due to server authentication, in streaming mode; nested exception is java.net.HttpRetryException: cannot retry due to server authentication, in streaming mode

Interestingly, it seemed to be the same try-catch exception scenario in the (Test)RestTemplate code as highlighted in this thread. I got around the solution by using writing the same test using MockMvc, as suggested by my team lead.

i have the same problem.
when try to call a rest service with restTemplate, the body of response is lost but not the headers.

Try testCompile('org.apache.httpcomponents:httpclient')

I have similar issue.
When rest call returns UNAUTHORIZED then response body is lost/ignored by RestTemplate.
However when I do the same request using any gui-client, then UNAUTHORIZED status AND not-empty response body are returned.

I did also another test: temporarily changed return status to OK on the endpoint and then response body is not-empty as expected. Reverting to UNAUTHORIZED again returns empty body.

For sure when response status is UNAUTHORIZED RestTemplate loses response body.
For me it's a bug.

This is reported as issue in Spring Framework.
https://jira.spring.io/browse/SPR-9367

Was this page helpful?
0 / 5 - 0 ratings