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

Java LDAP 基礎操作完整實戰:連線、增刪改查

     編輯:Java 相關知識

前言

LDAP (Lightweight Directory Access Protocol) 是企業級身份認證、統一帳號管理的標準協議。Java 透過 JNDI (Java Naming and Directory Interface) 提供標準化 API 操作 LDAP。本文以 OpenLDAP/Active Directory 通用結構為例,展示完整 CRUD 代碼。

Maven 依賴

<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
        └── ...

核心工具類:原生 JNDI 實現

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);
        }
    }
}

Spring LDAP 現代封裝 (推薦)

@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;
    }
}

關鍵注意事項

  • 密碼儲存:生產環境**絕不可明文**,需用 SSHA/BCrypt,或依賴 LDAP 伺服器端密碼策略 (ppolicy)
  • 連線池:高併發必須使用連線池 (Spring LDAP 內建 PoolingContextSource)
  • SSL/TLS:生產強制 ldaps:// (636) 或 StartTLS,設定 java.naming.ldap.factory.socket
  • DN 特殊字元轉義:UID 包含逗號、等號等需用 LdapNameBuilder 或手動轉義
  • 分頁查詢:大量資料用 SimplePagedResultsControl (RFC 2696)
  • 屬性名大小寫:LDAP 不區分大小寫,但 Java 屬性鍵建議統一小寫

測試驗證

@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 映射、分頁控制等企業級特性。

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