OpenLDAP使用疑惑解答及使用Java完成LDAP身份認證


導讀

LDAP(輕量級目錄訪問協議,Lightweight Directory Access Protocol)是實現提供被稱為目錄服務的信息服務。目錄服務是一種特殊的數據庫系統,其專門針對讀取,瀏覽和搜索操作進行了特定的優化。目錄一般用來包含描述性的,基於屬性的信息並支持精細復雜的過濾能力。目錄一般不支持通用數據庫針對大量更新操作操作需要的復雜的事務管理或回卷策略。而目錄服務的更新則一般都非常簡單。這種目錄可以存儲包括個人信息、web鏈結、jpeg圖像等各種信息。為了訪問存儲在目錄中的信息,就需要使用運行在TCP/IP 之上的訪問協議—LDAP。

LDAP目錄中的信息是是按照樹型結構組織,具體信息存儲在條目(entry)的數據結構中。常見的例子是通訊簿,由以字母順序排列的名字、地址和電話號碼組成。

目錄服務與關系數據庫之間的主要區別在於:二者都允許對存儲數據進行訪問,只是目錄主要用於讀取,其查詢的效率很高,而關系數據庫則是為讀寫而設計的。也就是目錄服務不適於進行頻繁的更新,屬於典型的分布式結構。

總結:對於查詢操作多於更新操作的(認證)系統來說,使用OpenLDAP是一個比關系數據庫如MySq、PostgreSQL等更好的選擇。


LDAP的功能

在LDAP的功能模型中定義了一系列利用LDAP協議的操作,主要包含以下4部分:

查詢操作:允許查詢目錄和取得數據,其查詢性能比關系數據庫好。

更新操作:目錄的更新操作沒關系數據庫方便,更新性能較差,但也同樣允許進行添加、刪除、修改等操作。

復制操作:前面也提到過,LDAP是一種典型的分布式結構,提供復制操作,可將主服務器的數據的更新復制到設置的從服務器中。

認證和管理操作:允許客戶端在目錄中識別自己,並且能夠控制一個會話的性質。


而本文所要將的OpenLDAP就是一個優秀的開源的LDAP實現。


OpenLDAP安裝配置及疑惑解答

1. 安裝和配置OpenLDAP

安裝軟件非常簡單,但在配置過程中遇到了不少坎坷,不是服務啟動不成功就是驗證不成功。

具體的安裝和配置方法網上一大把,但都參差不齊,主要是因為新舊版本的OpenLDAP不同,配置方法有很大的改動。

下面給出網上幾個還算靠譜的Linux和Windows兩個平台下安裝該軟件的方法:

1)ubuntu安裝LDAP:安裝方法靠譜,但配置說的不太清楚,配置注意事項看后面。

2)Ubuntu OpenLDAP Server:官方教程,最值得借鑒,是英文的,這里有中文版的,但沒英文的清晰,說的比較簡單。

3)Linux下安裝openldap:二進制包安裝方法,適用於非Ubuntu的Linux系統,稍微有點麻煩,在安裝OpenlDAP之前還需要安裝Berkeley DB,但配置靈活,可以自定義安裝路徑什么的。后面的配置也沒說清楚,主要看安裝方法。

4)Linux服務器部署系列之七—OpenLDAP篇:另一篇較詳細的二進制安裝方法及配置。

4)Windows下OpenLDAP的安裝及使用介紹了LDAP的一些基礎知識和Windows下安裝方法。

5)圖文介紹openLDAP在windows上的安裝配置:比較詳細,值得一看。


上面給出的這幾個鏈接雖然還不錯,但還是欠缺了些什么?對,就是講解,網上給出的教程都是手把手教你如何安裝和配置,而沒有說明版本差異、具體配置的含義及為什么這樣配置,如果因為版本或環境差異,你按其方法配置不成功,你也不知道哪里出的問題,因此建議還是先熟悉LDAP的基礎知識,配置文件含義然后再試着安裝。


2. OpenLDAP疑惑解答

下面根據我自己的經驗,給出幾個安裝和配置注意事項,供參考。


疑惑1:細心的人會發現有的教程說要配置主機DNS,添加與LDAP相關的域名,而大部分教程都沒有提及這個,那么到底要不要配置呢?

解答:當然需要配置。安裝好OpenLDAP后首先需要配置slapd.conf這個文件,其中里面有

