diff --git a/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java b/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java index 76e2dec93..7ca25f473 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/internal/HttpAPIClientHelper.java @@ -601,6 +601,9 @@ private void addHeaders(HttpPost req, Map requestConfig) { } else if (ClientConfigProperties.HTTP_USE_BASIC_AUTH.getOrDefault(requestConfig).booleanValue()) { String user = ClientConfigProperties.USER.getOrDefault(requestConfig); String password = ClientConfigProperties.PASSWORD.getOrDefault(requestConfig); + if (password == null) { + password = ""; + } // Use as-is, no encoding allowed req.addHeader( HttpHeaders.AUTHORIZATION, diff --git a/client-v2/src/test/java/com/clickhouse/client/HttpTransportTests.java b/client-v2/src/test/java/com/clickhouse/client/HttpTransportTests.java index fc2d13a86..0bf88ea46 100644 --- a/client-v2/src/test/java/com/clickhouse/client/HttpTransportTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/HttpTransportTests.java @@ -1003,6 +1003,40 @@ public void testBearerTokenAuth() throws Exception { } } + @Test(groups = { "integration" }) + public void testBasicAuthWithNoPassword() throws Exception { + WireMockServer mockServer = new WireMockServer(WireMockConfiguration + .options().port(9090).notifier(new ConsoleNotifier(false))); + mockServer.start(); + + try { + // Expected: "default:" with empty password, not "default:null" + String expectedAuth = "Basic " + Base64.getEncoder() + .encodeToString("default:".getBytes(StandardCharsets.UTF_8)); + + mockServer.addStubMapping(WireMock.post(WireMock.anyUrl()) + .withHeader("Authorization", WireMock.equalTo(expectedAuth)) + .willReturn(WireMock.aResponse() + .withHeader("X-ClickHouse-Summary", + "{ \"read_bytes\": \"10\", \"read_rows\": \"1\"}")).build()); + + try (Client client = new Client.Builder() + .addEndpoint(Protocol.HTTP, "localhost", mockServer.port(), false) + .compressServerResponse(false) + // no setPassword() call — password should default to empty, not "null" + .build()) { + + try (QueryResponse response = client.query("SELECT 1").get(1, TimeUnit.SECONDS)) { + Assert.assertEquals(response.getReadBytes(), 10); + } catch (Exception e) { + Assert.fail("Basic auth with no password should send empty password, not 'null'", e); + } + } + } finally { + mockServer.stop(); + } + } + @Test(groups = { "integration" }) public void testJWTWithCloud() throws Exception { if (!isCloud()) {