From a0320b9505073357bbd085e5ee4a4894ecd1e9f3 Mon Sep 17 00:00:00 2001 From: Thomas <> Date: Mon, 6 Feb 2023 15:04:35 +0100 Subject: feat(http): add request interceptor to pre-emptive HTTP Basic authentication --- .../core/impl/http/HttpClientConfiguration.java | 3 ++ .../eaaf/core/impl/http/HttpClientFactory.java | 8 ++++ .../interceptor/PreemptiveAuthInterceptor.java | 55 ++++++++++++++++++++++ .../eaaf/core/test/http/HttpClientFactoryTest.java | 33 +++++++++++++ 4 files changed, 99 insertions(+) create mode 100644 eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/interceptor/PreemptiveAuthInterceptor.java (limited to 'eaaf_core_utils') diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java index 40d22205..5e873fe8 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientConfiguration.java @@ -45,6 +45,9 @@ public class HttpClientConfiguration { @Setter boolean disableHostnameValidation = false; + @Setter + boolean enablePreEmptiveHttpBasicAuth = true; + @Setter boolean disableTlsHostCertificateValidation = false; diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java index 784dbe0e..ac5905ac 100644 --- a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/HttpClientFactory.java @@ -49,6 +49,7 @@ import at.gv.egiz.eaaf.core.exceptions.EaafException; import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory; import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration.KeyStoreType; import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.core.impl.http.interceptor.PreemptiveAuthInterceptor; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -305,6 +306,13 @@ public class HttpClientFactory implements IHttpClientFactory { log.info("Basic http authentication was injected with username: {}", httpClientConfig.getUsername()); + if (httpClientConfig.isEnablePreEmptiveHttpBasicAuth()) { + log.info("Inject pre-emptive HTTP Basic-Auth interceptor for client: {}", + httpClientConfig.getFriendlyName()); + builder.addInterceptorFirst(new PreemptiveAuthInterceptor()); + + } + } else { log.trace("Injection of Http Basic authentication was skipped"); diff --git a/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/interceptor/PreemptiveAuthInterceptor.java b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/interceptor/PreemptiveAuthInterceptor.java new file mode 100644 index 00000000..5edc8cac --- /dev/null +++ b/eaaf_core_utils/src/main/java/at/gv/egiz/eaaf/core/impl/http/interceptor/PreemptiveAuthInterceptor.java @@ -0,0 +1,55 @@ +package at.gv.egiz.eaaf.core.impl.http.interceptor; + +import java.io.IOException; + +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.AuthState; +import org.apache.http.auth.Credentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.impl.auth.BasicScheme; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.HttpCoreContext; + +import lombok.extern.slf4j.Slf4j; + +/** + * Intercepter for Apache HTTP client to pre-emptive Basic authentication. + * + * @author tlenz + * + */ +@Slf4j +public class PreemptiveAuthInterceptor implements HttpRequestInterceptor { + + @Override + public void process(HttpRequest request, HttpContext context) throws HttpException, IOException { + final AuthState authState = (AuthState) context.getAttribute(HttpClientContext.TARGET_AUTH_STATE); + + // If no auth scheme available yet, try to initialize it + // preemptively + if (authState.getAuthScheme() == null) { + final CredentialsProvider credentialsProvider = + (CredentialsProvider) context.getAttribute(HttpClientContext.CREDS_PROVIDER); + final HttpHost targetHost = (HttpHost) context.getAttribute(HttpCoreContext.HTTP_TARGET_HOST); + + final Credentials credentials = credentialsProvider.getCredentials( + new AuthScope(targetHost.getHostName(), targetHost.getPort())); + if (credentials == null) { + log.warn("Find HTTP credential-provider but not credential matches. " + + "Use it as it is and looking what happend"); + + } else { + log.trace("Updating HTTP basic-auth state to pre-emptive credentials ... "); + authState.update(new BasicScheme(), credentials); + + } + } + + } + +} diff --git a/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/http/HttpClientFactoryTest.java b/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/http/HttpClientFactoryTest.java index 62de99c0..7f3982be 100644 --- a/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/http/HttpClientFactoryTest.java +++ b/eaaf_core_utils/src/test/java/at/gv/egiz/eaaf/core/test/http/HttpClientFactoryTest.java @@ -1,5 +1,7 @@ package at.gv.egiz.eaaf.core.test.http; +import static org.junit.jupiter.api.Assertions.assertEquals; + import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.HttpURLConnection; @@ -179,6 +181,7 @@ public class HttpClientFactoryTest { public void getCustomClientBasicAuth() throws EaafException, ClientProtocolException, IOException, InterruptedException { final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); + config.setEnablePreEmptiveHttpBasicAuth(false); config.setAuthMode("password"); config.setUsername("jUnit"); config.setPassword("password"); @@ -206,9 +209,39 @@ public class HttpClientFactoryTest { final RecordedRequest httpReq2 = mockWebServer.takeRequest(); Assert.assertNull("wrong BasicAuthHeader", httpReq1.getHeader("Authorization")); Assert.assertNotNull("missing BasicAuthHeader", httpReq2.getHeader("Authorization")); + assertEquals("Basic alVuaXQ6cGFzc3dvcmQ=", httpReq2.getHeader("Authorization"), "wrong authHeader"); } + @Test + public void getCustomClientBasicAuthWithPreEmptive() throws EaafException, ClientProtocolException, + IOException, InterruptedException { + final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); + config.setAuthMode("password"); + config.setUsername("jUnit"); + config.setPassword("password"); + + final CloseableHttpClient client = httpClientFactory.getHttpClient(config); + Assert.assertNotNull("httpClient", client); + + //setup test webserver that requestes http Basic authentication + mockWebServer = new MockWebServer(); + mockServerUrl = mockWebServer.url("/sp/junit"); + mockWebServer.enqueue(new MockResponse().setResponseCode(200) + .setBody("Successful auth!")); + + //request webservice + final HttpUriRequest httpGet2 = new HttpGet(mockServerUrl.url().toString()); + final CloseableHttpResponse httpResp2 = client.execute(httpGet2); + Assert.assertEquals("http statusCode", 200, httpResp2.getStatusLine().getStatusCode()); + + //check request contains basic authentication after authentication was requested + final RecordedRequest httpReq1 = mockWebServer.takeRequest(); + Assert.assertNotNull("missing BasicAuthHeader", httpReq1.getHeader("Authorization")); + assertEquals("Basic alVuaXQ6cGFzc3dvcmQ=", httpReq1.getHeader("Authorization"), "wrong authHeader"); + + } + @Test public void getCustomClientBasicAuthNoUsername() { final HttpClientConfiguration config = new HttpClientConfiguration("jUnit"); -- cgit v1.2.3