suffix        "dc=example, dc=com"

這樣一句需要自己配置,這兩個dc代表什么意思呢?其實dc就是“domainComponent”,也就是域名的組成部分,准確的說是主機域名的 后綴組成部分,如果這里的配置與你的主機域名不對應的話,服務一般是啟動不了的。那么怎么配置域名呢?Linux和Windows下的配置文件如下:

Linux下:/etc/hosts

Windows下:C:\Windows\System32\drivers\etc\hosts

需要在hosts文件里添加一條域名(如果沒配置的話),格式如下:

127.0.1.1       hostname.example.com    hostname

比如我的主機名是min,並添加的域名配置是:

127.0.1.1       min.alexia.cn    min

那么相應的我就需要在slapd.conf里這樣配置suffix:

suffix        "dc=alexia, dc=cn"

當然這里域名后綴不一定只有兩級,也可以是hostname.example.com.cn,然后suffix就應該是“dc=example, dc=com, dc=cn”,這隨便你怎么設置了,只要對應就行。


疑惑2:很多版本的slapd.conf里默認都配置了下面兩個變量:

modulepath      /usr/lib/ldap
moduleload      back_@BACKEND@

這是什么意思?需要改動嗎?

解答:這是數據庫database的backend,一般slapd.conf里配置的database都是 bdb,也就是Berkeley DB,有的也許是hdb,其實也是Berkeley DB,只是兩個不同的存儲引擎(就像Mysql有MyISAM和InnoDB兩個不同的存儲引擎一樣)。而modulepath和moduleload指 定了動態模塊路徑及動態裝載的后端模塊,因為OpenLDAP默認是用Berkeley DB存儲數據的,如果你有動態的數據需要裝載,那么就需要配置這兩個參數,對於一般用戶將這兩個注釋掉即可。


疑惑3:OpenLDAP默認采用Berkeley DB存儲數據,那么可以換用其它的關系數據庫嗎?具體如何配置呢?

解答:當然可以。首先需要明確ldap數據模型來自RDBMS(關系數據庫模型),而並沒有指定一定是哪個 DB,只要是關系數據庫都可以作為LDAP的后台,那么你為什么會想用其它的數據庫代替自帶的Berkeley DB呢?我想可能是性能相關了,對於少量數據你用哪個都可以,但若涉及到稍大點的數據,比如成千上萬的用戶查詢,那么Berkeley DB的性能就不可觀了,而且Berkeley DB管理起來也不太方便,畢竟對這個數據庫熟悉的人不多,如果能換作我們經常使用的數據庫,不僅性能得到提升,管理起來也十分容易,豈不是一舉多得。

具體怎么配置了,請參考這篇文章:用postgresql作后台的openldap,以PostgreSQL作為例子進行講解。


疑惑4:新舊版本的OpenLDAP到底有什么差異呢?

解答:簡單一句話就是:舊版本的OpenLDAP配置文件一般是slapd.conf(路徑可能是/etc/openldap,也可能是/usr/local/openldap,甚至可能是/usr/share/slapd/,不同版本不同安裝不同系統都可能不同,可使用locate slapd.conf進行查找正確的路徑),而新版本(我測試的新版本是2.4.31)的OpenLDAP服務運行時並不會讀取該配置文件,而是從slapd.d目錄(一般與slapd.conf在同一目錄下)中讀取相關信息,我們需要把該目錄下的數據刪掉,然后利用我們在slapd.conf里配置的信息重新生成配置數據。這也可能是你啟動服務后運行ldap相關命令卻出現“ldap_bind: Invalid credentials (49)”錯誤的主要原因。具體怎么重新生成配置數據請看參考資料。


疑惑5:自定義的ldif數據文件中的objectclass后的domain、top、organizationalUnit、inetOrgPerson等等都是什么意思,可以隨便寫嗎?

解答:存儲LDAP配置信息及目錄內容的標准文本文件格式是LDIF(LDAP Interchange Format),使用文本文件來格式來存儲這些信息是為了方便讀取和修改,這也是其它大多數服務配置文件所采取的格式。LDIF文件常用來向目錄導入或更 改記錄信息,這些信息需要按照LDAP中schema的格式進行組織,並會接受schema 的檢查,如果不符合其要求的格式將會出現報錯信息。因此,ldif文件中的屬性都定義在各大schema中,其中objectclass是對象的類屬性, 不能隨便填寫,而應與schema中一致。一般slapd.conf文件的頭部都包含了這些schema:

