zoukankan      html  css  js  c++  java
  • ldap 数据库

    原文地址:  http://www.linuxfly.org/post/569/

     

      好久以前曾写过[原]LDAP服务介绍一文,其中介绍了LDAP服务的基本情况。最近,因项目原因,需要对某个ldap 数据库进行修改和迁移的工作,目前已基本完成。特此机会,把在bash和php 中操作ldap 的注意事项简单总结一下。

    一、基础知识
    首先,如果您对LDAP 不认识,建议先看看[原]LDAP服务介绍一文。本文以Linux 下常用的OpenLDAP为例说明。
    LDAP 以数方式存放数据,每个节点可存放属性或作为下面节点的父节点。DN是作为某个节点的唯一标识,例如:

    ou=mail.linuxfly.org,dc=linuxfly,dc=org


    操作时,必须指定DN的位置。与普通数据库类似,操作LDAP,主要就是添加、删除或修改节点、其属性值等。
    属性必须要遵循.schema模式文件中定义的规则,这些文件通常放在/etc/openldap/schema/目录下,在/etc/openldap/sldapd.conf 中载入后生效,并且对载入顺序有一定的要求。

    引用
    # 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 。

    二、登陆信息
    当需要修改或添加ldap 数据时,需要提供登陆信息,这些信息在/etc/openldap/sldap.conf 中提供:

    引用
    database        bdb #后台数据库类型
    suffix "dc=rfmail"  #目录树后缀
    rootdn "cn=root,dc=rfmail"  #管理员DN信息
    rootpw linuxfly.com #明文密码


    若已经安装BerkeleyDB数据库,则可把database设置为dbd形式,否则,可用ldbm 。(数据较慢)
    另外,密码可使用slappasswd 命令设定:

    引用
    # 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


    三、使用openldap-clients 套件操作ldap
    操作LDAP的方式有很多,下面以bash下用openldap-clients 套件提供的工具来进行。
    1、初始化数据
    这一步通常是由特定的应用程序根据其自身使用的需要创建的。包括两部分的内容:

    引用
    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 命令各参数含义如下:

    引用
    -x 为使用简单密码验证方式
    -D 指定管理员DN(与slapd.conf中一致)
    -W 为管理员密码,可以使用-w password 直接输入密码
    -f 为初始化数据LDIF的文件名
    -h 为操作的服务器IP地址


    2、搜索操作
    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 的不同。前者为搜索匹配,后者为限制显示的属性值。

    3、身份验证
    修改或添加内容需进行用户验证,可通过下面的命令确认验证信息:

    引用
    # ldapwhoami -x -D 'cn=root,dc=rfmail' -w 'linuxfly.com'
    dn:cn=root,dc=rfmail
    Result: Success (0)


    4、修改操作
    修改内容通常由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中定义的对象属性规范,需修改后才能重新操作。


    5、删除操作
    删除时,给出DN即可:

    # ldapdelete -x -D 'cn=root,dc=rfmail' -w 'linuxfly.com' -r 'dc=rfmail'


    -r 表示以递归模式删除,即删除该节点下面的所有子节点。

    四、使用php操作LDAP
    用openldap-clients 操作LDAP可满足简单的查询、修改需要,但若需要更进一步的操作,建议用php、perl 等实现。下面以php 为例。
    1、绑定服务器

    1. $l_host="ldap://192.168.228.135"; //服务器IP  
    2. $l_port='389'//LDAP服务端口  
    3. $l_loginpw='linuxfly.com'//登陆密码,无论slapd.conf中是明文或{SHA}方式,这里都用明文  
    4. $l_logindn='cn=root,dc=rfmail'//登陆DN  
    5. $l_root_dn='dc=rfmail'//根目录 suffix  
    6. $l_conn=ldap_connect($l_host,$l_portor die('Connect error!');  
    7. $boo=ldap_bind($l_conn,$l_logindn,$l_loginpw);  
    8. if ($boo)  
    9.     echo "成功绑定源服务器!\n";  
    10.  else {  
    11.     echo "绑定源服务器失败\n";  
    12.     exit;  
    13. }  
    14. ldap_unbind($l_conn);  


    运行情况:

    引用
    # php ldap_test.php
    成功绑定源服务器!


    ※ 注意,在我的运行环境(php 5.1.6)中,虽然slapd.conf已设置为{SHA}方式,但在php中提供的登陆密码仍只需使用明文即可,而不要使用{SHA}的字符串形式。我在这里耽搁了很长时间,详细可见附录说明。

    2、读取操作
    在ldap_unbind()的前面增加如下语句:

    1. $l_filter='(objectClass=*)'//设置过滤  
    2. $justthese=array('dn','o'); //设置输出属性  
    3. $search_id=ldap_search($l_conn,$l_root_dn,$l_filter,$justthese);  
    4. $l_entries = ldap_get_entries($l_conn,$search_id);  
    5. 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()方法:

    1. $search_id=ldap_search($l_conn,$l_root_dn,$l_filter,$justthese);  
    2. $l_entry = ldap_first_entry($l_conn,$search_id);  
    3. if ($l_entry) {  
    4.     do {  
    5.         $l_o = ldap_get_values($l_conn,$l_entry,'o');  
    6.         var_dump($l_entry,$l_o);  
    7.     } while ($l_entry=ldap_next_entry($l_conn,$l_entry));  
    8. }  


    结果:

    引用
    # php ldap_test.php
    成功绑定源服务器!
    resource(6) of type (ldap result entry)
    array(2) {
      [0]=>
      string(12) "linuxfly.com"
      ["count"]=>
      int(1)
    }


    3、修改操作
    与openldap-clients 套件提供的工具操作方式不同,PHP中对LDAP的修改和添加操作是分开的。用于修改的函数有两个ldap_modify和ldap_ mod_ replace,前者用于修改节点,后者用于修改属性。由于ldap_modify也可用于直接修改属性,故ldap_modify 较常用。
    这两个函数都只接受数组作为参数,表示新的属性值。

    1. $new_o=array('o'=>'linuxfly.org');  
    2. $boo = ldap_modify($l_conn,$l_root_dn,$new_o);  
    3. if (!$boo) {  
    4.     echo "Modify fail.\n";  
    5. else {  
    6.     echo "Modify successfully.\n";  
    7. }  


    运行结果:

    引用
    # 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


    4、添加操作
    同样的,添加操作也有两个函数,ldap_ add 和ldap_mod_add,前者用于添加节点,后者用于添加属性。但受限于schema规则限定,与修改操作不同,两个函数通常不能混用。
    a、添加一个新的节点
    代码:

    1. $add_entry=array();  
    2. $add_entry['dc']='linuxfly.com';  
    3. $add_entry['objectclass']='masterdomain';  
    4. $add_entry['kvcount']=0;  
    5. $new_dn='dc=linuxfly.com,dc=rfmail';  
    6. $boo = ldap_add($l_conn,$new_dn,$add_entry);  
    7. if (!$boo) {  
    8.     echo "Add fail.\n";  
    9. else {  
    10.     echo "Add successfully.\n";  
    11. }  


    ※ 使用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中,同一个节点同一个属性是可以拥有多个值的。

    1. $add_entry2=array();  
    2. $add_entry2['operateUserList']=array('freeze','active');  
    3. $new_dn='dc=linuxfly.com,dc=rfmail';  
    4. $boo = ldap_mod_add($l_conn,$new_dn,$add_entry2);  
    5. if (!$boo) {  
    6.         echo "Add attributes values fail.\n";  
    7. else {  
    8.         echo "Add attributest values successfully.\n";  
    9. }  


    ※ 使用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


    5、删除操作
    与添加类似,PHP对LDAP的删除操作可分为对节点和属性两个函数ldap_delete()、ldap_mod_del():

    1. $delete_attr['operateUserList']='freeze';  
    2. $delete_dn='dc=linuxfly.com,dc=rfmail';  
    3. $boo = ldap_mod_del($l_conn,$delete_dn,$delete_attr);  
    4. if (!$boo) {  
    5.     echo "Delete attributes values fail.\n";  
    6. else {  
    7.     echo "Delete attributes values sucessfully.\n";  
    8. }  
    9.   
    10. $boo = ldap_delete($l_conn,$delete_dn);  
    11. if (!$boo) {  
    12.     echo "Delete entry  fail.\n";  
    13. else {  
    14.     echo "Delete entry  successfully.\n";  
    15. }  


    ※ 注意:使用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


    五、附录
    1、SHA、SSHA等用户名密码问题
    slappasswd 命令可创建SHA、SSHA等结构的密码,默认为SSHA方式,创建方法可参考上面的描述。
    使用时,需分两种情况:

    引用
    a、虽然在sldapd.conf 的rootpw 部分设定该密码串,但在PHP bind()提供$passwd验证串时,只需提供明文形式的密码,而无需提供{SHA}等格式,否则验证会失败,报Invalid credentials 错误;
    b、当把{SHA}等方式用于普通属性值对时,PHP 需提供{SHA}形式的、完全一致的字符串,否则匹配将失败。


    根据PHP的不同版本,可用下面的形式获得{SHA}结构的密码串:

    1. $userpw="{SHA}".base64_encode(sha1('linuxfly.com',TRUE));  
    2. 或  
    3. $userpw="{SHA}".base64_encode(pack("H*",sha1('linuxfly.com')));  


    详情请参考:What are {SHA} and {SSHA} passwords and how do I generate them 一文。

    2、ldap_add() 、ldap_mod_add() 等函数可接受的数组问题
    ldap_add()、ldap_mod_add()、ldap_modify()、ldap_mod_repalce()等函数用于添加或修改节点、属性值对。在为它们提供新(或需修改)的属性时,必须以数组形式。而且,该数组元素必须唯一,而且从0序号开始逐一增加。类似下面的数组将不符合要求:

    1. $add_entry['uid']=array('1','0','1','2');  
    2. $mod_entry['uid']=array(1=>'1',3=>'2');  


    为满足要求,通常可使用array_unique() 和 array_slice()协助我们:

    1. $add_entry['uid']=array_unique($add_entry['uid']);  
    2. $mod_entry['uid']=array_slice($mod_entry['uid'],0); //重排序号  


    3、中文字符问题
    LDAP 中不允许保存GB2312或GB18030字符串,要保存这些字符集,必须用base64或unicode转码。PHP有提供base64_encode()和base64_decode()函数,unicode转码可参考网上资料。
    不过,LDAP可接受UTF-8 格式的中文字符。也就是说,您可以把GB2312或GB18030字符串,通过iconv()等函数转为UTF-8 后直接保存到LDAP中。

    1. $utf8_string=iconv('GBK','UTF-8//TRANSLIT',$string);  


    记住,当用ldapsearch输出LDAP内容时,UTF-8 编码的字符串将会自动以base64形式输出,需手动转码:

    引用
    # echo -n 'TGludXhGbHnpo5jmiaw='|base64 -d|iconv -f utf8 -t gb18030
    LinuxFly飘扬


    4、节点间关系
    LDAP没有冗余性或唯一性要求,各节点除了上下层(父子层)关系外,没有特殊的要求。
    因此,对于有特定要求的应用,例如,两个节点没有直接上下层关系,但某个节点属于另一个节点时(用户属于某个组)。这通常需利用LDAP节点可拥有多个属性值的特性,由程序来实现。例如,把用户的uid写入组节点的uid属性,多个用户属于该组,就在该组写入多个属性:

    引用
    uid: 1
    uid: 2
    uid: 3


    5、搜索匹配
    上面提供的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 查询基本知识

    6、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数据库很大,可能无法完全打开,并报内存不足的问题,故一般仅作简单使用。

    7、测试脚本
    上述操作的测试脚本:


    先配置好openldap,解压上述测试脚本包后,导入数据,并运行:

    # ldapadd -x -D 'cn=root,dc=rfmail' -w linuxfly.com -f example_init.ldif
    # php ldap_test.php


    六、参考资料
    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 ,没细看,但应该有值得参考的地方)

    知识共享许可协议
    作品Tim Zhang创作,采用知识共享署名 3.0 中国大陆许可协议进行许可。 。
  • 相关阅读:
    缩点【洛谷P1262】 间谍网络
    模板-割点
    Tarjan缩点+LCA【洛谷P2416】 泡芙
    模拟赛 10-20考试记
    BFS【bzoj1667】: [Usaco2006 Oct]Cows on Skates滑旱冰的奶牛
    最短路【bzoj2464】: 中山市选[2009]小明的游戏
    linux /dev/mapper/centos-root 被占满
    Centos7中安装Mysql8并修改密码策略并远程连接
    Centos7中PHP编译安装mysqli扩展报错
    Linux中Composer 在安装依赖包与本地php版本不符问题
  • 原文地址:https://www.cnblogs.com/ccdc/p/2645808.html
Copyright © 2011-2022 走看看