首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java JNDI 封裝 LDAP 操作工具類:連接工廠與 CRUD 統一入口

     編輯:Java 相關知識

架構設計

三層封裝:

  1. Env:連接參數不可變容器
  2. LdapConnectionFactory:線程安全的 DirContext 獲取/關閉
  3. LdapOperUtils:業務級 CRUD 操作 (增刪改查、搜索、修改屬性)

核心代碼實現

1. Env 連接參數類


public final class Env {
    private final String factory;           // com.sun.jndi.ldap.LdapCtxFactory
    private final String url;               // ldap://host:389 / ldaps://host:636
    private final String adminDn;           // cn=admin,dc=example,dc=com
    private final String adminPassword;
    private final String sslTrustStore;     // 信任庫路徑 (ldaps)
    private final String securityProtocol;  // ssl / starttls
    private final String connectTimeout;    // 毫秒

    // 構造器、Getter、Builder 模式...
}

2. LdapConnectionFactory 連接工廠


public final class LdapConnectionFactory {
    private final Env env;
    private final ThreadLocal<DirContext> contextHolder = new ThreadLocal<>();

    public LdapConnectionFactory(Env env) { this.env = env; }

    public DirContext getContext() throws NamingException {
        DirContext ctx = contextHolder.get();
        if (ctx != null) return ctx;

        Hashtable<String, String> props = new Hashtable<>();
        props.put(Context.INITIAL_CONTEXT_FACTORY, env.getFactory());
        props.put(Context.PROVIDER_URL, env.getUrl());
        props.put(Context.SECURITY_AUTHENTICATION, "simple");
        props.put(Context.SECURITY_PRINCIPAL, env.getAdminDn());
        props.put(Context.SECURITY_CREDENTIALS, env.getAdminPassword());
        if (env.getConnectTimeout() != null)
            props.put("com.sun.jndi.ldap.connect.timeout", env.getConnectTimeout());
        if (env.getSslTrustStore() != null) {
            props.put("java.naming.ldap.factory.socket", 
                "com.example.SSLTrustSocketFactory"); // 自定義信任所有證書或指定信任庫
        }

        ctx = new InitialDirContext(props);
        contextHolder.set(ctx);
        return ctx;
    }

    public void closeContext() {
        DirContext ctx = contextHolder.get();
        if (ctx != null) {
            try { ctx.close(); } catch (NamingException ignored) {}
            contextHolder.remove();
        }
    }
}

3. LdapOperUtils 核心操作


public final class LdapOperUtils {
    private final LdapConnectionFactory factory;

    public LdapOperUtils(LdapConnectionFactory factory) { this.factory = factory; }

    // 創建條目
    public void create(String dn, Attributes attrs) throws NamingException {
        DirContext ctx = factory.getContext();
        ctx.createSubcontext(dn, attrs);
    }

    // 刪除條目
    public void delete(String dn) throws NamingException {
        factory.getContext().destroySubcontext(dn);
    }

    // 修改屬性 (增/刪/改)
    public void modifyAttributes(String dn, int modOp, String attrName, Object... values) 
            throws NamingException {
        ModificationItem[] items = Arrays.stream(values)
            .map(v -> new ModificationItem(modOp, new BasicAttribute(attrName, v)))
            .toArray(ModificationItem[]::new);
        factory.getContext().modifyAttributes(dn, items);
    }

    // 搜索 (支援篩選器、返回屬性、頁面)
    public NamingEnumeration<SearchResult> search(String baseDn, String filter, 
            String[] returningAttrs, SearchControls controls) throws NamingException {
        return factory.getContext().search(baseDn, filter, returningAttrs, controls);
    }

    // 便捷:單對象查找
    public SearchResult findOne(String baseDn, String filter, String[] attrs) 
            throws NamingException {
        SearchControls ctls = new SearchControls();
        ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
        ctls.setReturningAttributes(attrs);
        ctls.setCountLimit(1);
        NamingEnumeration<SearchResult> enum = search(baseDn, filter, attrs, ctls);
        return enum.hasMore() ? enum.next() : null;
    }
}

關鍵配置說明

參數示例值說明
factorycom.sun.jndi.ldap.LdapCtxFactoryJDK 內建 LDAP 提供者
urlldap://192.168.1.10:389標準端口 389,SSL 636
adminDncn=admin,dc=company,dc=com管理員完整 DN
connectTimeout5000避免網絡故障無限阻塞
sslTrustStore/etc/ssl/certs/ldap-trust.jksldaps 必需,導入服務端 CA 證書

常見操作模式


// 1. 添加用戶
Attributes attrs = new BasicAttributes();
attrs.put("objectClass", new String[]{"top", "person", "organizationalPerson", "inetOrgPerson"});
attrs.put("cn", "Zhang San");
attrs.put("sn", "Zhang");
attrs.put("mail", "zhangsan@company.com");
attrs.put("userPassword", "initPass123".getBytes());
ldap.create("cn=Zhang San,ou=users,dc=company,dc=com", attrs);

// 2. 修改郵箱
ldap.modifyAttributes("cn=Zhang San,ou=users,dc=company,dc=com", 
    DirContext.REPLACE_ATTRIBUTE, "mail", "zhang.san@company.com");

// 3. 搜索部門所有用戶
SearchControls ctls = new SearchControls();
ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
ctls.setReturningAttributes(new String[]{"cn", "mail", "telephoneNumber"});
NamingEnumeration<SearchResult> results = ldap.search(
    "ou=dev,dc=company,dc=com", "(objectClass=inetOrgPerson)", null, ctls);
while (results.hasMore()) {
    SearchResult sr = results.next();
    System.out.println(sr.getAttributes().get("cn").get());
}

生產級增強建議

  1. 連接池:使用 com.sun.jndi.ldap.pool 或自定義池化 DirContext,避免頻繁握手
  2. 重試與熔斷:網絡抖動時指數退避重試,連續失敗熔斷降級
  3. 異步執行:寫操作提交到線程池,返回 CompletableFuture
  4. Schema 感知:緩存 ObjectClass/屬性定義,校驗輸入合法性
  5. 審計日誌:記錄所有寫操作 (誰、何時、什麼 DN、舊值/新值)
  6. Spring LDAP 整合:生產環境建議直接使用 spring-ldap,提供 LdapTemplate、ODM、事務支援

總結

JNDI 是 Java 訪問目錄服務的標準 SPI,封裝三層 (Env/Factory/OperUtils) 可實現配置解耦、連接復用、業務語義化。核心難點在於SSL 信任鏈配置、連接生命週期管理、屬性修改語法 (ModificationItem)。生產環境強烈建議接入 Spring LDAP 或成熟連接池組件,專注業務邏輯而非底層協議細節。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved