diff --git a/pulsar-client-api/src/main/java/org/apache/pulsar/client/api/Authentication.java b/pulsar-client-api/src/main/java/org/apache/pulsar/client/api/Authentication.java
index 48d9e3e230701..b60e7e7761fc5 100644
--- a/pulsar-client-api/src/main/java/org/apache/pulsar/client/api/Authentication.java
+++ b/pulsar-client-api/src/main/java/org/apache/pulsar/client/api/Authentication.java
@@ -82,6 +82,13 @@ default AuthenticationDataProvider getAuthData(String brokerHostName) throws Pul
*/
void start() throws PulsarClientException;
+ /**
+ * Initialize the authentication provider with {@link AuthenticationInitContext}.
+ */
+ default void start(AuthenticationInitContext context) throws PulsarClientException {
+ start();
+ }
+
/**
* An authentication Stage.
* when authentication complete, passed-in authFuture will contains authentication related http request headers.
diff --git a/pulsar-client-api/src/main/java/org/apache/pulsar/client/api/AuthenticationInitContext.java b/pulsar-client-api/src/main/java/org/apache/pulsar/client/api/AuthenticationInitContext.java
new file mode 100644
index 0000000000000..edd8dd95b4bd1
--- /dev/null
+++ b/pulsar-client-api/src/main/java/org/apache/pulsar/client/api/AuthenticationInitContext.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pulsar.client.api;
+
+
+import java.util.Optional;
+
+/**
+ * Authentication initialization context that provides access to shared services and resources
+ * during the authentication provider initialization phase.
+ *
+ *
This context enables authentication implementations to use shared resources such as
+ * thread pools, DNS resolvers, and timers that are managed by the Pulsar client, rather than
+ * creating their own instances. This improves resource utilization and performance, especially
+ * when multiple client instances or authentication providers are used within the same application.
+ *
+ *
Authentication providers should prefer using shared resources when available to minimize
+ * system resource consumption and improve performance through better resource reuse.
+ *
+ * @see Authentication
+ */
+public interface AuthenticationInitContext {
+ /**
+ * Retrieves a shared service instance by its class type.
+ *
+ *
This method looks up a globally registered service which is shared among
+ * all authentication providers.
+ *
+ *
Example:
+ *
{@code
+ * Optional eventLoop = context.getService(EventLoopGroup.class);
+ * if (eventLoop.isPresent()) {
+ * // Use the shared event loop group
+ * this.eventLoopGroup = eventLoop.get();
+ * } else {
+ * // Fallback to creating a new instance
+ * this.eventLoopGroup = new NioEventLoopGroup();
+ * }
+ * }
+ *
+ * @param The type of service to retrieve
+ * @param serviceClass The class of the service to retrieve
+ * @return An {@link Optional} containing the service instance if available,
+ * or {@link Optional#empty()} if no such service is registered
+ */
+ Optional getService(Class serviceClass);
+
+ /**
+ * Retrieves a named shared service instance by its class type and name.
+ *
+ *
This method allows lookup of services that are registered under a specific
+ * name, enabling multiple instances of the same service type to be distinguished.
+ * This is useful for:
+ *
+ *
Specialized DNS resolvers (e.g., "secure-dns", "internal-dns")
+ *
Different thread pools for various purposes
+ *
Multiple timer instances with different configurations
+ *
HTTP clients with different proxy configurations
+ *
+ *
+ *
Example:
+ *
{@code
+ * // Get a DNS resolver configured for internal network resolution
+ * Optional internalResolver =
+ * context.getServiceByName(NameResolver.class, "internal-network");
+ *
+ * // Get a different DNS resolver for external resolution
+ * Optional externalResolver =
+ * context.getServiceByName(NameResolver.class, "external-network");
+ * }
+ *
+ * @param The type of service to retrieve
+ * @param serviceClass The class of the service to retrieve. Cannot be null.
+ * @param name The name under which the service is registered. Cannot be null or empty.
+ * @return An {@link Optional} containing the named service instance if available,
+ * or {@link Optional#empty()} if no such service is registered with the given name
+ */
+ Optional getServiceByName(Class serviceClass, String name);
+}
diff --git a/pulsar-client/src/main/java/org/apache/pulsar/client/impl/AuthenticationInitContextImpl.java b/pulsar-client/src/main/java/org/apache/pulsar/client/impl/AuthenticationInitContextImpl.java
new file mode 100644
index 0000000000000..3f6d782f0035f
--- /dev/null
+++ b/pulsar-client/src/main/java/org/apache/pulsar/client/impl/AuthenticationInitContextImpl.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pulsar.client.impl;
+
+import io.netty.channel.EventLoopGroup;
+import io.netty.resolver.NameResolver;
+import io.netty.util.Timer;
+import java.net.InetAddress;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import org.apache.pulsar.client.api.AuthenticationInitContext;
+
+public class AuthenticationInitContextImpl implements AuthenticationInitContext {
+ private final Map, Object> sharedServices = new HashMap<>();
+ private final Map, Object>> namedServices = new HashMap<>();
+
+ public AuthenticationInitContextImpl(EventLoopGroup eventLoopGroup,
+ Timer timer,
+ NameResolver nameResolver) {
+ this.sharedServices.put(EventLoopGroup.class, eventLoopGroup);
+ this.sharedServices.put(Timer.class, timer);
+ this.sharedServices.put(NameResolver.class, nameResolver);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Optional getService(Class serviceClass) {
+ if (serviceClass == null) {
+ throw new IllegalArgumentException("Service class cannot be null");
+ }
+ return Optional.ofNullable((T) sharedServices.get(serviceClass));
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Optional getServiceByName(Class serviceClass, String name) {
+ if (name == null || name.isEmpty()) {
+ throw new IllegalArgumentException("Service name cannot be null or empty");
+ }
+ Map, Object> services = namedServices.get(name);
+ if (services != null) {
+ return Optional.ofNullable((T) services.get(serviceClass));
+ }
+ return Optional.empty();
+ }
+
+ public void addService(Class serviceClass, T instance) {
+ sharedServices.put(serviceClass, instance);
+ }
+
+ public void addService(Class serviceClass, String name, T instance) {
+ namedServices.computeIfAbsent(name, k -> new HashMap<>())
+ .put(serviceClass, instance);
+ }
+}
diff --git a/pulsar-client/src/main/java/org/apache/pulsar/client/impl/PulsarClientImpl.java b/pulsar-client/src/main/java/org/apache/pulsar/client/impl/PulsarClientImpl.java
index 3a2ff97f51ecc..a9e710a9b8c51 100644
--- a/pulsar-client/src/main/java/org/apache/pulsar/client/impl/PulsarClientImpl.java
+++ b/pulsar-client/src/main/java/org/apache/pulsar/client/impl/PulsarClientImpl.java
@@ -228,7 +228,6 @@ public PulsarClientImpl(ClientConfigurationData conf, EventLoopGroup eventLoopGr
this.eventLoopGroup = eventLoopGroupReference;
this.instrumentProvider = new InstrumentProvider(conf.getOpenTelemetry());
clientClock = conf.getClock();
- conf.getAuthentication().start();
this.scheduledExecutorProvider = scheduledExecutorProvider != null ? scheduledExecutorProvider :
PulsarClientResourcesConfigurer.createScheduledExecutorProvider(conf);
if (connectionPool != null) {
@@ -270,6 +269,7 @@ public PulsarClientImpl(ClientConfigurationData conf, EventLoopGroup eventLoopGr
} else {
this.timer = timer;
}
+ conf.getAuthentication().start(buildAuthenticationInitContext());
lookup = createLookup(conf.getServiceUrl());
if (conf.getServiceUrlProvider() != null) {
@@ -306,6 +306,13 @@ public PulsarClientImpl(ClientConfigurationData conf, EventLoopGroup eventLoopGr
throw t;
}
}
+ private AuthenticationInitContextImpl buildAuthenticationInitContext() {
+ return new AuthenticationInitContextImpl(
+ createdEventLoopGroup ? null : eventLoopGroup,
+ needStopTimer ? null : timer,
+ addressResolver == null ? null : DnsResolverUtil.adaptToNameResolver(addressResolver)
+ );
+ }
private void reduceConsumerReceiverQueueSize() {
for (ConsumerBase> consumer : consumers) {
@@ -1086,7 +1093,7 @@ public void updateAuthentication(Authentication authentication) throws IOExcepti
conf.getAuthentication().close();
}
conf.setAuthentication(authentication);
- conf.getAuthentication().start();
+ conf.getAuthentication().start(buildAuthenticationInitContext());
}
public void updateTlsTrustCertsFilePath(String tlsTrustCertsFilePath) {
diff --git a/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/httpclient/AuthenticationHttpClientConfig.java b/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/httpclient/AuthenticationHttpClientConfig.java
new file mode 100644
index 0000000000000..4e7981fc29348
--- /dev/null
+++ b/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/httpclient/AuthenticationHttpClientConfig.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pulsar.client.impl.auth.httpclient;
+
+
+import lombok.Builder;
+import lombok.Getter;
+
+/**
+ * Configuration for HTTP clients used in authentication providers.
+ *
+ *
This class encapsulates the configuration parameters needed to create HTTP clients
+ * for authentication-related HTTP requests, such as OAuth2 token exchange requests.
+ *
+ *
Configuration Parameters
+ *
+ *
readTimeout: Maximum time to wait for a response from the server
+ * (default: 30000 ms)
+ *
connectTimeout: Maximum time to establish a connection
+ * (default: 10000 ms)
+ *
trustCertsFilePath: Path to a custom trust certificate file
+ * for TLS/SSL connections
+ *
This configuration is primarily used by {@link AuthenticationHttpClientFactory} to
+ * create properly configured HTTP clients for authentication providers.
+ *
+ * @see AuthenticationHttpClientFactory
+ * @see org.apache.pulsar.client.impl.auth.oauth2.AuthenticationOAuth2
+ */
+@Getter
+public class AuthenticationHttpClientConfig {
+ private int readTimeout = 30000;
+ private int connectTimeout = 10000;
+ private final String trustCertsFilePath;
+
+ @Builder(builderClassName = "ConfigBuilder")
+ public AuthenticationHttpClientConfig(int readTimeout, int connectTimeout, String trustCertsFilePath) {
+ this.readTimeout = readTimeout > 0 ? readTimeout : this.readTimeout;
+ this.connectTimeout = connectTimeout > 0 ? connectTimeout : this.connectTimeout;
+ this.trustCertsFilePath = trustCertsFilePath;
+ }
+}
diff --git a/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/httpclient/AuthenticationHttpClientFactory.java b/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/httpclient/AuthenticationHttpClientFactory.java
new file mode 100644
index 0000000000000..b3d02c9e6ef6c
--- /dev/null
+++ b/pulsar-client/src/main/java/org/apache/pulsar/client/impl/auth/httpclient/AuthenticationHttpClientFactory.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pulsar.client.impl.auth.httpclient;
+
+import io.netty.channel.EventLoopGroup;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.resolver.NameResolver;
+import io.netty.util.Timer;
+import java.io.File;
+import java.net.InetAddress;
+import java.util.Optional;
+import javax.net.ssl.SSLException;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.pulsar.PulsarVersion;
+import org.apache.pulsar.client.api.AuthenticationInitContext;
+import org.asynchttpclient.AsyncHttpClient;
+import org.asynchttpclient.DefaultAsyncHttpClient;
+import org.asynchttpclient.DefaultAsyncHttpClientConfig;
+
+/**
+ * Factory for creating HTTP clients used by authentication providers.
+ *
+ *
This factory creates {@link AsyncHttpClient} instances that are optimized for
+ * authentication-related HTTP requests. It supports:
+ *
+ *
Reusing shared resources from {@link AuthenticationInitContext}
+ *
Configurable timeouts for connections and reads
+ *
Custom SSL/TLS trust certificates
+ *
DNS resolver configuration
+ *
+ *
+ *
Resource Sharing
+ *
When a {@link AuthenticationInitContext} is provided, the factory will attempt to
+ * reuse shared resources:
+ *