1、基础知识
首先,若是您对LDAP 不认识,建议先看看[原]LDAP服务介绍一文。本文以Linux 下经常使用的OpenLDAP为例说明。
LDAP 以数方式存放数据,每一个节点可存放属性或做为下面节点的父节点。DN是做为某个节点的惟一标识,例如:
php
ou=mail.linuxfly.org,dc=linuxfly,dc=org
操做时,必须指定DN的位置。与普通数据库相似,操做LDAP,主要就是添加、删除或修改节点、其属性值等。
属性必需要遵循.schema模式文件中定义的规则,这些文件一般放在/etc/openldap/schema/目录下,在/etc/openldap/sldapd.conf 中载入后生效,而且对载入顺序有必定的要求。html
引用
# cat /etc/openldap/slapd.conf |grep include
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/rfmail.schema
LDAP没有冗余性或惟一性要求,所以,在程序应用时,一般需手动建立属性,并赋值(指向)其余节点。
一般状况下,ldap 都是全局可读的。也就是说,默认配置下,不会限制读取ldap 中的信息。其运行端口为:389 。
2、登录信息
当须要修改或添加ldap 数据时,须要提供登录信息,这些信息在/etc/openldap/sldap.conf 中提供:linux
引用
database bdb #后台数据库类型
suffix "dc=rfmail" #目录树后缀
rootdn "cn=root,dc=rfmail" #管理员DN信息
rootpw linuxfly.com #明文密码
若已经安装BerkeleyDB数据库,则可把database设置为dbd形式,不然,可用ldbm 。(数据较慢)
另外,密码可以使用slappasswd 命令设定:web
引用
# slappasswd -h {SHA} -s 'linuxfly.com'
{SHA}vwSiF9lGtUkXixIIu/kFBDLc2Rg=
并把配置文件修改成:数据库
引用
rootpw {SHA}vwSiF9lGtUkXixIIu/kFBDLc2Rg=
※ 注意:slappasswd默认为{SSHA}方式密码,可以使用-h 指定密码方式。
启动便可:数组
引用
# service ldap start
# netstat -ln|grep 389
tcp 0 0 0.0.0.0:389 0.0.0.0:* LISTEN
tcp 0 0 :::389 :::* LISTEN
3、使用openldap-clients 套件操做ldap
操做LDAP的方式有不少,下面以bash下用openldap-clients 套件提供的工具来进行。
一、初始化数据
这一步一般是由特定的应用程序根据其自身使用的须要建立的。包括两部分的内容:bash
引用
a、建立.schema文件,该文件定义了后面ldap 中存放对象的类型和属性,这已经在上面sldapd.conf文件中定义;
b、初始化数据结构,其结构必须由上面的.schema文件已经定义的,并由应用程序读取和使用。
初始化文件一般为.ldif 结尾,称为LDIF数据交换格式。这种格式是行界定、冒号分隔的属性-值对。例如:服务器
引用
dn: dc=rfmail
objectClass: top
objectClass: dcObject
objectClass: organization
dc: rfmail
o: rfmail
导入时,执行:
数据结构
# ldapadd -x -h 192.168.228.135 -D 'cn=root,dc=rfmail' -W -f rfmail.ldif
ldapadd 命令各参数含义以下:oracle
引用
-x 为使用简单密码验证方式
-D 指定管理员DN(与slapd.conf中一致)
-W 为管理员密码,可使用-w password 直接输入密码
-f 为初始化数据LDIF的文件名
-h 为操做的服务器IP地址
二、搜索操做
LDAP是读优化数据库,所以,读的速度很快,也很经常使用。但与关系数据库不一样,其以树结构形式读取数据,若不添加过滤,会显示匹配节点下全部节点的内容。若以ldif 的形式表达,刚开始可能不太习惯。
# ldapsearch -x -b 'dc=rfmail'
首先要留意的是,ldapsearch 不须要提供验证信息。由于正如前面提到的,LDAP 默认供任何人可读。
-b 后面定义搜索节点位置,即从该节点往其子节点进行搜索
再看下面的命令:
# ldapsearch -x -b 'dc=rfmail' '(objectclass=top)'
命令后面括号中定义的是filter,即过滤符。这里只有节点中有属性为objactclass=top的才显示。
再看看:
# ldapsearch -x -b 'dc=rfmail' '(objectclass=*)' 'dn'
这行过滤符中用星号匹配,行末的'dn'表示仅显示该节点的dn属性,即requesting: dn。(不然会显示节点的所有属性)
※ 注意,请区分filter 与 requesting 的不一样。前者为搜索匹配,后者为限制显示的属性值。
三、身份验证
修改或添加内容需进行用户验证,可经过下面的命令确认验证信息:
引用
# ldapwhoami -x -D 'cn=root,dc=rfmail' -w 'linuxfly.com'
dn:cn=root,dc=rfmail
Result: Success (0)
四、修改操做
修改内容一般由LDIF 文件提供。所以,可先用ldapsearch 导出节点内容:
# ldapsearch -x -LLL -b 'dc=rfmail' '(objectclass=*)' 'dn' 'o' > example_dn.ldif
-LLL 表示不输出注释内容,以便后续从新导入。
用vi 修改.ldif 文件,内容改成:
引用
# cat example_dn.ldif
dn: dc=rfmail
o: linuxfly.com
而后,执行下面的命令从新导入:
# ldapmodify -x -D 'cn=root,dc=rfmail' -w 'linuxfly.com' -f example_dn.ldif
查看结果:
引用
# ldapsearch -x -LLL -b 'dc=rfmail' '(objectclass=*)'
dn: dc=rfmail
objectClass: top
objectClass: dcObject
objectClass: organization
dc: rfmail
o: linuxfly.com
※ 注意:
引用
a、ldapadd 与ldapmodify -a 做用相同
b、若是在添加或修改时,报Naming violation等错误,则说明添加或修改的内容不符合schema中定义的对象属性规范,需修改后才能从新操做。
五、删除操做
删除时,给出DN便可:
# ldapdelete -x -D 'cn=root,dc=rfmail' -w 'linuxfly.com' -r 'dc=rfmail'
-r 表示以递归模式删除,即删除该节点下面的全部子节点。
4、使用php操做LDAP
用openldap-clients 操做LDAP可知足简单的查询、修改须要,但若须要更进一步的操做,建议用php、perl 等实现。下面以php 为例。
一、绑定服务器
- $l_host="ldap://192.168.228.135"; //服务器IP
- $l_port='389';
- $l_loginpw='linuxfly.com';
- $l_logindn='cn=root,dc=rfmail';
- $l_root_dn='dc=rfmail';
- $l_conn=ldap_connect($l_host,$l_port) or die('Connect error!');
- $boo=ldap_bind($l_conn,$l_logindn,$l_loginpw);
- if ($boo)
- echo "成功绑定源服务器!\n";
- else {
- echo "绑定源服务器失败\n";
- exit;
- }
- ldap_unbind($l_conn);
运行状况:
引用
# php ldap_test.php
成功绑定源服务器!
※ 注意,在个人运行环境(php 5.1.6)中,虽然slapd.conf已设置为{SHA}方式,但在php中提供的登录密码仍只需使用明文便可,而不要使用{SHA}的字符串形式。我在这里耽搁了很长时间,详细可见附录说明。
二、读取操做
在ldap_unbind()的前面增长以下语句:
- $l_filter='(objectClass=*)';
- $justthese=array('dn','o');
- $search_id=ldap_search($l_conn,$l_root_dn,$l_filter,$justthese);
- $l_entries = ldap_get_entries($l_conn,$search_id);
- print_r($l_entries);
这个与ldap_search的使用基本一致的,ldap_get_entries()可返回由数组组成的所有匹配值的信息:
引用
# php ldap_test.php
成功绑定源服务器!
Array
(
[count] => 1
[0] => Array
(
[o] => Array
(
[count] => 1
[0] => linuxfly.com
)
[0] => o
[count] => 1
[dn] => dc=rfmail
)
)
若匹配的数据很大,ldap_get_entries()返回的数组可能会超出内存限制。这时,可改用ldap_first_entry()方法:
- $search_id=ldap_search($l_conn,$l_root_dn,$l_filter,$justthese);
- $l_entry = ldap_first_entry($l_conn,$search_id);
- if ($l_entry) {
- do {
- $l_o = ldap_get_values($l_conn,$l_entry,'o');
- var_dump($l_entry,$l_o);
- } while ($l_entry=ldap_next_entry($l_conn,$l_entry));
- }
结果:
引用
# php ldap_test.php
成功绑定源服务器!
resource(6) of type (ldap result entry)
array(2) {
[0]=>
string(12) "linuxfly.com"
["count"]=>
int(1)
}
三、修改操做
与openldap-clients 套件提供的工具操做方式不一样,PHP中对LDAP的修改和添加操做是分开的。用于修改的函数有两个ldap_modify和ldap_ mod_ replace,前者用于修改节点,后者用于修改属性。因为ldap_modify也可用于直接修改属性,故ldap_modify 较经常使用。
这两个函数都只接受数组做为参数,表示新的属性值。
- $new_o=array('o'=>'linuxfly.org');
- $boo = ldap_modify($l_conn,$l_root_dn,$new_o);
- if (!$boo) {
- echo "Modify fail.\n";
- } else {
- echo "Modify successfully.\n";
- }
运行结果:
引用
# php ldap_test.php
成功绑定源服务器!
Modify successfully.
# ldapsearch -x -LLL -D cn=root,dc=rfmail -w linuxfly.com -b 'dc=rfmail'
dn: dc=rfmail
objectClass: top
objectClass: dcObject
objectClass: organization
dc: rfmail
o: linuxfly.org
四、添加操做
一样的,添加操做也有两个函数,ldap_ add 和ldap_mod_add,前者用于添加节点,后者用于添加属性。但受限于schema规则限定,与修改操做不一样,两个函数一般不能混用。
a、添加一个新的节点
代码:
- $add_entry=array();
- $add_entry['dc']='linuxfly.com';
- $add_entry['objectclass']='masterdomain';
- $add_entry['kvcount']=0;
- $new_dn='dc=linuxfly.com,dc=rfmail';
- $boo = ldap_add($l_conn,$new_dn,$add_entry);
- if (!$boo) {
- echo "Add fail.\n";
- } else {
- echo "Add successfully.\n";
- }
※ 使用ldap_add 时,需注意:
引用
1)须要用新添加的节点来定义新的DN值,并在后面的数组中有对应的值对;在上面,DN就是 dc=linuxfly.com,dc=rfmail 和$add_entry['dc']='linuxfly.com' 数组元素;
2)objectclass 定义的对象不能缺乏,写成$add_entry['objectClass']='masterdomain' 也能够。
结果:
引用
# php ldap_test.php
成功绑定源服务器!
Add successfully.
# ldapsearch -x -LLL -D cn=root,dc=rfmail -w linuxfly.com -b 'dc=rfmail'
dn: dc=rfmail
objectClass: top
objectClass: dcObject
objectClass: organization
dc: rfmail
o: linuxfly.org
dn: dc=linuxfly.com,dc=rfmail
dc: linuxfly.com
objectClass: masterdomain
kvcount: 0
b、给节点添加新属性
LDAP中,同一个节点同一个属性是能够拥有多个值的。
- $add_entry2=array();
- $add_entry2['operateUserList']=array('freeze','active');
- $new_dn='dc=linuxfly.com,dc=rfmail';
- $boo = ldap_mod_add($l_conn,$new_dn,$add_entry2);
- if (!$boo) {
- echo "Add attributes values fail.\n";
- } else {
- echo "Add attributest values successfully.\n";
- }
※ 使用ldap_mod_add 时,需注意:
引用
1)不能用ldap_add代替,不然会报下面的错误
ldap_add(): Add: Internal (implementation specific) error
2)同一节点下,虽然能够有多个属性,但属性值不能重复,不然会报:
ldap_mod_add(): Modify: Type or value exists
(您能够再运行一次上面的脚本试试。)
3)ldap_mod_add() 与ldap_mod_replace() 是不一样的,前者添加,后者替换指定的属性值;不过,两函数都要求输入的数组元素值惟一,而且从0开始按顺序排列,不然,会报:
ldap_mod_add(): Value array must have consecutive indices 0, 1, ...
解决办法,见附录说明。
结果:
引用
# php ldap_test.php
成功绑定源服务器!
Add attributes values successfully.
# ldapsearch -x -LLL -D cn=root,dc=rfmail -w linuxfly.com -b 'dc=linuxfly.com,dc=rfmail'
dn: dc=linuxfly.com,dc=rfmail
dc: linuxfly.com
objectClass: masterdomain
kvcount: 0
operateUserList: freeze
operateUserList: active
五、删除操做
与添加相似,PHP对LDAP的删除操做可分为对节点和属性两个函数ldap_delete()、ldap_mod_del():
- $delete_attr['operateUserList']='freeze';
- $delete_dn='dc=linuxfly.com,dc=rfmail';
- $boo = ldap_mod_del($l_conn,$delete_dn,$delete_attr);
- if (!$boo) {
- echo "Delete attributes values fail.\n";
- } else {
- echo "Delete attributes values sucessfully.\n";
- }
-
- $boo = ldap_delete($l_conn,$delete_dn);
- if (!$boo) {
- echo "Delete entry fail.\n";
- } else {
- echo "Delete entry successfully.\n";
- }
※ 注意:使用ldap_mod_del() 不能删除关键RDN及objectClass等属性。
执行结果:
引用
# php ldap_test.php
成功绑定源服务器!
Delete attributes values sucessfully.
# ldapsearch -x -LLL -D cn=root,dc=rfmail -w linuxfly.com -b 'dc=linuxfly.com,dc=rfmail'
dn: dc=linuxfly.com,dc=rfmail
dc: linuxfly.com
objectClass: masterdomain
kvcount: 0
operateUserList: active
# php ldap_test.php
成功绑定源服务器!
Delete entry successfully.
# ldapsearch -x -LLL -D cn=root,dc=rfmail -w linuxfly.com -b 'dc=rfmail'
dn: dc=rfmail
objectClass: top
objectClass: dcObject
objectClass: organization
dc: rfmail
o: linuxfly.org
5、附录
一、SHA、SSHA等用户名密码问题
slappasswd 命令可建立SHA、SSHA等结构的密码,默认为SSHA方式,建立方法可参考上面的描述。
使用时,需分两种状况:
引用
a、虽然在sldapd.conf 的rootpw 部分设定该密码串,但在PHP bind()提供$passwd验证串时,只需提供明文形式的密码,而无需提供{SHA}等格式,不然验证会失败,报Invalid credentials 错误;
b、当把{SHA}等方式用于普通属性值对时,PHP 需提供{SHA}形式的、彻底一致的字符串,不然匹配将失败。
根据PHP的不一样版本,可用下面的形式得到{SHA}结构的密码串:
- $userpw="{SHA}".base64_encode(sha1('linuxfly.com',TRUE));
- 或
- $userpw="{SHA}".base64_encode(pack("H*",sha1('linuxfly.com')));
详情请参考:What are {SHA} and {SSHA} passwords and how do I generate them 一文。
二、ldap_add() 、ldap_mod_add() 等函数可接受的数组问题
ldap_add()、ldap_mod_add()、ldap_modify()、ldap_mod_repalce()等函数用于添加或修改节点、属性值对。在为它们提供新(或需修改)的属性时,必须以数组形式。并且,该数组元素必须惟一,并且从0序号开始逐一增长。相似下面的数组将不符合要求:
- $add_entry['uid']=array('1','0','1','2');
- $mod_entry['uid']=array(1=>'1',3=>'2');
为知足要求,一般可以使用array_unique() 和 array_slice()协助咱们:
- $add_entry['uid']=array_unique($add_entry['uid']);
- $mod_entry['uid']=array_slice($mod_entry['uid'],0);
三、中文字符问题
LDAP 中不容许保存GB2312或GB18030字符串,要保存这些字符集,必须用base64或unicode转码。PHP有提供base64_encode()和base64_decode()函数,unicode转码可参考网上资料。
不过,LDAP可接受UTF-8 格式的中文字符。也就是说,您能够把GB2312或GB18030字符串,经过iconv()等函数转为UTF-8 后直接保存到LDAP中。
- $utf8_string=iconv('GBK','UTF-8//TRANSLIT',$string);
记住,当用ldapsearch输出LDAP内容时,UTF-8 编码的字符串将会自动以base64形式输出,需手动转码:
引用
# echo -n 'TGludXhGbHnpo5jmiaw='|base64 -d|iconv -f utf8 -t gb18030
LinuxFly飘扬
四、节点间关系
LDAP没有冗余性或惟一性要求,各节点除了上下层(父子层)关系外,没有特殊的要求。
所以,对于有特定要求的应用,例如,两个节点没有直接上下层关系,但某个节点属于另外一个节点时(用户属于某个组)。这一般需利用LDAP节点可拥有多个属性值的特性,由程序来实现。例如,把用户的uid写入组节点的uid属性,多个用户属于该组,就在该组写入多个属性:
五、搜索匹配
上面提供的ldapsearch或ldap_search()所使用的filter都很简单,实际上,该匹配方式有不少,例如:
引用
'(|(objectclass=user)(objectclass=person)' //表示或关系
'(&(objectclass=user)(objectclass=person)' //表示和关系
'(|(objectclass=user)(!(objectclass=person))' //表示或、非关系
(&(|(objectclass=user)(objectclass=person)(objectclass=inetOrgPerson)
(objectclass=organizationalPerson))(!(objectclass=computer))) //更复杂的类型
请留意写法,更详细的内容,可参考:自定义 LDAP 过滤器和属性 和 LDAP 查询基本知识。
六、phpldapadmin工具
有一个叫phpldapadmin的工具,其有点相似phpmyadmin,可提供图形化的管理LDAP。配置很简单,解压后,修改/etc/httpd/conf.d/phpldapadmin.conf 文件为:
引用
# cat phpldapadmin.conf
Alias /phpldapadmin /usr/share/phpldapadmin/htdocs
Alias /ldapadmin /usr/share/phpldapadmin/htdocs
<Directory /usr/share/phpldapadmin/htdocs>
Order Deny,Allow
#Deny from all
#Allow from 127.0.0.1
Allow from all
</Directory>
而后启动Apache,访问http://ip/phpldapadmin ,输入登陆DN和密码便可。需留意的是,EPEL上提供的phpldapadmin-1.0.1-1.el5.rpm 彷佛有点问题,反正我是没法登录成功,改用其余版本没有问题。
另外,phpladpadmin受限于PHP的限制,若LDAP数据库很大,可能没法彻底打开,并报内存不足的问题,故通常仅做简单使用。
七、测试脚本
上述操做的测试脚本:

下载文件
先配置好openldap,解压上述测试脚本包后,导入数据,并运行:
# ldapadd -x -D 'cn=root,dc=rfmail' -w linuxfly.com -f example_init.ldif
# php ldap_test.php
6、参考资料
LDAP Administration Guide
(至关详细的介绍LDAP的online EBook)
PHP Manual: Lightweight Directory Access Protocol
OpenLDAP Faq-: What are {SHA} and {SSHA} passwords and how do I generate them
自定义 LDAP 过滤器和属性
LDAP 查询基本知识
OpenLDAP user - edit details and/or password
(该文提供了一个SSHA格式密码的实现代码)
OpenLDAP 2.2 Administrator's Guide: Using SASL
openldap经常使用命令
(该文只简略浏览了一下,没细看,但其中启用sasl 验证、配置服务器复制部分值得参考)
使用 OpenLDAP 集中管理用户账号
(IBM 提供的一篇关于OpenLDAP 配置的文档,部份内容值得参考)
使用 PHP 建立 LDAP 目录服务(这是无心中找到的一篇文章,描述用PHP操做Oracle Internet Directory ,没细看,但应该有值得参考的地方)