package com.ankurm.restclientbasicauth; import com.ankurm.restclientbasicauth.client.ApiClient; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatusCode; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestClient; import static org.assertj.core.api.Assertions.assertThat; /** * A real embedded Tomcat on a random port, a real Spring Security filter chain, and a real * RestClient making real HTTP calls over loopback -- no MockMvc here, because the point is to * prove the client actually authenticates over the wire, the same way the original article's * CommandLineRunner did against RestTemplate. */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class RestClientBasicAuthTest { @LocalServerPort int port; @Autowired ApiClient apiClient; @Autowired RestClient.Builder builder; private String baseUrl() { return "http://localhost:" + port; } @Test void defaultHeadersBasicAuthSucceeds() { String response = apiClient.callSecuredEndpointViaDefaultHeaders(baseUrl()); assertThat(response).isEqualTo("Hello, you have accessed a secured endpoint!"); Transcript.write("01-restclient-basic-auth-default-headers.txt", "// RestClient.Builder builder = ...;\n" + "// RestClient client = builder.defaultHeaders(h -> h.setBasicAuth(\"admin\", \"password123\")).build();\n" + "// client.get().uri(baseUrl + \"/api/hello\").retrieve().body(String.class);\n\n" + "Response: " + response + "\n"); } @Test void interceptorBasicAuthSucceeds() { String response = apiClient.callSecuredEndpointViaInterceptor(baseUrl()); assertThat(response).isEqualTo("Hello, you have accessed a secured endpoint!"); Transcript.write("02-restclient-basic-auth-interceptor.txt", "// RestClient.Builder builder = ...;\n" + "// RestClient client = builder.requestInterceptor(\n" + "// new BasicAuthenticationInterceptor(\"admin\", \"password123\")).build();\n" + "// client.get().uri(baseUrl + \"/api/hello\").retrieve().body(String.class);\n\n" + "Response: " + response + "\n"); } @Test void noCredentialsGets401() { RestClient noAuthClient = builder.build(); // the auto-configured builder, no basic auth added HttpClientErrorException ex = catchHttpClientErrorException(() -> noAuthClient.get().uri(baseUrl() + "/api/hello").retrieve().body(String.class)); assertThat(ex.getStatusCode()).isEqualTo(org.springframework.http.HttpStatus.UNAUTHORIZED); Transcript.write("03-restclient-no-credentials-401.txt", "// RestClient client = builder.build(); // no basic auth\n" + "// client.get().uri(baseUrl + \"/api/hello\").retrieve().body(String.class);\n\n" + "Thrown: " + ex.getClass().getName() + "\n" + "Status: " + ex.getStatusCode() + "\n" + "\n# retrieve() throws HttpClientErrorException on 4xx by default -- same default as\n" + "# RestTemplate, unlike RestClient's own exchange() method, which disables that default.\n"); } @Test void wrongPasswordAlsoGets401() { RestClient wrongPasswordClient = builder .defaultHeaders(h -> h.setBasicAuth("admin", "not-the-password")) .build(); HttpClientErrorException ex = catchHttpClientErrorException(() -> wrongPasswordClient.get().uri(baseUrl() + "/api/hello").retrieve().body(String.class)); assertThat(ex.getStatusCode()).isEqualTo(org.springframework.http.HttpStatus.UNAUTHORIZED); Transcript.write("04-restclient-wrong-password-401.txt", "Thrown: " + ex.getClass().getName() + "\n" + "Status: " + ex.getStatusCode() + "\n"); } private interface ThrowingRunnable { void run(); } private HttpClientErrorException catchHttpClientErrorException(ThrowingRunnable runnable) { try { runnable.run(); } catch (HttpClientErrorException e) { return e; } throw new AssertionError("Expected HttpClientErrorException but none was thrown"); } }