This is an automated email from the ASF dual-hosted git repository.

lmccay pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git


The following commit(s) were added to refs/heads/master by this push:
     new 3f1cf239a KNOX-3247 - Knox LDAP Server with Pluggable Backend (#1144)
3f1cf239a is described below

commit 3f1cf239aefc059967c5482c8486ad65f1eac4f6
Author: lmccay <[email protected]>
AuthorDate: Thu Feb 12 17:16:23 2026 -0500

    KNOX-3247 - Knox LDAP Server with Pluggable Backend (#1144)
    
    * KNOX-3247 - Knox LDAP Server with Pluggable Backend
---
 gateway-server/pom.xml                             |  54 +++
 .../org/apache/knox/gateway/GatewayMessages.java   |  13 +
 .../gateway/config/impl/GatewayConfigImpl.java     |  62 +++
 .../gateway/services/DefaultGatewayServices.java   |   8 +
 .../services/ldap/GroupLookupInterceptor.java      | 147 +++++++
 .../services/ldap/KnoxLDAPServerManager.java       | 237 +++++++++++
 .../gateway/services/ldap/KnoxLDAPService.java     | 130 ++++++
 .../knox/gateway/services/ldap/LdapMessages.java   |  87 ++++
 .../services/ldap/backend/BackendFactory.java      |  49 +++
 .../gateway/services/ldap/backend/FileBackend.java | 144 +++++++
 .../gateway/services/ldap/backend/LdapBackend.java |  68 ++++
 .../services/ldap/backend/LdapProxyBackend.java    | 452 +++++++++++++++++++++
 ....knox.gateway.services.ldap.backend.LdapBackend |  21 +
 .../src/main/resources/conf/gateway-site.xml       | 181 +++++++++
 .../services/AbstractGatewayServicesTest.java      |   3 +-
 .../services/ldap/KnoxLDAPServerManagerTest.java   | 188 +++++++++
 .../gateway/services/ldap/KnoxLDAPServiceTest.java | 194 +++++++++
 .../services/ldap/backend/BackendFactoryTest.java  | 124 ++++++
 .../org/apache/knox/gateway/GatewayTestConfig.java |  36 ++
 .../apache/knox/gateway/config/GatewayConfig.java  |  47 +++
 .../apache/knox/gateway/services/ServiceType.java  |   3 +-
 knox-token-management-ui/package.json              |   2 +-
 pom.xml                                            |  22 +-
 23 files changed, 2268 insertions(+), 4 deletions(-)

diff --git a/gateway-server/pom.xml b/gateway-server/pom.xml
index 75ca1dfe5..e50154095 100644
--- a/gateway-server/pom.xml
+++ b/gateway-server/pom.xml
@@ -233,6 +233,10 @@
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-text</artifactId>
@@ -465,6 +469,56 @@
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+        </dependency>
+        <!-- ApacheDS dependencies for embedded LDAP server (provided to avoid 
transitive issues) -->
+        <dependency>
+            <groupId>org.apache.directory.server</groupId>
+            <artifactId>apacheds-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.directory.server</groupId>
+            <artifactId>apacheds-core-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.directory.server</groupId>
+            <artifactId>apacheds-protocol-shared</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.directory.server</groupId>
+            <artifactId>apacheds-protocol-ldap</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.directory.server</groupId>
+            <artifactId>apacheds-ldif-partition</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.directory.server</groupId>
+            <artifactId>apacheds-jdbm-partition</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.directory.api</groupId>
+            <artifactId>api-ldap-model</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.directory.api</groupId>
+            <artifactId>api-ldap-schema-data</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.directory.api</groupId>
+            <artifactId>api-ldap-client-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
         <!-- ********** ********** ********** ********** ********** ********** 
-->
         <!-- ********** Test Dependencies                           ********** 
-->
         <!-- ********** ********** ********** ********** ********** ********** 
-->
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java 
b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java
index dc133cfdf..7e8240879 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java
@@ -812,4 +812,17 @@ public interface GatewayMessages {
 
   @Message( level = MessageLevel.DEBUG, text = "Strict-Transport-Security 
header enabled with \"{0}\" option" )
   void strictTransportHeaderEnabled(String option);
+
+  // LDAP Service messages
+  @Message(level = MessageLevel.INFO, text = "LDAP service is enabled and will 
be started on port {0}")
+  void ldapServiceEnabled(int port);
+
+  @Message(level = MessageLevel.INFO, text = "LDAP service is disabled")
+  void ldapServiceDisabled();
+
+  @Message(level = MessageLevel.ERROR, text = "Failed to start LDAP service: 
{0}")
+  void ldapServiceStartFailed(@StackTrace(level = MessageLevel.DEBUG) 
Exception e);
+
+  @Message(level = MessageLevel.ERROR, text = "LDAP service not found or not 
properly registered")
+  void ldapServiceNotFound();
 }
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
index e2c099c4e..df70c37ac 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
@@ -32,6 +32,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -1698,4 +1699,65 @@ public class GatewayConfigImpl extends Configuration 
implements GatewayConfig {
   public String getStrictTransportOption() {
     return get(STRICT_TRANSPORT_OPTION, DEFAULT_STRICT_TRANSPORT_OPTION);
   }
+
+  // LDAP Service Configuration
+  @Override
+  public boolean isLDAPEnabled() {
+    return Boolean.parseBoolean(get(LDAP_ENABLED, "false"));
+  }
+
+  @Override
+  public int getLDAPPort() {
+    return Integer.parseInt(get(LDAP_PORT, "3890"));
+  }
+
+  @Override
+  public String getLDAPBaseDN() {
+    return get(LDAP_BASE_DN, "dc=proxy,dc=com");
+  }
+
+  @Override
+  public String getLDAPBackendType() {
+    return get(LDAP_BACKEND_TYPE, "file");
+  }
+
+  @Override
+  public String getLDAPBackendDataFile() {
+    String configuredPath = get(LDAP_BACKEND_DATA_FILE, null);
+    if (configuredPath != null && !configuredPath.isEmpty()) {
+      // Support ${GATEWAY_DATA_HOME} variable substitution
+      configuredPath = configuredPath.replace("${GATEWAY_DATA_HOME}", 
getGatewayDataDir());
+      return configuredPath;
+    }
+    // Default to data directory if not configured
+    return getGatewayDataDir() + File.separator + "ldap-users.json";
+  }
+
+  @Override
+  public Set<String> getPropertyNames() {
+    Set<String> names = new HashSet<>();
+    Iterator<Map.Entry<String, String>> iterator = this.iterator();
+    while (iterator.hasNext()) {
+      Map.Entry<String, String> entry = iterator.next();
+      names.add(entry.getKey());
+    }
+    return names;
+  }
+
+  @Override
+  public Map<String, String> getLDAPBackendConfig(String backendType) {
+    Map<String, String> config = new HashMap<>();
+    String prefix = "gateway.ldap.backend." + backendType + ".";
+
+    for (String key : getPropertyNames()) {
+      if (key != null && key.startsWith(prefix)) {
+        String configKey = key.substring(prefix.length());
+        String value = get(key);
+        if (value != null) {
+          config.put(configKey, value);
+        }
+      }
+    }
+    return config;
+  }
 }
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/DefaultGatewayServices.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/DefaultGatewayServices.java
index 676202d5f..06ce95d93 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/DefaultGatewayServices.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/DefaultGatewayServices.java
@@ -26,6 +26,7 @@ import org.apache.knox.gateway.deploy.DeploymentContext;
 import org.apache.knox.gateway.descriptor.FilterParamDescriptor;
 import org.apache.knox.gateway.descriptor.ResourceDescriptor;
 import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.services.ldap.KnoxLDAPService;
 import org.apache.knox.gateway.services.security.KeystoreService;
 import org.apache.knox.gateway.services.security.KeystoreServiceException;
 import org.apache.knox.gateway.topology.Provider;
@@ -82,6 +83,13 @@ public class DefaultGatewayServices extends 
AbstractGatewayServices {
     addService(ServiceType.CONCURRENT_SESSION_VERIFIER, 
gatewayServiceFactory.create(this, ServiceType.CONCURRENT_SESSION_VERIFIER, 
config, options));
 
     addService(ServiceType.GATEWAY_STATUS_SERVICE, 
gatewayServiceFactory.create(this, ServiceType.GATEWAY_STATUS_SERVICE, config, 
options));
+
+    // LDAP Service - infrastructure service for embedded LDAP server
+    if (config.isLDAPEnabled()) {
+      KnoxLDAPService ldapService = new KnoxLDAPService();
+      ldapService.init(config, options);
+      addService(ServiceType.LDAP_SERVICE, ldapService);
+    }
   }
 
   @Override
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/GroupLookupInterceptor.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/GroupLookupInterceptor.java
new file mode 100644
index 000000000..ea397fea7
--- /dev/null
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/GroupLookupInterceptor.java
@@ -0,0 +1,147 @@
+/*
+ * 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.knox.gateway.services.ldap;
+
+import org.apache.directory.api.ldap.model.cursor.ListCursor;
+import org.apache.directory.api.ldap.model.entry.Entry;
+import org.apache.directory.api.ldap.model.exception.LdapException;
+import org.apache.directory.api.ldap.model.schema.SchemaManager;
+import org.apache.directory.server.core.api.DirectoryService;
+import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
+import org.apache.directory.server.core.api.filtering.EntryFilteringCursorImpl;
+import org.apache.directory.server.core.api.interceptor.BaseInterceptor;
+import 
org.apache.directory.server.core.api.interceptor.context.BindOperationContext;
+import 
org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.services.ldap.backend.LdapBackend;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Interceptor for LDAP operations to proxy user searches to backend when not 
found locally
+ */
+public class GroupLookupInterceptor extends BaseInterceptor {
+    private static final LdapMessages LOG = 
MessagesFactory.get(LdapMessages.class);
+    private DirectoryService directoryService;
+    private LdapBackend backend;
+    private static final Pattern UID_PATTERN = 
Pattern.compile(".*\\(uid=([^)]+)\\).*");
+    private static final Pattern CN_PATTERN = 
Pattern.compile(".*\\(cn=([^)]+)\\).*");
+
+    public GroupLookupInterceptor(DirectoryService directoryService, 
LdapBackend backend) {
+        this.directoryService = directoryService;
+        this.backend = backend;
+    }
+
+    @Override
+    public EntryFilteringCursor search(SearchOperationContext ctx) throws 
LdapException {
+        String filter = ctx.getFilter() != null ? ctx.getFilter().toString() : 
"";
+        String baseDn = ctx.getDn() != null ? ctx.getDn().toString() : "";
+
+        LOG.ldapSearch(baseDn, filter);
+
+        // First try the normal search
+        EntryFilteringCursor originalResults;
+        try {
+            originalResults = next(ctx);
+        } catch (Exception e) {
+            throw new LdapException(e);
+        }
+
+        // Check if this is a user search and if we got no results, try the 
backend
+        if (isUserSearch(filter)) {
+            String username = extractUser(filter);
+
+            // Check if we have any results from local search
+            List<Entry> entries = new ArrayList<>();
+            try {
+                while (originalResults.next()) {
+                    entries.add(originalResults.get());
+                }
+                originalResults.close();
+            } catch (Exception e) {
+                // If we get an error or no results, try the backend
+            }
+
+            // If no local results, try backend
+            if (entries.isEmpty() && username != null) {
+                try {
+                    SchemaManager schemaManager = 
directoryService.getSchemaManager();
+
+                    if (username.contains("*")) {
+                        // Wildcard search - use searchUsers
+                        LOG.ldapSearch(baseDn, "wildcard user search: " + 
username);
+                        List<Entry> backendEntries = 
backend.searchUsers(username, schemaManager);
+
+                        // Return backend results directly without caching to 
avoid deadlock
+                        // (caching during an active search can cause ApacheDS 
locking issues)
+                        entries.addAll(backendEntries);
+                    } else {
+                        // Specific user lookup
+                        LOG.ldapUserLoaded(username);
+                        Entry backendEntry = backend.getUser(username, 
schemaManager);
+
+                        if (backendEntry != null) {
+                            // Return backend result directly without caching
+                            entries.add(backendEntry);
+                        }
+                    }
+                } catch (Exception e) {
+                    LOG.ldapServiceStopFailed(e);
+                }
+            }
+
+            // Return cursor with our results - use a simple approach
+            return new EntryFilteringCursorImpl(new ListCursor<>(entries), 
ctx, directoryService.getSchemaManager());
+        }
+
+        return originalResults;
+    }
+
+    @Override
+    public void bind(BindOperationContext ctx) {
+        // Allow anonymous bind or simple bind
+        LOG.ldapBind(ctx.getDn() != null ? ctx.getDn().toString() : 
"anonymous");
+        try {
+            next(ctx);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private boolean isUserSearch(String filter) {
+        return UID_PATTERN.matcher(filter).matches() || 
CN_PATTERN.matcher(filter).matches();
+    }
+
+    private String extractUser(String filter) {
+        Matcher uidMatcher = UID_PATTERN.matcher(filter);
+        if (uidMatcher.matches()) {
+            return uidMatcher.group(1);
+        }
+
+        Matcher cnMatcher = CN_PATTERN.matcher(filter);
+        if (cnMatcher.matches()) {
+            return cnMatcher.group(1);
+        }
+
+        return null;
+    }
+}
+
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManager.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManager.java
new file mode 100644
index 000000000..849f8e3a9
--- /dev/null
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManager.java
@@ -0,0 +1,237 @@
+/*
+ * 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.knox.gateway.services.ldap;
+
+import org.apache.directory.api.ldap.model.entry.DefaultEntry;
+import org.apache.directory.api.ldap.model.entry.Entry;
+import org.apache.directory.api.ldap.model.name.Dn;
+import org.apache.directory.api.ldap.model.schema.SchemaManager;
+import org.apache.directory.api.ldap.schema.loader.JarLdifSchemaLoader;
+import org.apache.directory.api.ldap.schema.manager.impl.DefaultSchemaManager;
+import org.apache.directory.server.core.DefaultDirectoryService;
+import org.apache.directory.server.core.api.DirectoryService;
+import org.apache.directory.server.core.api.InstanceLayout;
+import org.apache.directory.server.core.api.schema.SchemaPartition;
+import 
org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmPartition;
+import org.apache.directory.server.core.partition.ldif.LdifPartition;
+import org.apache.directory.server.ldap.LdapServer;
+import org.apache.directory.server.protocol.shared.transport.TcpTransport;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.services.ldap.backend.BackendFactory;
+import org.apache.knox.gateway.services.ldap.backend.LdapBackend;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * Manages the ApacheDS LDAP server instance with pluggable backends
+ */
+public class KnoxLDAPServerManager {
+    private static final LdapMessages LOG = 
MessagesFactory.get(LdapMessages.class);
+
+    private DirectoryService directoryService;
+    private LdapServer ldapServer;
+    private LdapBackend backend;
+    private File workDir;
+    private int port;
+    private String baseDn;
+    private String remoteBaseDn;
+
+    /**
+     * Initialize the LDAP server with the given configuration
+     *
+     * @param workDir Directory for LDAP data storage
+     * @param port Port for LDAP server to listen on
+     * @param baseDn Base DN for LDAP entries in the proxy server
+     * @param backendType Type of backend to use
+     * @param backendConfig Backend-specific configuration
+     * @param remoteBaseDn Base DN of the remote LDAP server (for proxy 
backends)
+     */
+    public void initialize(File workDir, int port, String baseDn, String 
backendType, Map<String, String> backendConfig, String remoteBaseDn) throws 
Exception {
+        this.workDir = workDir;
+        this.port = port;
+        this.baseDn = baseDn;
+        this.remoteBaseDn = remoteBaseDn;
+
+        // Initialize backend
+        backendConfig.put("baseDn", baseDn);
+        backend = BackendFactory.createBackend(backendType, backendConfig);
+
+        // Clean up previous run if it didn't shut down cleanly
+        File lockFile = new File(workDir, "run/instance.lock");
+        if (lockFile.exists()) {
+            LOG.ldapCleaningLockFile(lockFile.getAbsolutePath());
+            lockFile.delete();
+        }
+
+        workDir.mkdirs();
+    }
+
+    /**
+     * Start the LDAP server
+     */
+    public void start() throws Exception {
+        LOG.ldapServiceStarting(port, baseDn);
+
+        // Initialize DirectoryService
+        directoryService = new DefaultDirectoryService();
+        directoryService.setInstanceLayout(new InstanceLayout(workDir));
+
+        // Create and load schema manager manually
+        JarLdifSchemaLoader loader = new JarLdifSchemaLoader();
+        SchemaManager schemaManager = new DefaultSchemaManager(loader);
+        schemaManager.loadAllEnabled();
+        directoryService.setSchemaManager(schemaManager);
+
+        // Initialize schema partition
+        LdifPartition schemaPartition = new LdifPartition(schemaManager, 
directoryService.getDnFactory());
+        schemaPartition.setPartitionPath(new File(workDir, "schema").toURI());
+        SchemaPartition schemaLdifPartition = new 
SchemaPartition(schemaManager);
+        schemaLdifPartition.setWrappedPartition(schemaPartition);
+        directoryService.setSchemaPartition(schemaLdifPartition);
+
+        // Create system partition (required)
+        JdbmPartition systemPartition = new JdbmPartition(schemaManager, 
directoryService.getDnFactory());
+        systemPartition.setId("system");
+        systemPartition.setSuffixDn(new Dn(schemaManager, "ou=system"));
+        systemPartition.setPartitionPath(new File(workDir, "system").toURI());
+        directoryService.setSystemPartition(systemPartition);
+
+        // Create our custom partition for proxy base DN
+        JdbmPartition partition = new JdbmPartition(schemaManager, 
directoryService.getDnFactory());
+        partition.setId("proxy");
+        partition.setSuffixDn(new Dn(schemaManager, baseDn));
+        partition.setPartitionPath(new File(workDir, "proxy").toURI());
+        directoryService.addPartition(partition);
+
+        // Create partition for remote base DN if different from proxy base DN
+        // This allows backend entries with remote DNs to be returned in 
search results
+        if (remoteBaseDn != null && !remoteBaseDn.equals(baseDn)) {
+            JdbmPartition remotePartition = new JdbmPartition(schemaManager, 
directoryService.getDnFactory());
+            remotePartition.setId("remote");
+            remotePartition.setSuffixDn(new Dn(schemaManager, remoteBaseDn));
+            remotePartition.setPartitionPath(new File(workDir, 
"remote").toURI());
+            directoryService.addPartition(remotePartition);
+        }
+
+        // Add our interceptor for group lookups
+        directoryService.addLast(new GroupLookupInterceptor(directoryService, 
backend));
+
+        // Allow anonymous access
+        directoryService.setAllowAnonymousAccess(true);
+
+        // Start the service
+        directoryService.startup();
+
+        // Add base entries to the partition
+        createBaseEntries(schemaManager);
+
+        // Create LDAP server on configured port
+        ldapServer = new LdapServer();
+        ldapServer.setTransports(new TcpTransport(port));
+        ldapServer.setDirectoryService(directoryService);
+
+        ldapServer.start();
+
+        LOG.ldapServiceStarted(port);
+    }
+
+    /**
+     * Stop the LDAP server
+     */
+    public void stop() throws Exception {
+        LOG.ldapServiceStopping(port);
+
+        if (ldapServer != null) {
+            try {
+                ldapServer.stop();
+            } catch (Exception e) {
+                LOG.ldapServiceStopFailed(e);
+            }
+        }
+
+        if (directoryService != null) {
+            try {
+                directoryService.shutdown();
+            } catch (Exception e) {
+                LOG.ldapServiceStopFailed(e);
+            }
+        }
+
+        LOG.ldapServiceStopped();
+    }
+
+    private void createBaseEntries(SchemaManager schemaManager) throws 
Exception {
+        // Create base entries for proxy base DN
+        createBaseEntriesForDn(schemaManager, baseDn);
+
+        // Create base entries for remote base DN if different
+        if (remoteBaseDn != null && !remoteBaseDn.equals(baseDn)) {
+            createBaseEntriesForDn(schemaManager, remoteBaseDn);
+        }
+    }
+
+    private void createBaseEntriesForDn(SchemaManager schemaManager, String 
dn) throws Exception {
+        Dn baseDnName = new Dn(schemaManager, dn);
+        if (!directoryService.getAdminSession().exists(baseDnName)) {
+            Entry baseDnEntry = new DefaultEntry(schemaManager);
+            baseDnEntry.setDn(baseDnName);
+            baseDnEntry.add("objectClass", "top", "domain");
+            // Extract dc value from baseDn (e.g., "dc=proxy,dc=com" -> 
"proxy")
+            String dcValue = dn.split(",")[0].split("=")[1];
+            baseDnEntry.add("dc", dcValue);
+            directoryService.getAdminSession().add(baseDnEntry);
+        }
+
+        Dn usersOuDn = new Dn(schemaManager, "ou=people," + dn);
+        if (!directoryService.getAdminSession().exists(usersOuDn)) {
+            Entry usersOu = new DefaultEntry(schemaManager);
+            usersOu.setDn(usersOuDn);
+            usersOu.add("objectClass", "top", "organizationalUnit");
+            usersOu.add("ou", "people");
+            directoryService.getAdminSession().add(usersOu);
+        }
+
+        Dn groupsOuDn = new Dn(schemaManager, "ou=groups," + dn);
+        if (!directoryService.getAdminSession().exists(groupsOuDn)) {
+            Entry groupsOu = new DefaultEntry(schemaManager);
+            groupsOu.setDn(groupsOuDn);
+            groupsOu.add("objectClass", "top", "organizationalUnit");
+            groupsOu.add("ou", "groups");
+            directoryService.getAdminSession().add(groupsOu);
+        }
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    public String getBaseDn() {
+        return baseDn;
+    }
+
+    /**
+     * Check if the LDAP server is currently running.
+     *
+     * @return true if the server is running, false otherwise
+     */
+    public boolean isRunning() {
+        return ldapServer != null && ldapServer.isStarted();
+    }
+
+}
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPService.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPService.java
new file mode 100644
index 000000000..1ec748da9
--- /dev/null
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPService.java
@@ -0,0 +1,130 @@
+/*
+ * 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.knox.gateway.services.ldap;
+
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.services.Service;
+import org.apache.knox.gateway.services.ServiceLifecycleException;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * Knox LDAP Service - provides an embedded LDAP server with pluggable backends
+ * for user and group lookups.
+ */
+public class KnoxLDAPService implements Service {
+    private static final LdapMessages LOG = 
MessagesFactory.get(LdapMessages.class);
+
+    private KnoxLDAPServerManager ldapServerManager;
+    private boolean enabled;
+
+    @Override
+    public void init(GatewayConfig config, Map<String, String> options) throws 
ServiceLifecycleException {
+        this.enabled = config.isLDAPEnabled();
+
+        if (!enabled) {
+            return;
+        }
+
+        try {
+            // Initialize the LDAP server manager with configuration
+            ldapServerManager = new KnoxLDAPServerManager();
+
+            // Prepare work directory for LDAP data
+            File gatewayDataDir = new File(config.getGatewayDataDir());
+            File ldapWorkDir = new File(gatewayDataDir, "ldap-server");
+
+            // Get configuration
+            int port = config.getLDAPPort();
+            String baseDn = config.getLDAPBaseDN();
+            String backendType = config.getLDAPBackendType();
+
+            // Get backend-specific configuration using prefixed properties
+            Map<String, String> backendConfig = 
config.getLDAPBackendConfig(backendType);
+
+            // Add common configuration
+            backendConfig.put("baseDn", baseDn);
+
+            // Add legacy dataFile property for backwards compatibility with 
file backend
+            if ("file".equalsIgnoreCase(backendType) && 
!backendConfig.containsKey("dataFile")) {
+                backendConfig.put("dataFile", config.getLDAPBackendDataFile());
+            }
+
+            // For proxy backends, extract remoteBaseDn if present
+            String remoteBaseDn = backendConfig.get("remoteBaseDn");
+
+            // Initialize but don't start yet
+            ldapServerManager.initialize(ldapWorkDir, port, baseDn, 
backendType, backendConfig, remoteBaseDn);
+
+        } catch (Exception e) {
+            throw new ServiceLifecycleException("Failed to initialize LDAP 
service", e);
+        }
+    }
+
+    @Override
+    public void start() throws ServiceLifecycleException {
+        if (!enabled) {
+            return;
+        }
+
+        try {
+            // Start the LDAP server
+            ldapServerManager.start();
+        } catch (Exception e) {
+            LOG.ldapServiceStartFailed(e);
+            throw new ServiceLifecycleException("Failed to start LDAP 
service", e);
+        }
+    }
+
+    @Override
+    public void stop() throws ServiceLifecycleException {
+        if (!enabled || ldapServerManager == null) {
+            return;
+        }
+
+        try {
+            ldapServerManager.stop();
+        } catch (Exception e) {
+            LOG.ldapServiceStopFailed(e);
+            throw new ServiceLifecycleException("Failed to stop LDAP service", 
e);
+        }
+    }
+
+    /**
+     * Get the port the LDAP server is listening on
+     */
+    public int getLdapPort() {
+        return ldapServerManager != null ? ldapServerManager.getPort() : -1;
+    }
+
+    /**
+     * Get the base DN for LDAP entries
+     */
+    public String getBaseDn() {
+        return ldapServerManager != null ? ldapServerManager.getBaseDn() : 
null;
+    }
+
+    /**
+     * Check if the LDAP service is enabled
+     */
+    public boolean isEnabled() {
+        return enabled;
+    }
+}
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapMessages.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapMessages.java
new file mode 100644
index 000000000..a03c0b689
--- /dev/null
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapMessages.java
@@ -0,0 +1,87 @@
+/*
+ * 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.knox.gateway.services.ldap;
+
+import org.apache.knox.gateway.i18n.messages.Message;
+import org.apache.knox.gateway.i18n.messages.MessageLevel;
+import org.apache.knox.gateway.i18n.messages.Messages;
+import org.apache.knox.gateway.i18n.messages.StackTrace;
+
+@Messages(logger = "org.apache.knox.gateway.services.ldap")
+public interface LdapMessages {
+
+    @Message(level = MessageLevel.INFO,
+            text = "Starting LDAP service on port {0} with base DN: {1}")
+    void ldapServiceStarting(int port, String baseDn);
+
+    @Message(level = MessageLevel.INFO,
+            text = "LDAP service started successfully on port {0}")
+    void ldapServiceStarted(int port);
+
+    @Message(level = MessageLevel.INFO,
+            text = "Stopping LDAP service on port {0}")
+    void ldapServiceStopping(int port);
+
+    @Message(level = MessageLevel.INFO,
+            text = "LDAP service stopped successfully")
+    void ldapServiceStopped();
+
+    @Message(level = MessageLevel.ERROR,
+            text = "Failed to start LDAP service: {0}")
+    void ldapServiceStartFailed(@StackTrace(level = MessageLevel.DEBUG) 
Exception e);
+
+    @Message(level = MessageLevel.ERROR,
+            text = "Failed to stop LDAP service: {0}")
+    void ldapServiceStopFailed(@StackTrace(level = MessageLevel.DEBUG) 
Exception e);
+
+    @Message(level = MessageLevel.INFO,
+            text = "Loading backend: {0} (via {1})")
+    void ldapBackendLoading(String backendName, String source);
+
+    @Message(level = MessageLevel.WARN,
+            text = "Backend ''{0}'' not found, using FileBackend")
+    void ldapBackendNotFound(String backendName);
+
+    @Message(level = MessageLevel.WARN,
+            text = "Data file not found: {0}, creating sample data")
+    void ldapDataFileNotFound(String dataFile);
+
+    @Message(level = MessageLevel.INFO,
+            text = "Loaded {0} users from {1}")
+    void ldapUsersLoaded(int count, String dataFile);
+
+    @Message(level = MessageLevel.INFO,
+            text = "Created sample data file: {0}")
+    void ldapSampleDataCreated(String path);
+
+    @Message(level = MessageLevel.DEBUG,
+            text = "LDAP Search: {0} | {1}")
+    void ldapSearch(String baseDn, String filter);
+
+    @Message(level = MessageLevel.DEBUG,
+            text = "LDAP Bind: {0}")
+    void ldapBind(String dn);
+
+    @Message(level = MessageLevel.INFO,
+            text = "Loaded user from backend: {0}")
+    void ldapUserLoaded(String username);
+
+    @Message(level = MessageLevel.INFO,
+            text = "Cleaning up old lock file: {0}")
+    void ldapCleaningLockFile(String lockFile);
+}
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/BackendFactory.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/BackendFactory.java
new file mode 100644
index 000000000..f432a6dc5
--- /dev/null
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/BackendFactory.java
@@ -0,0 +1,49 @@
+/*
+ * 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.knox.gateway.services.ldap.backend;
+
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.services.ldap.LdapMessages;
+
+import java.util.Map;
+import java.util.ServiceLoader;
+
+/**
+ * Factory for loading backend implementations using ServiceLoader for full 
extensibility.
+ * Backends are discovered via 
META-INF/services/org.apache.knox.gateway.services.ldap.backend.LdapBackend
+ * Built-in backends (file, ldap) are registered via ServiceLoader along with 
any external plugins.
+ */
+public class BackendFactory {
+    private static final LdapMessages LOG = 
MessagesFactory.get(LdapMessages.class);
+
+    public static LdapBackend createBackend(String backendName, Map<String, 
String> config) throws Exception {
+        // Use ServiceLoader to discover all available backends (built-in and 
external plugins)
+        ServiceLoader<LdapBackend> loader = 
ServiceLoader.load(LdapBackend.class);
+        for (LdapBackend backend : loader) {
+            if (backend.getName().equalsIgnoreCase(backendName)) {
+                LOG.ldapBackendLoading(backend.getName(), "ServiceLoader");
+                backend.initialize(config);
+                return backend;
+            }
+        }
+
+        // No matching backend found
+        LOG.ldapBackendNotFound(backendName);
+        throw new IllegalArgumentException("No LDAP backend found for type: " 
+ backendName);
+    }
+}
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/FileBackend.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/FileBackend.java
new file mode 100644
index 000000000..b0cdcd860
--- /dev/null
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/FileBackend.java
@@ -0,0 +1,144 @@
+/*
+ * 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.knox.gateway.services.ldap.backend;
+
+import com.google.gson.Gson;
+import org.apache.directory.api.ldap.model.entry.DefaultEntry;
+import org.apache.directory.api.ldap.model.entry.Entry;
+import org.apache.directory.api.ldap.model.schema.SchemaManager;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.services.ldap.LdapMessages;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * File-based backend that reads user/group data from JSON
+ */
+public class FileBackend implements LdapBackend {
+    private static final LdapMessages LOG = 
MessagesFactory.get(LdapMessages.class);
+
+    private Map<String, UserData> users = new HashMap<>();
+    private String dataFile;
+    private String baseDn;
+
+    static class UserData {
+        String username;
+        String cn;
+        String sn;
+        List<String> groups;
+        Map<String, String> attributes;
+    }
+
+    static class BackendData {
+        List<UserData> users;
+    }
+
+    @Override
+    public String getName() {
+        return "file";
+    }
+
+    @Override
+    public void initialize(Map<String, String> config) throws Exception {
+        dataFile = config.getOrDefault("dataFile", "ldap-users.json");
+        baseDn = config.getOrDefault("baseDn", "dc=proxy,dc=com");
+        loadData();
+    }
+
+    private void loadData() throws Exception {
+        Path path = Paths.get(dataFile);
+
+        if (!Files.exists(path)) {
+            LOG.ldapDataFileNotFound(dataFile);
+            throw new Exception("LDAP data file not found: " + dataFile + ". 
Please create the file with user data before starting the service.");
+        }
+
+        String json = Files.readString(path);
+        Gson gson = new Gson();
+        BackendData data = gson.fromJson(json, BackendData.class);
+
+        if (data != null && data.users != null) {
+            for (UserData user : data.users) {
+                users.put(user.username, user);
+            }
+            LOG.ldapUsersLoaded(users.size(), dataFile);
+        }
+    }
+
+    @Override
+    public Entry getUser(String username, SchemaManager schemaManager) throws 
Exception {
+        UserData userData = users.get(username);
+        if (userData == null) {
+            return null;
+        }
+
+        Entry entry = new DefaultEntry(schemaManager);
+        entry.setDn("uid=" + userData.username + ",ou=Users," + baseDn);
+        entry.add("objectClass", "top");
+        entry.add("objectClass", "person");
+        entry.add("objectClass", "organizationalPerson");
+        entry.add("objectClass", "inetOrgPerson");
+        entry.add("uid", userData.username);
+        entry.add("cn", userData.cn);
+        entry.add("sn", userData.sn);
+
+        // Add groups as description
+        if (userData.groups != null && !userData.groups.isEmpty()) {
+            entry.add("description", "Groups: " + String.join(", ", 
userData.groups));
+        }
+
+        // Add custom attributes
+        if (userData.attributes != null) {
+            for (Map.Entry<String, String> attr : 
userData.attributes.entrySet()) {
+                entry.add(attr.getKey(), attr.getValue());
+            }
+        }
+
+        return entry;
+    }
+
+    @Override
+    public List<String> getUserGroups(String username) throws Exception {
+        UserData userData = users.get(username);
+        return userData != null && userData.groups != null ? userData.groups : 
Collections.emptyList();
+    }
+
+    @Override
+    public List<Entry> searchUsers(String filter, SchemaManager schemaManager) 
throws Exception {
+        List<Entry> results = new ArrayList<>();
+
+        // Simple filter matching - just check if username matches
+        for (String username : users.keySet()) {
+            if (filter.contains("uid=" + username) || filter.contains("*")) {
+                Entry entry = getUser(username, schemaManager);
+                if (entry != null) {
+                    results.add(entry);
+                }
+            }
+        }
+
+        return results;
+    }
+}
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapBackend.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapBackend.java
new file mode 100644
index 000000000..6530c13b3
--- /dev/null
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapBackend.java
@@ -0,0 +1,68 @@
+/*
+ * 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.knox.gateway.services.ldap.backend;
+
+import org.apache.directory.api.ldap.model.entry.Entry;
+import org.apache.directory.api.ldap.model.schema.SchemaManager;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Interface for pluggable LDAP backends.
+ * Implementations can provide user/group data from various sources:
+ * - File-based (JSON, LDIF, properties)
+ * - JDBC databases
+ * - Remote LDAP servers (proxy/federation)
+ * - REST APIs (Knox, Ranger, etc.)
+ */
+public interface LdapBackend {
+    /**
+     * Get the name of this backend implementation
+     */
+    String getName();
+
+    /**
+     * Initialize the backend with configuration
+     * @param config Configuration properties
+     */
+    void initialize(Map<String, String> config) throws Exception;
+
+    /**
+     * Get a user entry by username
+     * @param username The username to look up
+     * @param schemaManager Schema manager for creating entries
+     * @return Entry or null if not found
+     */
+    Entry getUser(String username, SchemaManager schemaManager) throws 
Exception;
+
+    /**
+     * Get groups for a user
+     * @param username The username
+     * @return List of group names
+     */
+    List<String> getUserGroups(String username) throws Exception;
+
+    /**
+     * Search for users matching a filter
+     * @param filter LDAP filter string (simplified)
+     * @param schemaManager Schema manager for creating entries
+     * @return List of matching entries
+     */
+    List<Entry> searchUsers(String filter, SchemaManager schemaManager) throws 
Exception;
+}
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackend.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackend.java
new file mode 100644
index 000000000..2c37cb135
--- /dev/null
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackend.java
@@ -0,0 +1,452 @@
+/*
+ * 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.knox.gateway.services.ldap.backend;
+
+import org.apache.directory.api.ldap.model.cursor.CursorException;
+import org.apache.directory.api.ldap.model.cursor.EntryCursor;
+import org.apache.directory.api.ldap.model.entry.Attribute;
+import org.apache.directory.api.ldap.model.entry.DefaultEntry;
+import org.apache.directory.api.ldap.model.entry.Entry;
+import org.apache.directory.api.ldap.model.exception.LdapException;
+import org.apache.directory.api.ldap.model.message.SearchScope;
+import org.apache.directory.api.ldap.model.schema.SchemaManager;
+import org.apache.directory.ldap.client.api.DefaultLdapConnectionFactory;
+import org.apache.directory.ldap.client.api.LdapConnection;
+import org.apache.directory.ldap.client.api.LdapConnectionConfig;
+import org.apache.directory.ldap.client.api.LdapConnectionPool;
+import 
org.apache.directory.ldap.client.api.ValidatingPoolableLdapConnectionFactory;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.services.ldap.LdapMessages;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * LDAP backend that proxies to an external LDAP server.
+ * Can use central LDAP configuration or backend-specific configuration.
+ */
+public class LdapProxyBackend implements LdapBackend {
+    private static final LdapMessages LOG = 
MessagesFactory.get(LdapMessages.class);
+
+    private String ldapUrl;
+    private String bindDn;
+    private String bindPassword;
+    private String userSearchBase;
+    private String groupSearchBase;
+    private String proxyBaseDn;  // Base DN for proxy entries (e.g., 
dc=proxy,dc=com)
+    private String remoteBaseDn;  // Base DN for remote server searches (e.g., 
dc=hadoop,dc=apache,dc=org)
+    private int port;
+    private String host;
+
+    // Configurable attributes for AD/LDAP compatibility
+    private String userIdentifierAttribute = "uid"; // uid for LDAP, 
sAMAccountName for AD
+    private String userSearchFilter = "({userIdAttr}={username})"; // Will be 
populated with userIdentifierAttribute
+    private String groupMemberAttribute = "memberUid"; // member for AD, 
memberUid for POSIX
+    private boolean useMemberOf; // Use memberOf attribute for group lookup 
(efficient for AD)
+
+    // Connection pool for efficient connection reuse
+    private LdapConnectionPool connectionPool;
+
+    @Override
+    public String getName() {
+        return "ldap";
+    }
+
+    @Override
+    public void initialize(Map<String, String> config) throws Exception {
+        // Proxy base DN is for entries created in the proxy LDAP server
+        proxyBaseDn = config.get("baseDn");
+        if (proxyBaseDn == null || proxyBaseDn.isEmpty()) {
+            throw new IllegalArgumentException("baseDn is required for LDAP 
proxy backend");
+        }
+
+        // Remote base DN is for searching the remote LDAP server
+        remoteBaseDn = config.get("remoteBaseDn");
+        if (remoteBaseDn == null || remoteBaseDn.isEmpty()) {
+            throw new IllegalArgumentException("remoteBaseDn is required for 
LDAP proxy backend - this is the base DN of the remote LDAP server");
+        }
+
+        // Support both url and host/port configuration
+        ldapUrl = config.get("url");
+        if (ldapUrl != null && !ldapUrl.isEmpty()) {
+            // Parse URL to extract host and port
+            parseLdapUrl(ldapUrl);
+        } else {
+            host = config.get("host");
+            if (host == null || host.isEmpty()) {
+                throw new IllegalArgumentException("Either 'url' or 'host' is 
required for LDAP proxy backend");
+            }
+            String portStr = config.get("port");
+            if (portStr == null || portStr.isEmpty()) {
+                throw new IllegalArgumentException("'port' is required when 
using 'host' configuration");
+            }
+            port = Integer.parseInt(portStr);
+            ldapUrl = "ldap://"; + host + ":" + port;
+        }
+
+        // Support both naming conventions: bindDn/bindPassword and 
systemUsername/systemPassword
+        bindDn = config.get("bindDn");
+        if (bindDn == null || bindDn.isEmpty()) {
+            bindDn = config.get("systemUsername");
+        }
+
+        bindPassword = config.get("bindPassword");
+        if (bindPassword == null || bindPassword.isEmpty()) {
+            bindPassword = config.get("systemPassword");
+        }
+
+        // Search bases use the remote server's base DN
+        userSearchBase = config.getOrDefault("userSearchBase", "ou=people," + 
remoteBaseDn);
+        groupSearchBase = config.getOrDefault("groupSearchBase", "ou=groups," 
+ remoteBaseDn);
+
+        // Configure attribute mappings for AD/LDAP compatibility
+        userIdentifierAttribute = 
config.getOrDefault("userIdentifierAttribute", "uid");
+        config.getOrDefault("userDnTemplate", 
"uid={username},ou=Users,{baseDn}");
+        groupMemberAttribute = config.getOrDefault("groupMemberAttribute", 
"memberUid");
+        useMemberOf = Boolean.parseBoolean(config.getOrDefault("useMemberOf", 
"false"));
+
+        // Build search filter template
+        userSearchFilter = "(" + userIdentifierAttribute + "={username})";
+
+        LOG.ldapBackendLoading(getName(), "Proxying " + proxyBaseDn + " to " + 
ldapUrl + " (" + remoteBaseDn + ") with " +
+                              userIdentifierAttribute + " attribute" +
+                              (useMemberOf ? " using memberOf lookups" : " 
using group searches"));
+
+        // Initialize connection pool
+        initializeConnectionPool(config);
+    }
+
+    /**
+     * Initializes the LDAP connection pool with configurable parameters.
+     * Uses a validating pool to ensure connections remain healthy.
+     *
+     * @param config Configuration map that may contain pool settings
+     * @throws Exception if connection pool initialization fails
+     */
+    private void initializeConnectionPool(Map<String, String> config) throws 
Exception {
+        // Configure connection settings
+        LdapConnectionConfig connectionConfig = new LdapConnectionConfig();
+        connectionConfig.setLdapHost(host);
+        connectionConfig.setLdapPort(port);
+
+        if (bindDn != null && !bindDn.isEmpty()) {
+            connectionConfig.setName(bindDn);
+            connectionConfig.setCredentials(bindPassword);
+        }
+
+        // Connection pool configuration (with sensible defaults)
+        int maxActive = Integer.parseInt(config.getOrDefault("poolMaxActive", 
"8"));
+
+        // Create connection factory
+        DefaultLdapConnectionFactory factory = new 
DefaultLdapConnectionFactory(connectionConfig);
+
+        // Create validating poolable connection factory to test connections
+        ValidatingPoolableLdapConnectionFactory poolFactory = new 
ValidatingPoolableLdapConnectionFactory(factory);
+
+        // Create the pool with max size
+        connectionPool = new LdapConnectionPool(poolFactory);
+        connectionPool.setMaxTotal(maxActive);
+        connectionPool.setTestOnBorrow(true);
+
+        LOG.ldapBackendLoading(getName(), "Initialized connection pool with 
maxActive=" + maxActive);
+    }
+
+    private void parseLdapUrl(String url) {
+        // Simple URL parsing for ldap://host:port
+        if (url.startsWith("ldap://";)) {
+            String hostPort = url.substring(7);
+            int colonIdx = hostPort.indexOf(':');
+            if (colonIdx > 0) {
+                host = hostPort.substring(0, colonIdx);
+                try {
+                    port = Integer.parseInt(hostPort.substring(colonIdx + 1));
+                } catch (NumberFormatException e) {
+                    port = 389;
+                }
+            } else {
+                host = hostPort;
+                port = 389;
+            }
+        } else if (url.startsWith("ldaps://")) {
+            String hostPort = url.substring(8);
+            int colonIdx = hostPort.indexOf(':');
+            if (colonIdx > 0) {
+                host = hostPort.substring(0, colonIdx);
+                try {
+                    port = Integer.parseInt(hostPort.substring(colonIdx + 1));
+                } catch (NumberFormatException e) {
+                    port = 636;
+                }
+            } else {
+                host = hostPort;
+                port = 636;
+            }
+        }
+    }
+
+    /**
+     * Gets a connection from the connection pool.
+     * Connections obtained from this method should be released back to the 
pool
+     * using releaseConnection() when done.
+     *
+     * @return An LDAP connection from the pool
+     * @throws Exception if unable to get a connection from the pool
+     */
+    private LdapConnection getConnection() throws Exception {
+        return connectionPool.getConnection();
+    }
+
+    /**
+     * Releases a connection back to the pool.
+     * This method should be called in a finally block to ensure connections 
are returned.
+     *
+     * @param connection The connection to release back to the pool
+     */
+    private void releaseConnection(LdapConnection connection) {
+        if (connection != null) {
+            try {
+                connectionPool.releaseConnection(connection);
+            } catch (Exception e) {
+                LOG.ldapServiceStopFailed(e);
+            }
+        }
+    }
+
+    /**
+     * Closes the connection pool and releases all resources.
+     * Should be called when the backend is being shut down.
+     */
+    public void close() {
+        if (connectionPool != null) {
+            try {
+                connectionPool.close();
+            } catch (Exception e) {
+                LOG.ldapServiceStopFailed(e);
+            }
+        }
+    }
+
+    @Override
+    public Entry getUser(String username, SchemaManager schemaManager) throws 
Exception {
+        LdapConnection connection = null;
+        try {
+            connection = getConnection();
+            // Search for user using configurable attribute
+            String filter = userSearchFilter.replace("{username}", username);
+            EntryCursor cursor = connection.search(userSearchBase, filter, 
SearchScope.SUBTREE, "*");
+
+            if (cursor.next()) {
+                Entry sourceEntry = cursor.get();
+                Entry entry = createProxyEntry(sourceEntry, username, 
connection, schemaManager);
+                cursor.close();
+                return entry;
+            }
+
+            cursor.close();
+            return null;
+        } finally {
+            releaseConnection(connection);
+        }
+    }
+
+    @Override
+    public List<String> getUserGroups(String username) throws Exception {
+        LdapConnection connection = null;
+        try {
+            connection = getConnection();
+            if (useMemberOf) {
+                // Use memberOf attribute for efficient AD lookups
+                return getUserGroupsViaMemberOf(connection, username);
+            } else {
+                // Use traditional group search approach
+                String filter = userSearchFilter.replace("{username}", 
username);
+                EntryCursor cursor = connection.search(userSearchBase, filter, 
SearchScope.SUBTREE, "dn");
+
+                if (cursor.next()) {
+                    String userDn = cursor.get().getDn().toString();
+                    cursor.close();
+                    return getUserGroupsInternal(connection, userDn, username);
+                }
+
+                cursor.close();
+            }
+            return Collections.emptyList();
+        } finally {
+            releaseConnection(connection);
+        }
+    }
+
+    private List<String> getUserGroupsViaMemberOf(LdapConnection connection, 
String username) throws LdapException, CursorException, IOException {
+        List<String> groups = new ArrayList<>();
+
+        // Search for user and retrieve memberOf attribute
+        String filter = userSearchFilter.replace("{username}", username);
+        EntryCursor cursor = connection.search(userSearchBase, filter, 
SearchScope.SUBTREE, "memberOf");
+
+        if (cursor.next()) {
+            Entry userEntry = cursor.get();
+            Attribute memberOfAttr = userEntry.get("memberOf");
+
+            if (memberOfAttr != null) {
+                // Extract group names from DNs
+                for (org.apache.directory.api.ldap.model.entry.Value value : 
memberOfAttr) {
+                    String groupDn = value.getString();
+                    String groupName = extractGroupNameFromDn(groupDn);
+                    if (groupName != null) {
+                        groups.add(groupName);
+                    }
+                }
+            }
+        }
+
+        cursor.close();
+        return groups;
+    }
+
+    private String extractGroupNameFromDn(String groupDn) {
+        // Extract CN from DN like "CN=Domain 
Admins,CN=Users,DC=company,DC=com"
+        if (groupDn.toLowerCase(Locale.ROOT).startsWith("cn=")) {
+            int commaIdx = groupDn.indexOf(',');
+            if (commaIdx > 0) {
+                return groupDn.substring(3, commaIdx);
+            }
+        }
+        return null;
+    }
+
+    private List<String> getUserGroupsInternal(LdapConnection connection, 
String userDn, String username) throws LdapException, CursorException, 
IOException {
+        List<String> groups = new ArrayList<>();
+
+        // Search for groups where user is a member - build filter based on 
configuration
+        String filter;
+        if ("member".equals(groupMemberAttribute)) {
+            // AD style - uses full DN
+            filter = "(|" +
+                    "(member=" + userDn + ")" +
+                    "(uniqueMember=" + userDn + ")" +
+                    ")";
+        } else {
+            // POSIX style - uses username
+            filter = "(|" +
+                    "(memberUid=" + username + ")" +
+                    "(member=" + userDn + ")" +
+                    "(uniqueMember=" + userDn + ")" +
+                    ")";
+        }
+
+        EntryCursor cursor = connection.search(groupSearchBase, filter, 
SearchScope.SUBTREE, "cn");
+
+        while (cursor.next()) {
+            Entry groupEntry = cursor.get();
+            Attribute cnAttr = groupEntry.get("cn");
+            if (cnAttr != null) {
+                groups.add(cnAttr.getString());
+            }
+        }
+
+        cursor.close();
+        return groups;
+    }
+
+    @Override
+    public List<Entry> searchUsers(String filter, SchemaManager schemaManager) 
throws Exception {
+        List<Entry> results = new ArrayList<>();
+        LdapConnection connection = null;
+
+        try {
+            connection = getConnection();
+            String searchValue = filter.contains("*") ? "*" : filter;
+            String ldapFilter = "(" + userIdentifierAttribute + "=" + 
searchValue + ")";
+            EntryCursor cursor = connection.search(userSearchBase, ldapFilter, 
SearchScope.SUBTREE, "*");
+
+            while (cursor.next()) {
+                Entry sourceEntry = cursor.get();
+                Attribute idAttr = sourceEntry.get(userIdentifierAttribute);
+                if (idAttr != null) {
+                    String username = idAttr.getString();
+                    Entry entry = createProxyEntry(sourceEntry, username, 
connection, schemaManager);
+                    results.add(entry);
+                }
+            }
+
+            cursor.close();
+            return results;
+        } finally {
+            releaseConnection(connection);
+        }
+    }
+
+    /**
+     * Creates a proxy entry from a backend source entry with all required 
attributes.
+     * This method standardizes the conversion of backend LDAP entries to 
proxy entries,
+     * preserving the backend DN and copying all standard user attributes.
+     *
+     * @param sourceEntry The entry from the backend LDAP server
+     * @param username The username for the entry
+     * @param connection The LDAP connection for fetching group information
+     * @param schemaManager The schema manager for creating entries
+     * @return A new Entry with backend DN and all copied attributes
+     * @throws Exception if entry creation or attribute copying fails
+     */
+    private Entry createProxyEntry(Entry sourceEntry, String username, 
LdapConnection connection, SchemaManager schemaManager) throws Exception {
+        // Standard proxy approach: return entry with backend DN unchanged
+        // This preserves DN integrity for bind operations and DN references
+        Entry entry = new DefaultEntry(schemaManager);
+        entry.setDn(sourceEntry.getDn());
+
+        // Copy all attributes as-is from backend
+        copyAttribute(sourceEntry, entry, "objectClass");
+        copyAttribute(sourceEntry, entry, userIdentifierAttribute);
+
+        // Map identifier attribute to uid for consistency if needed
+        if (!"uid".equals(userIdentifierAttribute)) {
+            Attribute idAttr = sourceEntry.get(userIdentifierAttribute);
+            if (idAttr != null) {
+                entry.add("uid", idAttr.getString());
+            }
+        }
+
+        copyAttribute(sourceEntry, entry, "cn");
+        copyAttribute(sourceEntry, entry, "sn");
+        copyAttribute(sourceEntry, entry, "mail");
+        copyAttribute(sourceEntry, entry, "description");
+        copyAttribute(sourceEntry, entry, "memberOf");  // Preserve group 
memberships
+
+        // Get user's groups
+        List<String> groups = getUserGroupsInternal(connection, 
sourceEntry.getDn().toString(), username);
+        if (!groups.isEmpty()) {
+            entry.add("description", "Groups: " + String.join(", ", groups));
+        }
+
+        return entry;
+    }
+
+    private void copyAttribute(Entry source, Entry target, String 
attributeName) throws LdapException {
+        Attribute attr = source.get(attributeName);
+        if (attr != null) {
+            // Copy all values of the attribute (important for multi-valued 
attributes like objectClass)
+            for (org.apache.directory.api.ldap.model.entry.Value value : attr) 
{
+                target.add(attributeName, value.getString());
+            }
+        }
+    }
+}
diff --git 
a/gateway-server/src/main/resources/META-INF/services/org.apache.knox.gateway.services.ldap.backend.LdapBackend
 
b/gateway-server/src/main/resources/META-INF/services/org.apache.knox.gateway.services.ldap.backend.LdapBackend
new file mode 100644
index 000000000..c8bd82de6
--- /dev/null
+++ 
b/gateway-server/src/main/resources/META-INF/services/org.apache.knox.gateway.services.ldap.backend.LdapBackend
@@ -0,0 +1,21 @@
+##########################################################################
+# 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.
+##########################################################################
+
+# Built-in LDAP backend implementations
+org.apache.knox.gateway.services.ldap.backend.FileBackend
+org.apache.knox.gateway.services.ldap.backend.LdapProxyBackend
\ No newline at end of file
diff --git a/gateway-server/src/main/resources/conf/gateway-site.xml 
b/gateway-server/src/main/resources/conf/gateway-site.xml
index ff5293b56..9669a1941 100644
--- a/gateway-server/src/main/resources/conf/gateway-site.xml
+++ b/gateway-server/src/main/resources/conf/gateway-site.xml
@@ -37,4 +37,185 @@ limitations under the License.
         <description>The directory within GATEWAY_HOME that contains gateway 
topology files and deployments.</description>
     </property>
 
+    <!-- LDAP Proxy Service Configuration -->
+    <property>
+        <name>gateway.ldap.enabled</name>
+        <value>true</value>
+        <description>Enable the embedded LDAP service for user and group 
lookups. Set to true to enable.</description>
+    </property>
+
+    <property>
+        <name>gateway.ldap.port</name>
+        <value>3890</value>
+        <description>Port for the LDAP service to listen on. Default is 
3890.</description>
+    </property>
+
+    <property>
+        <name>gateway.ldap.base.dn</name>
+        <value>dc=proxy,dc=com</value>
+        <description>Base DN for LDAP entries in the proxy server. Default is 
dc=proxy,dc=com.</description>
+    </property>
+
+    <property>
+        <name>gateway.ldap.backend.type</name>
+        <value>ldap</value>
+        <description>Backend type for LDAP service. Currently supported: file, 
ldap. Future: jdbc, knox.</description>
+    </property>
+
+    <property>
+        <name>gateway.ldap.backend.data.file</name>
+        <value>${GATEWAY_DATA_HOME}/ldap-users.json</value>
+        <description>Path to JSON data file for file-based backend. Supports 
${GATEWAY_DATA_HOME} variable.</description>
+    </property>
+
+    <!-- LDAP backend proxy configuration (active when 
gateway.ldap.backend.type=ldap) -->
+    <property>
+        <name>gateway.ldap.backend.ldap.url</name>
+        <value>ldap://localhost:33389</value>
+        <description>LDAP server URL for proxy backend</description>
+    </property>
+    <property>
+        <name>gateway.ldap.backend.ldap.remoteBaseDn</name>
+        <value>dc=hadoop,dc=apache,dc=org</value>
+        <description>Base DN of the remote LDAP server</description>
+    </property>
+    <property>
+        <name>gateway.ldap.backend.ldap.systemUsername</name>
+        <value>uid=guest,ou=people,dc=hadoop,dc=apache,dc=org</value>
+        <description>LDAP bind DN for proxy backend 
authentication</description>
+    </property>
+    <property>
+        <name>gateway.ldap.backend.ldap.systemPassword</name>
+        <value>guest-password</value>
+        <description>LDAP bind password for proxy backend 
authentication</description>
+    </property>
+
+    <!-- Backend-specific configuration using prefixed properties -->
+    <!-- Uncomment and configure based on backend type specified in 
gateway.ldap.backend.type -->
+
+    <!-- File backend configuration (gateway.ldap.backend.type=file) -->
+    <!--
+    <property>
+        <name>gateway.ldap.backend.file.dataFile</name>
+        <value>${GATEWAY_DATA_HOME}/ldap-users.json</value>
+        <description>Path to JSON file containing user and group 
data</description>
+    </property>
+    -->
+
+    <!-- LDAP proxy backend configuration (gateway.ldap.backend.type=ldap) -->
+    <!-- This backend proxies to an external LDAP server (e.g., demo LDAP) -->
+    <!--
+    Example 1: Using Knox demo LDAP server (default port 33389)
+    <property>
+        <name>gateway.ldap.backend.ldap.url</name>
+        <value>ldap://localhost:33389</value>
+        <description>LDAP server URL</description>
+    </property>
+    <property>
+        <name>gateway.ldap.backend.ldap.remoteBaseDn</name>
+        <value>dc=hadoop,dc=apache,dc=org</value>
+        <description>Base DN of the remote LDAP server</description>
+    </property>
+    <property>
+        <name>gateway.ldap.backend.ldap.systemUsername</name>
+        <value>uid=guest,ou=people,dc=hadoop,dc=apache,dc=org</value>
+        <description>LDAP bind DN for authentication</description>
+    </property>
+    <property>
+        <name>gateway.ldap.backend.ldap.systemPassword</name>
+        <value>guest-password</value>
+        <description>LDAP bind password</description>
+    </property>
+    Note: Entries from the remote server will be re-created under 
gateway.ldap.base.dn (e.g., dc=proxy,dc=com)
+    -->
+    <!--
+    Example 2: Using external LDAP with authentication (supports both naming 
conventions)
+    <property>
+        <name>gateway.ldap.backend.ldap.url</name>
+        <value>ldap://ldap.example.com:389</value>
+        <description>LDAP server URL</description>
+    </property>
+    <property>
+        <name>gateway.ldap.backend.ldap.remoteBaseDn</name>
+        <value>dc=example,dc=com</value>
+        <description>Base DN of the remote LDAP server</description>
+    </property>
+    <property>
+        <name>gateway.ldap.backend.ldap.systemUsername</name>
+        <value>cn=admin,dc=example,dc=com</value>
+        <description>LDAP bind DN for authentication (or use 
bindDn)</description>
+    </property>
+    <property>
+        <name>gateway.ldap.backend.ldap.systemPassword</name>
+        <value>secret</value>
+        <description>LDAP bind password (or use bindPassword)</description>
+    </property>
+    <property>
+        <name>gateway.ldap.backend.ldap.userSearchBase</name>
+        <value>ou=people,dc=example,dc=com</value>
+        <description>Base DN for user searches on remote server (defaults to 
ou=people,{remoteBaseDn})</description>
+    </property>
+    <property>
+        <name>gateway.ldap.backend.ldap.groupSearchBase</name>
+        <value>ou=groups,dc=example,dc=com</value>
+        <description>Base DN for group searches on remote server (defaults to 
ou=groups,{remoteBaseDn})</description>
+    </property>
+    -->
+    <!--
+    Alternative: Use host and port instead of URL
+    <property>
+        <name>gateway.ldap.backend.ldap.host</name>
+        <value>localhost</value>
+        <description>LDAP server hostname</description>
+    </property>
+    <property>
+        <name>gateway.ldap.backend.ldap.port</name>
+        <value>33389</value>
+        <description>LDAP server port</description>
+    </property>
+    -->
+
+    <!-- Database backend configuration (gateway.ldap.backend.type=database) 
-->
+    <!--
+    <property>
+        <name>gateway.ldap.backend.database.jdbc-url</name>
+        <value>jdbc:mysql://localhost:3306/knox</value>
+        <description>JDBC connection URL</description>
+    </property>
+    <property>
+        <name>gateway.ldap.backend.database.driver</name>
+        <value>com.mysql.cj.jdbc.Driver</value>
+        <description>JDBC driver class name</description>
+    </property>
+    <property>
+        <name>gateway.ldap.backend.database.username</name>
+        <value>knox_user</value>
+        <description>Database username</description>
+    </property>
+    <property>
+        <name>gateway.ldap.backend.database.password</name>
+        <value>secret</value>
+        <description>Database password</description>
+    </property>
+    -->
+
+    <!-- Knox Auth backend configuration (gateway.ldap.backend.type=knox-auth) 
-->
+    <!--
+    <property>
+        <name>gateway.ldap.backend.knox-auth.url</name>
+        <value>https://knox-server/gateway/sandbox/knoxauth/api</value>
+        <description>Knox authentication service URL</description>
+    </property>
+    <property>
+        <name>gateway.ldap.backend.knox-auth.username</name>
+        <value>admin</value>
+        <description>Knox admin username</description>
+    </property>
+    <property>
+        <name>gateway.ldap.backend.knox-auth.password</name>
+        <value>admin-password</value>
+        <description>Knox admin password</description>
+    </property>
+    -->
+
 </configuration>
\ No newline at end of file
diff --git 
a/gateway-server/src/test/java/org/apache/knox/gateway/services/AbstractGatewayServicesTest.java
 
b/gateway-server/src/test/java/org/apache/knox/gateway/services/AbstractGatewayServicesTest.java
index f38bc8b27..b27358c7c 100644
--- 
a/gateway-server/src/test/java/org/apache/knox/gateway/services/AbstractGatewayServicesTest.java
+++ 
b/gateway-server/src/test/java/org/apache/knox/gateway/services/AbstractGatewayServicesTest.java
@@ -65,7 +65,8 @@ public class AbstractGatewayServicesTest {
         ServiceType.SERVICE_REGISTRY_SERVICE,
         ServiceType.CONCURRENT_SESSION_VERIFIER,
         ServiceType.REMOTE_CONFIGURATION_MONITOR,
-        ServiceType.GATEWAY_STATUS_SERVICE
+        ServiceType.GATEWAY_STATUS_SERVICE,
+        ServiceType.LDAP_SERVICE
     };
 
     assertNotEquals(ServiceType.values(), orderedServiceTypes);
diff --git 
a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManagerTest.java
 
b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManagerTest.java
new file mode 100644
index 000000000..a9450a96f
--- /dev/null
+++ 
b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManagerTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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.knox.gateway.services.ldap;
+
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.After;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+/**
+ * Unit tests for KnoxLDAPServerManager.
+ */
+public class KnoxLDAPServerManagerTest {
+
+    private KnoxLDAPServerManager serverManager;
+    private File tempWorkDir;
+    private File tempLdapFile;
+
+    @Before
+    public void setUp() throws Exception {
+        serverManager = new KnoxLDAPServerManager();
+
+        // Create temporary work directory
+        tempWorkDir = File.createTempFile("knox-ldap-work", "");
+        tempWorkDir.delete();
+        tempWorkDir.mkdirs();
+        tempWorkDir.deleteOnExit();
+
+        // Create temporary LDAP data file
+        tempLdapFile = File.createTempFile("ldap-test", ".json");
+        tempLdapFile.deleteOnExit();
+
+        try (java.io.BufferedWriter writer = 
java.nio.file.Files.newBufferedWriter(tempLdapFile.toPath(), 
java.nio.charset.StandardCharsets.UTF_8)) {
+            
writer.write("{\"users\":[{\"dn\":\"uid=admin,ou=people,dc=test,dc=com\",\"uid\":\"admin\",\"cn\":\"Administrator\",\"userPassword\":\"admin-password\"}],\"groups\":[]}");
+        }
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (serverManager != null) {
+            try {
+                serverManager.stop();
+            } catch (Exception e) {
+                // Ignore cleanup errors
+            }
+        }
+        cleanupTempFiles();
+    }
+
+    @Test
+    public void testInitializeWithFileBackend() throws Exception {
+        Map<String, String> backendConfig = createFileBackendConfig();
+
+        serverManager.initialize(tempWorkDir, 3890, "dc=test,dc=com", "file", 
backendConfig, null);
+
+        assertEquals("Port should be set correctly", 3890, 
serverManager.getPort());
+        assertEquals("Base DN should be set correctly", "dc=test,dc=com", 
serverManager.getBaseDn());
+        assertFalse("Should not be running after initialize", 
serverManager.isRunning());
+    }
+
+    @Test
+    public void testInitializeWithLdapBackend() throws Exception {
+        Map<String, String> backendConfig = createLdapBackendConfig();
+
+        serverManager.initialize(tempWorkDir, 3891, "dc=proxy,dc=com", "ldap", 
backendConfig, "dc=hadoop,dc=apache,dc=org");
+
+        assertEquals("Port should be set correctly", 3891, 
serverManager.getPort());
+        assertEquals("Base DN should be set correctly", "dc=proxy,dc=com", 
serverManager.getBaseDn());
+        assertFalse("Should not be running after initialize", 
serverManager.isRunning());
+    }
+
+    @Test(expected = Exception.class)
+    public void testInitializeWithInvalidBackendType() throws Exception {
+        Map<String, String> backendConfig = new HashMap<>();
+        backendConfig.put("baseDn", "dc=test,dc=com");
+
+        serverManager.initialize(tempWorkDir, 3890, "dc=test,dc=com", 
"invalid", backendConfig, null);
+    }
+
+    @Test
+    public void testLockFileCleanup() throws Exception {
+        // Create a lock file to simulate previous unclean shutdown
+        File runDir = new File(tempWorkDir, "run");
+        runDir.mkdirs();
+        File lockFile = new File(runDir, "instance.lock");
+        lockFile.createNewFile();
+        assertTrue("Lock file should exist before initialization", 
lockFile.exists());
+
+        Map<String, String> backendConfig = createFileBackendConfig();
+        serverManager.initialize(tempWorkDir, 3890, "dc=test,dc=com", "file", 
backendConfig, null);
+
+        assertFalse("Lock file should be cleaned up during initialization", 
lockFile.exists());
+    }
+
+    @Test
+    public void testGettersBeforeInitialization() {
+        assertEquals("Port should be 0 before initialization", 0, 
serverManager.getPort());
+        assertEquals("Base DN should be null before initialization", null, 
serverManager.getBaseDn());
+        assertFalse("Should not be running before initialization", 
serverManager.isRunning());
+    }
+
+    @Test
+    public void testStopBeforeStart() throws Exception {
+        Map<String, String> backendConfig = createFileBackendConfig();
+        serverManager.initialize(tempWorkDir, 3890, "dc=test,dc=com", "file", 
backendConfig, null);
+
+        // Should not throw exception when stopping before starting
+        serverManager.stop();
+    }
+
+    @Test
+    public void testMultipleStopCalls() throws Exception {
+        Map<String, String> backendConfig = createFileBackendConfig();
+        serverManager.initialize(tempWorkDir, 3890, "dc=test,dc=com", "file", 
backendConfig, null);
+
+        // Multiple stop calls should not throw exceptions
+        serverManager.stop();
+        serverManager.stop();
+        serverManager.stop();
+    }
+
+    @Test(expected = Exception.class)
+    public void testStartWithoutInitialize() throws Exception {
+        // Should throw exception when starting without initialization
+        serverManager.start();
+    }
+
+    private Map<String, String> createFileBackendConfig() {
+        Map<String, String> config = new HashMap<>();
+        config.put("baseDn", "dc=test,dc=com");
+        config.put("dataFile", tempLdapFile.getAbsolutePath());
+        return config;
+    }
+
+    private Map<String, String> createLdapBackendConfig() {
+        Map<String, String> config = new HashMap<>();
+        config.put("baseDn", "dc=proxy,dc=com");
+        config.put("url", "ldap://localhost:33389";);
+        config.put("remoteBaseDn", "dc=hadoop,dc=apache,dc=org");
+        config.put("systemUsername", "cn=admin,dc=hadoop,dc=apache,dc=org");
+        config.put("systemPassword", "admin-password");
+        return config;
+    }
+
+    private void cleanupTempFiles() {
+        if (tempLdapFile != null && tempLdapFile.exists()) {
+            tempLdapFile.delete();
+        }
+        if (tempWorkDir != null && tempWorkDir.exists()) {
+            // Clean up work directory recursively
+            deleteRecursively(tempWorkDir);
+        }
+    }
+
+    private void deleteRecursively(File file) {
+        if (file.isDirectory()) {
+            File[] children = file.listFiles();
+            if (children != null) {
+                for (File child : children) {
+                    deleteRecursively(child);
+                }
+            }
+        }
+        file.delete();
+    }
+}
\ No newline at end of file
diff --git 
a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServiceTest.java
 
b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServiceTest.java
new file mode 100644
index 000000000..dbdb081da
--- /dev/null
+++ 
b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServiceTest.java
@@ -0,0 +1,194 @@
+/*
+ * 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.knox.gateway.services.ldap;
+
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.services.ServiceLifecycleException;
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.After;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+
+/**
+ * Unit tests for KnoxLDAPService.
+ */
+public class KnoxLDAPServiceTest {
+
+    private KnoxLDAPService ldapService;
+    private GatewayConfig mockConfig;
+    private File tempDataDir;
+    private File tempLdapFile;
+
+    @Before
+    public void setUp() throws Exception {
+        ldapService = new KnoxLDAPService();
+        mockConfig = createMock(GatewayConfig.class);
+
+        // Create temporary directories and files
+        tempDataDir = File.createTempFile("knox-ldap-test", "");
+        tempDataDir.delete();
+        tempDataDir.mkdirs();
+        tempDataDir.deleteOnExit();
+
+        tempLdapFile = new File(tempDataDir, "ldap-users.json");
+        try (java.io.BufferedWriter writer = 
java.nio.file.Files.newBufferedWriter(tempLdapFile.toPath(), 
java.nio.charset.StandardCharsets.UTF_8)) {
+            writer.write("{\"users\":[],\"groups\":[]}");
+        }
+        tempLdapFile.deleteOnExit();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (ldapService != null) {
+            try {
+                ldapService.stop();
+            } catch (Exception e) {
+                // Ignore cleanup errors
+            }
+        }
+        if (tempLdapFile != null && tempLdapFile.exists()) {
+            tempLdapFile.delete();
+        }
+        if (tempDataDir != null && tempDataDir.exists()) {
+            tempDataDir.delete();
+        }
+    }
+
+    @Test
+    public void testInitWithLdapDisabled() throws Exception {
+        expect(mockConfig.isLDAPEnabled()).andReturn(false);
+        replay(mockConfig);
+
+        ldapService.init(mockConfig, new HashMap<>());
+
+        assertFalse("LDAP service should not be enabled", 
ldapService.isEnabled());
+        assertEquals("LDAP port should be -1 when disabled", -1, 
ldapService.getLdapPort());
+
+        verify(mockConfig);
+    }
+
+    @Test
+    public void testInitWithLdapEnabledFileBackend() throws Exception {
+        setupMockConfigForFileBackend();
+        replay(mockConfig);
+
+        ldapService.init(mockConfig, new HashMap<>());
+
+        assertTrue("LDAP service should be enabled", ldapService.isEnabled());
+        assertEquals("Base DN should match config", "dc=test,dc=com", 
ldapService.getBaseDn());
+
+        verify(mockConfig);
+    }
+
+    @Test
+    public void testInitWithLdapEnabledLdapBackend() throws Exception {
+        setupMockConfigForLdapBackend();
+        replay(mockConfig);
+
+        ldapService.init(mockConfig, new HashMap<>());
+
+        assertTrue("LDAP service should be enabled", ldapService.isEnabled());
+        assertEquals("Base DN should match config", "dc=proxy,dc=com", 
ldapService.getBaseDn());
+
+        verify(mockConfig);
+    }
+
+    @Test(expected = ServiceLifecycleException.class)
+    public void testInitWithInvalidBackendType() throws Exception {
+        expect(mockConfig.isLDAPEnabled()).andReturn(true);
+        
expect(mockConfig.getGatewayDataDir()).andReturn(tempDataDir.getAbsolutePath());
+        expect(mockConfig.getLDAPPort()).andReturn(3890);
+        expect(mockConfig.getLDAPBaseDN()).andReturn("dc=test,dc=com");
+        expect(mockConfig.getLDAPBackendType()).andReturn("invalid");
+        expect(mockConfig.getLDAPBackendConfig("invalid")).andReturn(new 
HashMap<>());
+        replay(mockConfig);
+
+        ldapService.init(mockConfig, new HashMap<>());
+    }
+
+    @Test
+    public void testStartWhenDisabled() throws Exception {
+        expect(mockConfig.isLDAPEnabled()).andReturn(false);
+        replay(mockConfig);
+
+        ldapService.init(mockConfig, new HashMap<>());
+
+        // Should not throw exception
+        ldapService.start();
+
+        verify(mockConfig);
+    }
+
+    @Test
+    public void testStopWhenDisabled() throws Exception {
+        expect(mockConfig.isLDAPEnabled()).andReturn(false);
+        replay(mockConfig);
+
+        ldapService.init(mockConfig, new HashMap<>());
+
+        // Should not throw exception
+        ldapService.stop();
+
+        verify(mockConfig);
+    }
+
+    @Test
+    public void testGettersWhenNotInitialized() {
+        assertEquals("LDAP port should be -1 when not initialized", -1, 
ldapService.getLdapPort());
+        assertEquals("Base DN should be null when not initialized", null, 
ldapService.getBaseDn());
+        assertFalse("Should not be enabled when not initialized", 
ldapService.isEnabled());
+    }
+
+    private void setupMockConfigForFileBackend() {
+        expect(mockConfig.isLDAPEnabled()).andReturn(true);
+        
expect(mockConfig.getGatewayDataDir()).andReturn(tempDataDir.getAbsolutePath());
+        expect(mockConfig.getLDAPPort()).andReturn(3890);
+        expect(mockConfig.getLDAPBaseDN()).andReturn("dc=test,dc=com");
+        expect(mockConfig.getLDAPBackendType()).andReturn("file");
+
+        Map<String, String> fileBackendConfig = new HashMap<>();
+        fileBackendConfig.put("dataFile", tempLdapFile.getAbsolutePath());
+        
expect(mockConfig.getLDAPBackendConfig("file")).andReturn(fileBackendConfig);
+    }
+
+    private void setupMockConfigForLdapBackend() {
+        expect(mockConfig.isLDAPEnabled()).andReturn(true);
+        
expect(mockConfig.getGatewayDataDir()).andReturn(tempDataDir.getAbsolutePath());
+        expect(mockConfig.getLDAPPort()).andReturn(3890);
+        expect(mockConfig.getLDAPBaseDN()).andReturn("dc=proxy,dc=com");
+        expect(mockConfig.getLDAPBackendType()).andReturn("ldap");
+
+        Map<String, String> ldapBackendConfig = new HashMap<>();
+        ldapBackendConfig.put("url", "ldap://localhost:33389";);
+        ldapBackendConfig.put("remoteBaseDn", "dc=hadoop,dc=apache,dc=org");
+        ldapBackendConfig.put("systemUsername", 
"cn=admin,dc=hadoop,dc=apache,dc=org");
+        ldapBackendConfig.put("systemPassword", "admin-password");
+        
expect(mockConfig.getLDAPBackendConfig("ldap")).andReturn(ldapBackendConfig);
+    }
+}
\ No newline at end of file
diff --git 
a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/backend/BackendFactoryTest.java
 
b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/backend/BackendFactoryTest.java
new file mode 100644
index 000000000..fde29a087
--- /dev/null
+++ 
b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/backend/BackendFactoryTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.knox.gateway.services.ldap.backend;
+
+import org.junit.Test;
+import org.junit.Before;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.ServiceLoader;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Unit tests to verify ServiceLoader discovery of LDAP backends.
+ */
+public class BackendFactoryTest {
+
+    private Map<String, String> config;
+
+    @Before
+    public void setUp() throws Exception {
+        config = new HashMap<>();
+        config.put("baseDn", "dc=test,dc=com");
+
+        // Create a temporary data file for FileBackend tests
+        java.io.File tempFile = java.io.File.createTempFile("ldap-test", 
".json");
+        tempFile.deleteOnExit();
+
+        // Write minimal valid JSON
+        try (java.io.BufferedWriter writer = 
java.nio.file.Files.newBufferedWriter(tempFile.toPath(), 
java.nio.charset.StandardCharsets.UTF_8)) {
+            writer.write("{\"users\":[],\"groups\":[]}\n");
+        }
+
+        config.put("dataFile", tempFile.getAbsolutePath());
+    }
+
+    @Test
+    public void testServiceLoaderDiscovery() {
+        ServiceLoader<LdapBackend> loader = 
ServiceLoader.load(LdapBackend.class);
+
+        // Should discover at least the built-in backends
+        boolean foundFileBackend = false;
+        boolean foundLdapBackend = false;
+
+        for (LdapBackend backend : loader) {
+            String backendName = backend.getName();
+            if ("file".equals(backendName)) {
+                foundFileBackend = true;
+                assertTrue("File backend should be FileBackend instance", 
backend instanceof FileBackend);
+            } else if ("ldap".equals(backendName)) {
+                foundLdapBackend = true;
+                assertTrue("LDAP backend should be LdapProxyBackend instance", 
backend instanceof LdapProxyBackend);
+            }
+        }
+
+        assertTrue("ServiceLoader should discover file backend", 
foundFileBackend);
+        assertTrue("ServiceLoader should discover ldap backend", 
foundLdapBackend);
+    }
+
+    @Test
+    public void testCreateFileBackend() throws Exception {
+        LdapBackend fileBackend = BackendFactory.createBackend("file", config);
+
+        assertNotNull("File backend should be created", fileBackend);
+        assertTrue("Should create FileBackend instance", fileBackend 
instanceof FileBackend);
+        assertEquals("Backend name should be 'file'", "file", 
fileBackend.getName());
+    }
+
+    @Test
+    public void testCreateLdapBackend() throws Exception {
+        config.put("url", "ldap://localhost:389";);
+        config.put("remoteBaseDn", "dc=hadoop,dc=apache,dc=org");
+
+        LdapBackend ldapBackend = BackendFactory.createBackend("ldap", config);
+
+        assertNotNull("LDAP backend should be created", ldapBackend);
+        assertTrue("Should create LdapProxyBackend instance", ldapBackend 
instanceof LdapProxyBackend);
+        assertEquals("Backend name should be 'ldap'", "ldap", 
ldapBackend.getName());
+    }
+
+    @Test
+    public void testCaseInsensitiveBackendNames() throws Exception {
+        // Test uppercase
+        LdapBackend upperCaseBackend = BackendFactory.createBackend("FILE", 
config);
+        assertTrue("Should create FileBackend with uppercase name", 
upperCaseBackend instanceof FileBackend);
+
+        // Test mixed case
+        LdapBackend mixedCaseBackend = BackendFactory.createBackend("File", 
config);
+        assertTrue("Should create FileBackend with mixed case name", 
mixedCaseBackend instanceof FileBackend);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testUnknownBackendThrowsException() throws Exception {
+        BackendFactory.createBackend("unknown", config);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNullBackendNameThrowsException() throws Exception {
+        BackendFactory.createBackend(null, config);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testEmptyBackendNameThrowsException() throws Exception {
+        BackendFactory.createBackend("", config);
+    }
+}
\ No newline at end of file
diff --git 
a/gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
 
b/gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
index 5d96dfbb4..7c848d236 100644
--- 
a/gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
+++ 
b/gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
@@ -1202,4 +1202,40 @@ public class GatewayTestConfig extends Configuration 
implements GatewayConfig {
   public boolean isTopologyAsyncSupported(String topology) {
     return false;
   }
+
+  // LDAP Service Configuration
+  @Override
+  public boolean isLDAPEnabled() {
+    return false;
+  }
+
+  @Override
+  public int getLDAPPort() {
+    return 3890;
+  }
+
+  @Override
+  public String getLDAPBaseDN() {
+    return "dc=test,dc=com";
+  }
+
+  @Override
+  public String getLDAPBackendType() {
+    return "file";
+  }
+
+  @Override
+  public String getLDAPBackendDataFile() {
+    return getGatewayDataPath().resolve("ldap-users.json").toString();
+  }
+
+  @Override
+  public Set<String> getPropertyNames() {
+    return Collections.emptySet();
+  }
+
+  @Override
+  public Map<String, String> getLDAPBackendConfig(String backendType) {
+    return Collections.emptyMap();
+  }
 }
diff --git 
a/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java 
b/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
index ad3fee6d8..153f2c568 100644
--- 
a/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
+++ 
b/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
@@ -123,6 +123,13 @@ public interface GatewayConfig {
 
   String DEPLOYMENT_PATH_ALIAS = ".path.alias.";
 
+  // LDAP Service Configuration
+  String LDAP_ENABLED = "gateway.ldap.enabled";
+  String LDAP_PORT = "gateway.ldap.port";
+  String LDAP_BASE_DN = "gateway.ldap.base.dn";
+  String LDAP_BACKEND_TYPE = "gateway.ldap.backend.type";
+  String LDAP_BACKEND_DATA_FILE = "gateway.ldap.backend.data.file";
+
   /**
    * The location of the gateway configuration.
    * Subdirectories will be: topologies
@@ -1031,4 +1038,44 @@ public interface GatewayConfig {
    * @return the strict transport option if set; otherwise return the default 
value 'max-age=31536000'
    */
   String getStrictTransportOption();
+
+  /**
+   * @return true if the embedded LDAP service is enabled; otherwise false
+   */
+  boolean isLDAPEnabled();
+
+  /**
+   * @return the port for the LDAP service to listen on
+   */
+  int getLDAPPort();
+
+  /**
+   * @return the base DN for LDAP entries
+   */
+  String getLDAPBaseDN();
+
+  /**
+   * @return the backend type for LDAP (file, ldap, jdbc, etc.)
+   */
+  String getLDAPBackendType();
+
+  /**
+   * @return the path to the data file for file-based backend
+   */
+  String getLDAPBackendDataFile();
+
+  /**
+   * Get backend-specific configuration properties.
+   * Returns all properties with prefix "gateway.ldap.backend.{backendType}."
+   * with the prefix stripped from the keys.
+   *
+   * @param backendType the backend type (e.g., "file", "ldap", "database")
+   * @return map of configuration key-value pairs for the specified backend
+   */
+  Map<String, String> getLDAPBackendConfig(String backendType);
+
+  /**
+   * @return set of all property names in the configuration
+   */
+  Set<String> getPropertyNames();
 }
diff --git 
a/gateway-spi/src/main/java/org/apache/knox/gateway/services/ServiceType.java 
b/gateway-spi/src/main/java/org/apache/knox/gateway/services/ServiceType.java
index de4a83db9..d96db8d49 100644
--- 
a/gateway-spi/src/main/java/org/apache/knox/gateway/services/ServiceType.java
+++ 
b/gateway-spi/src/main/java/org/apache/knox/gateway/services/ServiceType.java
@@ -38,7 +38,8 @@ public enum ServiceType {
   TOPOLOGY_SERVICE("TopologyService"),
   CONCURRENT_SESSION_VERIFIER("ConcurrentSessionVerifier"),
   REMOTE_CONFIGURATION_MONITOR("RemoteConfigurationMonitor"),
-  GATEWAY_STATUS_SERVICE("GatewayStatusService");
+  GATEWAY_STATUS_SERVICE("GatewayStatusService"),
+  LDAP_SERVICE("LDAPService");
 
   private final String serviceTypeName;
   private final String shortName;
diff --git a/knox-token-management-ui/package.json 
b/knox-token-management-ui/package.json
index c3c363404..85a8a83fd 100644
--- a/knox-token-management-ui/package.json
+++ b/knox-token-management-ui/package.json
@@ -6,7 +6,7 @@
   "scripts": {
     "start": "ng serve  --verbose=true",
     "build": "ng build",
-    "build-prod": "ng build --configuration production",
+    "build-prod": "NODE_OPTIONS='--no-warnings' ng build --configuration 
production",
     "lint": "eslint token-management"
   },
   "private": true,
diff --git a/pom.xml b/pom.xml
index 4309d8b54..db2df1d3e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -189,6 +189,7 @@
         <commons-logging.version>1.3.5</commons-logging.version>
         <commons-math3.version>3.6.1</commons-math3.version>
         <commons-net.version>3.9.0</commons-net.version>
+        <commons-pool2.version>2.7.0</commons-pool2.version>
         <commons-text.version>1.10.0</commons-text.version>
         <cors-filter.version>2.9.1</cors-filter.version>
         <cryptacular.version>1.2.7</cryptacular.version>
@@ -2087,6 +2088,11 @@
                 <artifactId>commons-net</artifactId>
                 <version>${commons-net.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.apache.commons</groupId>
+                <artifactId>commons-pool2</artifactId>
+                <version>${commons-pool2.version}</version>
+            </dependency>
             <dependency>
                 <groupId>org.apache.commons</groupId>
                 <artifactId>commons-text</artifactId>
@@ -2124,7 +2130,6 @@
                 <artifactId>apacheds-jdbm</artifactId>
                 <version>${apacheds-jdbm.version}</version>
             </dependency>
-
             <dependency>
                 <groupId>org.apache.directory.server</groupId>
                 <artifactId>apacheds-core</artifactId>
@@ -2167,6 +2172,16 @@
                     </exclusion>
                 </exclusions>
             </dependency>
+            <dependency>
+                <groupId>org.apache.directory.server</groupId>
+                <artifactId>apacheds-ldif-partition</artifactId>
+                <version>${apacheds.directory.server.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.directory.server</groupId>
+                <artifactId>apacheds-jdbm-partition</artifactId>
+                <version>${apacheds.directory.server.version}</version>
+            </dependency>
 
             <dependency>
                 <groupId>org.apache.directory.api</groupId>
@@ -2183,6 +2198,11 @@
                 <artifactId>api-ldap-model</artifactId>
                 <version>${apacheds.directory.api.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.apache.directory.api</groupId>
+                <artifactId>api-ldap-schema-data</artifactId>
+                <version>${apacheds.directory.api.version}</version>
+            </dependency>
             <dependency>
                 <groupId>org.apache.directory.api</groupId>
                 <artifactId>api-util</artifactId>

Reply via email to