15

I've created a test for the creation of a new user:

private static String USERS_ENDPOINT = "http://localhost:8080/users/";
private static String GROUPS_ENDPOINT = "http://localhost:8080/groups/";

@Test
@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
public void whenCreateAppUser() {

    AppUser appUser = new AppUser();
    appUser.setUsername("test@example.com");
    appUser.setPassword("password");

    // Throws java.net.HttpRetryException
    template.postForEntity(USERS_ENDPOINT, appUser, AppUser.class);

    ResponseEntity<AppUser> appUserResponse = template.getForEntity(USERS_ENDPOINT + "1/", AppUser.class);

    assertEquals("Username is incorrect. AppUser not created?",
            appUser.getUsername(), appUserResponse.getBody().getUsername());
}

However, for some reason I am getting:

Caused by: java.net.HttpRetryException: cannot retry due to server authentication, in streaming mode
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1692)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492)
    at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480)
    at org.springframework.http.client.SimpleClientHttpResponse.getRawStatusCode(SimpleClientHttpResponse.java:55)
    at org.springframework.web.client.DefaultResponseErrorHandler.hasError(DefaultResponseErrorHandler.java:49)
    at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:735)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:700)
    ... 34 more

For the call

template.postForEntity(USERS_ENDPOINT, appUser, AppUser.class);

I actually don't know what I changed because this used to work for me. Any idea what causes this issue?


My WebSecurity settings are:

@Override
public void configure(WebSecurity web) throws Exception {

    final String[] SWAGGER_UI = {
            "/swagger-resources/**",
            "/swagger-ui.html",
            "/v2/api-docs",
            "/webjars/**"
    };

    web.ignoring().antMatchers("/pub/**", "/users")
            .antMatchers(SWAGGER_UI);
}
Stefan Falk
  • 23,898
  • 50
  • 191
  • 378
  • As a side note, the JavaDoc for `classMode` in `@DirtiesContext` clearly states the following: _Setting the class mode on an annotated test method has no meaning. For method-level control, use `methodMode` instead._ – Sam Brannen Mar 06 '18 at 15:01
  • It sounds like you're attempting to make a request that requires an authenticated user. Have you recently switched on security in your web app? – Sam Brannen Mar 06 '18 at 15:03
  • @SamBrannen Yes, I did but I actually ignore this particular endpoint (see edit). I can make this call from my webclient - which works - only the unit test won't do it's part - oh the irony. Thanks for the hint regarding `methodMode`! – Stefan Falk Mar 06 '18 at 16:42
  • What happens if you drop the trailing slash in the users URL in your test like this `"http://localhost:8080/users"`? – Sam Brannen Mar 07 '18 at 14:37

3 Answers3

29

Spring's RestTemplate and Spring Boot's TestRestTemplate will on JDK's internal HttpURLConnection implementation by default, which fails to access the body of an HTTP-response with status 401 "Unauthorized".

A magical solution is to include Apache's HTTP Client into the classpath — e.g. with Maven,

  • For Spring Boot 3.x.x:

    <dependency>
      <groupId>org.apache.httpcomponents.client5</groupId>
      <artifactId>httpclient5</artifactId>
      <version>5.2.1</version>
      <scope>test</scope>
    </dependency>
    
  • For Spring Boot 2.x.x:

    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
      <version>4.5.6</version>
      <scope>test</scope>
    </dependency>
    

In this case, Spring (Boot) will use Apache's HTTP Client which doesn't face the problem.


See also: https://github.com/spring-projects/spring-security-oauth/issues/441#issuecomment-92033542

Alex Shesterov
  • 26,085
  • 12
  • 82
  • 103
  • Also upgrade your spring boot version – Mahdi Esmaeili Oct 05 '20 at 11:16
  • It didn't solve my problem, I used `restOperations.exchange(request, responseType)`, and it gave me the same error when unauthorized. – RDK Nov 26 '20 at 12:53
  • @RDK then you should post a new question with your specific problem. – Alex Shesterov Nov 26 '20 at 13:14
  • 1
    It didn't work for me because by default a new instance of OAuth2RestTemplate used SimpleClientHttpRequestFactory and even though I created HttpComponentsClientHttpRequestFactory bean in the configuration it was not used, so I set it manually using `oAuth2RestTemplateInstance.setRequestFactory(httpComponentsClientHttpRequestFactory_bean)` – RDK Dec 09 '20 at 09:52
4

Using the HttpClient from the Apache HttpComponents Client library solved the issue for me.

To use the Apache HttpClient you have to configure your RestTemplate the following way:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestClientConfig {

    @Bean
    public RestTemplate restTemplate() {
        var template = new RestTemplate();

        template.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

        return template;
    }

}
Dharman
  • 30,962
  • 25
  • 85
  • 135
mwatzer
  • 816
  • 1
  • 11
  • 11
1

As answered in java.net.HttpRetryException: cannot retry due to server authentication, in streaming mode

an alternative is:

SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setOutputStreaming(false);
return requestFactory;
blacelle
  • 2,199
  • 1
  • 19
  • 28