LDAP (Lightweight Directory Access Protocol) 是企業級身份認證、統一帳號管理的標準協議。Java 透過 JNDI (Java Naming and Directory Interface) 提供標準化 API 操作 LDAP。本文以 OpenLDAP/Active Directory 通用結構為例,展示完整 CRUD 代碼。
<dependency>
<groupId>com.sun.jndi</groupId>
<artifactId>ldap</artifactId>
<version>1.2.4</version> <!-- JDK 內建通常無需額外引入 -->
</dependency>
<!-- Spring LDAP 現代封裝 (推薦生產環境) -->
<dependency>
<groupId>org.springframework.ldap</groupId>
<artifactId>spring-ldap-core</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
dc=company,dc=com
└── ou=users
├── uid=zhangsan
│ ├── cn: 張三
│ ├── sn: 三
│ ├── mail: zhangsan@company.com
│ └── userPassword: {SSHA}...
└── uid=lisi
└── ...
public class LdapTemplate {
private final String url = "ldap://ldap.company.com:389";
private final String baseDn = "ou=users,dc=company,dc=com";
private final String adminDn = "cn=admin,dc=company,dc=com";
private final String adminPwd = "secret";
/** 取得 LdapContext (已綁定管理員) */
private LdapContext getContext() throws NamingException {
Hashtable<String, String> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, url);
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, adminDn);
env.put(Context.SECURITY_CREDENTIALS, adminPwd);
env.put("com.sun.jndi.ldap.connect.timeout", "5000");
env.put("com.sun.jndi.ldap.read.timeout", "5000");
return new InitialLdapContext(env, null);
}
// ---------- CREATE ----------
public void addUser(String uid, String cn, String sn, String mail, String password)
throws NamingException {
try (LdapContext ctx = getContext()) {
Attributes attrs = new BasicAttributes(true); // ignore case
// objectClass 必須包含 person 衍生類
Attribute oc = new BasicAttribute("objectClass");
oc.add("top");
oc.add("person");
oc.add("organizationalPerson");
oc.add("inetOrgPerson"); // 常用擴展
attrs.put(oc);
attrs.put("uid", uid);
attrs.put("cn", cn);
attrs.put("sn", sn);
attrs.put("mail", mail);
attrs.put("userPassword", password.getBytes(StandardCharsets.UTF_8)); // 明文,生產應用 SSHA
String userDn = "uid=" + uid + "," + baseDn;
ctx.createSubcontext(userDn, attrs);
}
}
// ---------- READ ----------
public Attributes getUser(String uid) throws NamingException {
try (LdapContext ctx = getContext()) {
String userDn = "uid=" + uid + "," + baseDn;
return ctx.getAttributes(userDn, new String[]{"uid", "cn", "sn", "mail", "userPassword"});
}
}
public List<Attributes> searchUsers(String filter) throws NamingException {
try (LdapContext ctx = getContext()) {
SearchControls ctls = new SearchControls();
ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
ctls.setReturningAttributes(new String[]{"uid", "cn", "sn", "mail"});
NamingEnumeration<SearchResult> enum = ctx.search(baseDn, filter, ctls);
List<Attributes> list = new ArrayList<>();
while (enum.hasMore()) {
list.add(enum.next().getAttributes());
}
return list;
}
}
// ---------- UPDATE ----------
public void updateMail(String uid, String newMail) throws NamingException {
try (LdapContext ctx = getContext()) {
String userDn = "uid=" + uid + "," + baseDn;
ModificationItem[] mods = new ModificationItem[1];
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE,
new BasicAttribute("mail", newMail));
ctx.modifyAttributes(userDn, mods);
}
}
// ---------- DELETE ----------
public void deleteUser(String uid) throws NamingException {
try (LdapContext ctx = getContext()) {
String userDn = "uid=" + uid + "," + baseDn;
ctx.destroySubcontext(userDn);
}
}
// ---------- 認證 (Bind) ----------
public boolean authenticate(String uid, String password) {
try {
Hashtable<String, String> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, url);
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, "uid=" + uid + "," + baseDn);
env.put(Context.SECURITY_CREDENTIALS, password);
new InitialLdapContext(env, null).close(); // 綁定成功即認證通過
return true;
} catch (AuthenticationException e) {
return false; // 鳥密碼錯誤
} catch (NamingException e) {
throw new RuntimeException("LDAP 連線異常", e);
}
}
}
@Repository
public class SpringLdapUserRepo {
@Autowired private LdapTemplate ldapTemplate;
public void create(User user) {
Name dn = buildDn(user.getUid());
DirContextAdapter adapter = new DirContextAdapter(dn);
adapter.setAttributeValues("objectClass", new String[]{"top", "person", "organizationalPerson", "inetOrgPerson"});
adapter.setAttributeValue("uid", user.getUid());
adapter.setAttributeValue("cn", user.getCn());
adapter.setAttributeValue("sn", user.getSn());
adapter.setAttributeValue("mail", user.getMail());
adapter.setAttributeValue("userPassword", user.getPassword());
ldapTemplate.bind(adapter);
}
public User findByUid(String uid) {
Name dn = buildDn(uid);
return ldapTemplate.lookup(dn, this::mapToUser);
}
public void updateMail(String uid, String mail) {
Name dn = buildDn(uid);
ldapTemplate.modifyAttributes(dn,
new ModificationItem[]{new ModificationItem(DirContextOperations.REPLACE_ATTRIBUTE,
new BasicAttribute("mail", mail))});
}
public void delete(String uid) {
ldapTemplate.unbind(buildDn(uid));
}
private Name buildDn(String uid) {
return LdapNameBuilder.newInstance("ou=users,dc=company,dc=com")
.add("uid", uid).build();
}
private User mapToUser(DirContextOperations ctx) {
User u = new User();
u.setUid(ctx.getStringAttribute("uid"));
u.setCn(ctx.getStringAttribute("cn"));
u.setSn(ctx.getStringAttribute("sn"));
u.setMail(ctx.getStringAttribute("mail"));
return u;
}
}
PoolingContextSource)ldaps:// (636) 或 StartTLS,設定 java.naming.ldap.factory.socketLdapNameBuilder 或手動轉義SimplePagedResultsControl (RFC 2696)@Test
public void testCrud() {
LdapTemplate tpl = new LdapTemplate();
// Create
tpl.addUser("test001", "測試用戶", "測", "test@company.com", "Passw0rd!");
// Read
Attributes attrs = tpl.getUser("test001");
System.out.println("CN: " + attrs.get("cn").get());
// Search
List<Attributes> list = tpl.searchUsers("(cn=測試*)");
System.out.println("搜尋筆數: " + list.size());
// Update
tpl.updateMail("test001", "new@company.com");
// Auth
boolean ok = tpl.authenticate("test001", "Passw0rd!");
System.out.println("認證: " + ok);
// Delete
tpl.deleteUser("test001");
}
Java 操作 LDAP 核心是 JNDI LdapContext,掌握 createSubcontext/destroySubcontext/modifyAttributes/search 四大操作。生產環境**強烈建議使用 Spring LDAP**,簡化 DN 構建、屬性映射、連線池、異常轉換等樣板代碼,並內建支援事務、ODM 映射、分頁控制等企業級特性。