三層封裝:
DirContext 獲取/關閉
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 模式...
}
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();
}
}
}
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;
}
}
| 參數 | 示例值 | 說明 |
|---|---|---|
| factory | com.sun.jndi.ldap.LdapCtxFactory | JDK 內建 LDAP 提供者 |
| url | ldap://192.168.1.10:389 | 標準端口 389,SSL 636 |
| adminDn | cn=admin,dc=company,dc=com | 管理員完整 DN |
| connectTimeout | 5000 | 避免網絡故障無限阻塞 |
| sslTrustStore | /etc/ssl/certs/ldap-trust.jks | ldaps 必需,導入服務端 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());
}
com.sun.jndi.ldap.pool 或自定義池化 DirContext,避免頻繁握手CompletableFuturespring-ldap,提供 LdapTemplate、ODM、事務支援JNDI 是 Java 訪問目錄服務的標準 SPI,封裝三層 (Env/Factory/OperUtils) 可實現配置解耦、連接復用、業務語義化。核心難點在於SSL 信任鏈配置、連接生命週期管理、屬性修改語法 (ModificationItem)。生產環境強烈建議接入 Spring LDAP 或成熟連接池組件,專注業務邏輯而非底層協議細節。