include         ../etc/openldap/schema/core.schema
include         ../etc/openldap/schema/cosine.schema
include         ../etc/openldap/schema/inetorgperson.schema
include         ../etc/openldap/schema/nis.schema
include         ../etc/openldap/schema/krb5-kdc.schema
include         ../etc/openldap/schema/RADIUS-LDAPv3.schema
include         ../etc/openldap/schema/samba.schema

其中前三個是比較重要的schema,定義了我們所需要的各個類,比如ldif中一般先定義一個根節點,其相應的objectclass一般是 domain和top,而根節點下的ou屬性即定義組節點(group)的objectclass一般是 organizationalUnit,group下可以是group也可以是用戶節點,用戶節點的objectclass一般是 inetOrgPerson。而各個節點的一系列屬性如用戶節點的uid、mail、userPassword、sn等等都定義在schema中相關的 objectclass里,可以自己查找看看。


疑惑6:OpenLDAP認證用戶uid時默認是不區分大小寫的,也就是“alexia”與“AleXia”是同一個用戶,在有些情況下這並不合理,能配置使得認證時能區分大小寫嗎?

解答:以我目前的經驗來看,舊版本的OpenLDAP是可以配置區分大小寫的,而新版本的OpenLDAP卻配置不了。為什么這么說呢?

這里就涉及到“matching rules”這個概念了,即匹配規則,就是各個屬性按什么樣的規則進行匹配,比如是否區分大小寫、是否進行數字匹配等等,這里有詳細的官方匹配規則描述。比如舊版本的core.schema里有下面這樣一段:

attributetype ( 0.9.2342.19200300.100.1.1
   NAME ( 'uid' 'userid' )
   DESC 'RFC1274: user identifier' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch
   SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )

從字面上也可以看出,其中caseIgnoreMatchcaseIgnoreSubstringsMatch就定義了uid或userid屬性匹配時不區分大小寫,如果我們將其改為caseExactMatchcaseExactSubstringsMatch就表示用戶uid認證時需要區分大小寫,也就是“alexia”與“AleXia”同不同的用戶,這很簡單,在舊版本的OpenLDAP也行得通。

可是在新版本的OpenLDAP中卻不行,新版本的core.schema文件中也包含這樣一段:

#attributetype (  2.16.840.1.113730.3.1.217
#    NAME ( 'uid' 'userid' )
#    DESC 'RFC1274: user identifier'
#    EQUALITY caseIgnoreMatch
#    SUBSTR caseIgnoreSubstringsMatch
#    SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )

可惜是注釋掉的,那我們取消注釋然后改屬性行不行呢?答案是不行,會報錯:Duplicate attributeType: "2.16.840.1.113730.3.1.217”,也就是說該屬性已經被定義了,然后我就去包含的所有schema中搜索uid屬性的定義,結果卻找不到定義,那么為什么還會報這個錯誤呢?后來一陣搜索,終於在這個帖子“slapd: built-in schema for uidNumber/gidNumber does not have ordering directive”知道了答案,原來新版本的OpenLDAP已經把uid屬性定義schema硬編碼到了slapd程序中,也就是無法在配置文件中修改了,真是坑!

針對這個問題,我給出兩個不太好的解決方案:

  1. 下載OpenLDAP源碼,找到定義uid屬性匹配規則的地方,修改它然后重新編譯。這個工作量不輕松,熱愛研究源碼的人可以嘗試。
  2. 不 要用uid屬性進行認證,我們可以自定義一個與用戶一一對應的屬性如user-id(不要與已有的屬性重復就行),其配置與uid一模一樣(即模仿 uid),然后用該屬性作為認證的因子,建議重新建一個schema,然后配置好后include進slapd.conf中重啟服務即可。具體怎么定義和 配置可以參考這篇文章


我的主要經驗也就這些。OpenLDAP也有客戶端,如果你配置成功后,可以用客戶端或寫Java程序進行驗證。


OpenLDAP客戶端

OpenLDAP既有圖形客戶端也有網頁客戶端。

1. 圖形客戶端

主要有兩個圖形客戶端:LdapBrowser282 (下載:LdapBrowser282.zip,下載解壓后直接雙擊:lbe.bat 文件即可運行)和LdapAdmin(官方下載),使用都非常簡單。

