LDAP(輕量級目錄訪問協議,Lightweight Directory Access Protocol)是實現提供被稱爲目錄服務的信息服務。目錄服務是一種特殊的數據庫系統,其專門針對讀取,瀏覽和搜索操做進行了特定的優化。目錄通常用來包含描述性的,基於屬性的信息並支持精細複雜的過濾能力。目錄通常不支持通用數據庫針對大量更新操做操做須要的複雜的事務管理或回捲策略。而目錄服務的更新則通常都很是簡單。這種目錄能夠存儲包括我的信息、web鏈結、jpeg圖像等各類信息。爲了訪問存儲在目錄中的信息,就須要使用運行在TCP/IP 之上的訪問協議—LDAP。php
LDAP目錄中的信息是是按照樹型結構組織,具體信息存儲在條目(entry)的數據結構中。常見的例子是通信簿,由以字母順序排列的名字、地址和電話號碼組成。html
目錄服務與關係數據庫之間的主要區別在於:兩者都容許對存儲數據進行訪問,只是目錄主要用於讀取,其查詢的效率很高,而關係數據庫則是爲讀寫而設計的。也就是目錄服務不適於進行頻繁的更新,屬於典型的分佈式結構。java
總結:對於查詢操做多於更新操做的(認證)系統來講,使用OpenLDAP是一個比關係數據庫如MySq、PostgreSQL等更好的選擇。linux
在LDAP的功能模型中定義了一系列利用LDAP協議的操做,主要包含如下4部分:web
查詢操做:容許查詢目錄和取得數據,其查詢性能比關係數據庫好。
sql
更新操做:目錄的更新操做不要緊數據庫方便,更新性能較差,但也一樣容許進行添加、刪除、修改等操做。數據庫
複製操做:前面也提到過,LDAP是一種典型的分佈式結構,提供複製操做,可將主服務器的數據的更新複製到設置的從服務器中。
ubuntu
認證和管理操做:容許客戶端在目錄中識別本身,而且可以控制一個會話的性質。windows
而本文所要將的OpenLDAP就是一個優秀的開源的LDAP實現。後端
安裝軟件很是簡單,但在配置過程當中遇到了很多坎坷,不是服務啓動不成功就是驗證不成功。
具體的安裝和配置方法網上一大把,但都良莠不齊,主要是由於新舊版本的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的基礎知識,配置文件含義而後再試着安裝。
下面根據我本身的經驗,給出幾個安裝和配置注意事項,供參考。
疑惑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} )
從字面上也能夠看出,其中caseIgnoreMatch
和caseIgnoreSubstringsMatch
就定義了uid或userid屬性匹配時不區分大小寫,若是咱們將其改成caseExactMatch
和caseExactSubstringsMatch
就表示用戶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程序中,也就是沒法在配置文件中修改了,真是坑!
針對這個問題,我給出兩個不太好的解決方案:
個人主要經驗也就這些。OpenLDAP也有客戶端,若是你配置成功後,能夠用客戶端或寫Java程序進行驗證。
OpenLDAP既有圖形客戶端也有網頁客戶端。
主要有兩個圖形客戶端:LdapBrowser282 (下載:LdapBrowser282.zip,下載解壓後直接雙擊:lbe.bat 文件便可運行)和LdapAdmin(官方下載),使用都很是簡單。
以下是兩個客戶端的界面,都須要先創建一個連接,填上相應的IP地址、端口和dn配置,而後鏈接便可得到你配置的數據。
LDAP Admin客戶端:
即 phpLDAPadmin,基於PHP的一個web應用,須要配置Apache服務器和PHP,具體的配置方法可參考「phpLDAPadmin 安裝配置講解,經過 Web 端來管理您的 LDAP 服務器」,我比較偷懶,直接使用的PHPnow全套服務,安裝成功後大概是下面這樣一個界面:
下面借鑑網上資料提供一個簡單的認證程序以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
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 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的實現除了OpenLDAP外,還有其它,好比OpenDJ(Open source Directory services for the Java platform),它是一個新的LDAPv3相容目錄服務,爲Java平臺開發,提供了一個高性能的,高度可用和安全的企業管理的身份商店。其簡單的安 裝過程當中,結合了Java平臺的力量,使OpenDJ簡單和最快的目錄服務器部署和管理。有興趣的能夠查閱相關資料。