如下是兩個客戶端的界面,都需要先建立一個鏈接,填上相應的IP地址、端口和dn配置,然后連接即可獲得你配置的數據。


LDAP Browser客戶端:


LDAP Admin客戶端:


 

2. 網頁客戶端

即 phpLDAPadmin,基於PHP的一個web應用,需要配置Apache服務器和PHP,具體的配置方法可參考“phpLDAPadmin 安裝配置講解,通過 Web 端來管理您的 LDAP 服務器”,我比較偷懶,直接使用的PHPnow全套服務,安裝成功后大概是下面這樣一個界面:




使用Java完成LDAP身份驗證

下面借鑒網上資料提供一個簡單的認證程序如下:

import java.util.Hashtable;

import javax.naming.AuthenticationException;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.Control;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
 
public class LDAPAuthentication {
    private final String URL = "ldap://127.0.0.1:389/";
    private final String BASEDN = "ou=Tester,dc=alexia,dc=cn";  // 根據自己情況進行修改
    private final String FACTORY = "com.sun.jndi.ldap.LdapCtxFactory";
    private LdapContext ctx = null;
    private final Control[] connCtls = null;
 
    private void LDAP_connect() {
        Hashtable<String, String> env = new Hashtable<String, String>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, FACTORY);
        env.put(Context.PROVIDER_URL, URL + BASEDN);
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
         
        String root = "cn=manager,dc=alexia,dc=cn";  // 根,根據自己情況修改
        env.put(Context.SECURITY_PRINCIPAL, root);   // 管理員
        env.put(Context.SECURITY_CREDENTIALS, "123456");  // 管理員密碼
        
        try {
            ctx = new InitialLdapContext(env, connCtls);
            System.out.println( "認證成功" );  
            
        } catch (javax.naming.AuthenticationException e) {
            System.out.println("認證失敗:");
            e.printStackTrace();
        } catch (Exception e) {
        	System.out.println("認證出錯:");
            e.printStackTrace();
        }
        
		if (ctx != null) {
			try {
				ctx.close();
			}
			catch (NamingException e) {
	            e.printStackTrace();
			}

		}
    }
 
    private String getUserDN(String uid) {
        String userDN = "";
        LDAP_connect();
        try {
            SearchControls constraints = new SearchControls();
            constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
            NamingEnumeration<SearchResult> en = ctx.search("", "uid=" + uid, constraints);
            if (en == null || !en.hasMoreElements()) {
                System.out.println("未找到該用戶");
            }
            // maybe more than one element
            while (en != null && en.hasMoreElements()) {
                Object obj = en.nextElement();
                if (obj instanceof SearchResult) {
                    SearchResult si = (SearchResult) obj;
                    userDN += si.getName();
                    userDN += "," + BASEDN;
                } else {
                    System.out.println(obj);
                }
            }
        } catch (Exception e) {
            System.out.println("查找用戶時產生異常。");
            e.printStackTrace();
        }
 
        return userDN;
    }
 
    public boolean authenricate(String UID, String password) {
        boolean valide = false;
        String userDN = getUserDN(UID);
 
        try {
            ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, userDN);
            ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, password);
            ctx.reconnect(connCtls);
            System.out.println(userDN + " 驗證通過");
            valide = true;
        } catch (AuthenticationException e) {
            System.out.println(userDN + " 驗證失敗");
            System.out.println(e.toString());
            valide = false;
        } catch (NamingException e) {
            System.out.println(userDN + " 驗證失敗");
            valide = false;
        }
 
        return valide;
    }
    
    public static void main(String[] args) {
    	LDAPAuthentication ldap = new LDAPAuthentication();

    	if(ldap.authenricate("gygtest", "jmwang") == true){

    		System.out.println( "該用戶認證成功" ); 

    	}
    }
}


既可以作為普通程序的認證,也可以通過輸出檢查自己的配置是否正確。


LDAP擴展

LDAP的實現除了OpenLDAP外,還有其它,比如OpenDJ(Open source Directory services for the Java platform),它是一個新的LDAPv3相容目錄服務,為Java平台開發,提供了一個高性能的,高度可用和安全的企業管理的身份商店。其簡單的安 裝過程中,結合了Java平台的力量,使OpenDJ簡單和最快的目錄服務器部署和管理。有興趣的可以查閱相關資料。


參考資料



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM