系统工具 | LDAP 使用教程篇

LDAP 使用教程篇

Posted by Aiden on May 8, 2019

LDAP 是一个为查询、浏览和搜索而优化的专业分布式数据库,它成树状结构组织数据,就好象Linux/Unix系统中的文件目录一样。 目录数据库和关系数据库不同,它有优异的读性能,但写性能差,并且没有事务处理、回滚等复杂功能,不适于存储修改频繁的数据。所以目录天生是用来查询的,就好象它的名字一样。 目录服务是由目录数据库和一套访问协议组成的系统。类似以下的信息适合储存在目录中:

  • 企业员工和企业客户之类人员信息;
  • 公用证书和安全密钥;
  • 邮件地址、网址、IP等电脑信息;
  • 电脑配置信息。 …

Ldap 数据模型

每一个系统、协议都会有属于自己的模型,LDAP也不例外,在了解LDAP的基本模型之前我们需要先了解几个LDAP的目录树概念

目录树概念

  1. 目录树:在一个目录服务系统中,整个目录信息集可以表示为一个目录信息树,树中的每个节点是一个条目(Entry)。
  2. 条目:每个条目就是一条记录,每个条目有自己的唯一可区别的名称(DN)。
  3. 对象类:与某个实体类型对应的一组属性,对象类是可以继承的,这样父类的必须属性也会被继承下来(ObjectClass)。
  4. 属性:描述条目的某个方面的信息,一个属性由一个属性类型和一个或多个属性值组成,属性有必须属性和非必须属性(Attribute)。

image.png

基本概念

在浏览LDAP相关文档时经常会遇见一些概念,下面是常见概念的简单解释。

条目 Entry

条目,也叫记录项,是LDAP中最基本的颗粒,就像字典中的词条,或者是数据库中的记录。通常对LDAP的添加、删除、更改、检索都是以条目为基本对象的。

dn:每一个条目都有一个唯一的标识名(distinguished Name ,DN),如上图中一个 dn:”cn=baby,ou=marketing,ou=people,dc=mydomain,dc=org” 。通过DN的层次型语法结构,可以方便地表示出条目在LDAP树中的位置,通常用于检索。

rdn:一般指dn逗号最左边的部分,如cn=baby。它与RootDN不同,RootDN通常与RootPW同时出现,特指管理LDAP中信息的最高权限用户。

Base DN:LDAP目录树的最顶部就是根,也就是所谓的“Base DN”,如”dc=mydomain,dc=org”。

属性 Attribute

每个条目都可以有很多属性(Attribute),比如常见的人都有姓名、地址、电话等属性。每个属性都有名称及对应的值,属性值可以有单个、多个,比如你有多个邮箱。

属性不是随便定义的,需要符合一定的规则,而这个规则可以通过schema制定。比如,如果一个entry没有包含在 inetorgperson 这个 schema 中的 objectClass: inetOrgPerson ,那么就不能为它指定employeeNumber属性,因为employeeNumber是在inetOrgPerson中定义的。

LDAP为人员组织机构中常见的对象都设计了属性(比如commonName,surname)。下面有一些常用的别名:

属性 语法 描述 值(举例)
cn Directory String 姓名 sean
sn Directory String Chow
ou Directory String 单位(部门)名称 IT_SECTION
o Directory String 组织(公司)名称 example
telephoneNumber Telephone Number 电话号码 110
objectClass   内置属性 organizationalPerson
对象类 ObjectClass

对象类是属性的集合,LDAP预想了很多人员组织机构中常见的对象,并将其封装成对象类。比如人员(person)含有姓(sn)、名(cn)、电话(telephoneNumber)、密码(userPassword)等属性,单位职工(organizationalPerson)是人员(person)的继承类,除了上述属性之外还含有职务(title)、邮政编码(postalCode)、通信地址(postalAddress)等属性。

通过对象类可以方便的定义条目类型。每个条目可以直接继承多个对象类,这样就继承了各种属性。如果2个对象类中有相同的属性,则条目继承后只会保留1个属性。对象类同时也规定了哪些属性是基本信息,必须含有(Must 活Required,必要属性):哪些属性是扩展信息,可以含有(May或Optional,可选属性)。

对象类有三种类型:结构类型(Structural)抽象类型(Abstract)辅助类型(Auxiliary)

  • 结构类型是最基本的类型,它规定了对象实体的基本属性,每个条目属于且仅属于一个结构型对象类。
  • 抽象类型可以是结构类型或其他抽象类型父类,它将对象属性中共性的部分组织在一起,称为其他类的模板,条目不能直接集成抽象型对象类。
  • 辅助类型规定了对象实体的扩展属性。每个条目至少有一个结构性对象类。

下面是 inetOrgPerson 对象类的在 schema 中的定义,可以清楚的看到它的父类SUB和可选属性MAY、必要属性MUST(继承自 organizationalPerson ),关于各属性的语法则在schema中的attributetype定义。

# inetOrgPerson
# The inetOrgPerson represents people who are associated with an
# organization in some way.  It is a structural class and is derived
# from the organizationalPerson which is defined in X.521 [X521].
objectclass     ( 2.16.840.1.113730.3.2.2
  NAME 'inetOrgPerson'
      DESC 'RFC2798: Internet Organizational Person'
  SUP organizationalPerson
  STRUCTURAL
      MAY (
              audio $ businessCategory $ carLicense $ departmentNumber $
              displayName $ employeeNumber $ employeeType $ givenName $
              homePhone $ homePostalAddress $ initials $ jpegPhoto $
              labeledURI $ mail $ manager $ mobile $ o $ pager $
              photo $ roomNumber $ secretary $ uid $ userCertificate $
              x500uniqueIdentifier $ preferredLanguage $
              userSMIMECertificate $ userPKCS12 )
      )
Schema

对象类(ObjectClass)、属性类型(AttributeType)、语法(Syntax)分别约定了条目、属性、值,他们之间的关系如下图所示。 所以这些构成了模式(Schema)——对象类的集合。 条目数据在导入时通常需要接受模式检查,它确保了目录中所有的条目数据结构都是一致的。

schema(一般在/etc/ldap/schema/目录)在导入时要注意前后顺序。

backend & database

ldap的后台进程是由slapd 接收、响应请求,但实际存储数据、获取数据的操作是由Backends做的,而数据是存放在database中,所以你可以看到往往你可以看到backenddatabase指令是一样的值如 bdb 。

一个 backend 可以有多个 database instance,但每个 database 的 suffix 和 rootdn 不一样。openldap 2.4版本的模块是动态加载的,所以在使用backend时需要 moduleload back_bdb指令。

bdb是一个高性能的支持事务和故障恢复的数据库后端,可以满足绝大部分需求。许多旧文档里(包括官方)说建议将bdb作为首选后端服务(primary backend),但2.4版文档明确说hdb才是被首先推荐使用的,这从 2.4.40 版默认安装后的配置文件里也可以看出。hdb是基于bdb的,但是它通过扩展的索引和缓存技术可以加快数据访问,修改entries会更有效率,有兴趣可以访问上的链接或 slapd.backends

另外config是特殊的backend,用来在运行时管理slapd的配置,它只能有一个实例,甚至无需显式在 slapd.conf 中配置。

TLS & SASL

分布式LDAP 是以明文的格式通过网络来发送信息的,包括client访问ldap的密码(当然一般密码已然是二进制的),SSL/TLS 的加密协议就是来保证数据传送的保密性和完整性。

SASL (Simple Authenticaion and Security Layer)简单身份验证安全框架,它能够实现openldap客户端到服务端的用户验证,也是ldapsearch、ldapmodify这些标准客户端工具默认尝试与LDAP服务端认证用户的方式(前提是已经安装好 Cyrus SASL)。SASL有几大工业实现标准:Kerveros V5、DIGEST-MD5、EXTERNAL、PLAIN、LOGIN。

Kerveros V5是里面最复杂的一种,使用GSSAPI机制,必须配置完整的Kerberos V5安全系统,密码不再存放在目录服务器中,每一个dn与Kerberos数据库的主体对应。DIGEST-MD5稍微简单一点,密码通过saslpasswd2生成放在sasldb数据库中,或者将明文hash存到LDAP dn的userPassword中,每一个authid映射成目录服务器的dn,常和SSL配合使用。参考将 LDAP 客户端配置为使用安全性

EXTERNAL一般用于初始化添加schema时使用,如 ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/openldap/schema/core.ldif

其他
关键字 英文全称 含义
dc Domain Component 域名的部分,其格式是将完整的域名分成几部分,如域名为example.com变成dc=example,dc=com(一条记录的所属位置)
uid User Id 用户ID songtao.xu(一条记录的ID)

使用 ldapsearch 查询

要使用 ldapsearch 工具首先要下载 openldap-clients

yum -y install openldap-clients

ldapsearch 参数列表

参数 用途
-? 打印关于使用 ldapsearch 的帮助。
-a deref 指定别名反向引用。 请输入 never、always、search 或 find。 如果不使用此参数,那么缺省值为 never。
-A 只检索属性的名称,而不检索属性的值。
-b base dn 指定用作搜索起始点的专有名称。 使用引号来指定该值,例如:”ou=West,o=Renovations,c=US” 如果要搜索的服务器需要指定搜索起点,则必须使用此参数。 否则此参数是可选的。(可选)将 -s 与 -b 一起使用来确定搜索范围。没有 -s,-b 就会搜索指定为起始点的条目以及该条目的所有后代。
-B 允许打印非 ASCII 值
-D bind dn 指定服务器用于认证您的专有名称。 名称必须与目录中的条目相符,并且必须拥有搜索目录所需的权限。在引号中指定该名称,例如:”cn=Directory Manager,o=Renovations,c=US” 如果不使用此参数,则与服务器的连接是匿名的。 如果服务器不允许匿名连接,那么必须使用 -D。除了 -D,还必须使用 -w 参数来指定与专有名称相关联的密码。
-f file 指定包含要使用的搜索过滤器的文件,例如 -f 过滤器。请将每个搜索过滤器置于单独的一行。Ldapsearch 会对每一行执行一次搜索。 可选择指定过滤模式。 例如,指定 -f 过滤器 cn=%s,并在文件的每一行中输入公共名称的值。
-F sep 在属性名称和值之间打印 sep 而不是等号 (=)。例如,如果读取 ldapsearch 输出的工具希望使用其他的分隔符时,可以使用此参数。
-h host name 指定要连接到的服务器的主机名,例如,-h server.renovations.com。
-l timelimit 指定完成搜索的时间限制(秒)。 如果没有指定此参数或指定的限制为 0,那么搜索就没有时间限制。但是,ldapsearch 的等待时间决不会超过服务器上设置的搜索时间限制。
-L 指定以 LDIF 格式输出。 LDIF 格式使用冒号 (:) 而不是等号 (=) 作为属性描述符。 LDIF 对一次性添加或修改大量目录条目很有帮助。 例如,可以将输出内容导入兼容 LDAP 的目录中。
-M 将参考对象作为普通条目进行管理,以使 ldapsearch 可返回参考条目本身的属性,而不是所参考的条目的属性。
-n 显示如何执行搜索,但不实际执行搜索
-p port 指定服务器使用的端口。 如果没有使用此参数,缺省情况下 ldapsearch 使用 389 端口。
-R 不自动遵循服务器返回的搜索引用。
-s scope 指定使用 -b 参数时的搜索范围: base - 仅搜索使用 -b 参数指定的条目 onelevel - 仅搜索使用 -b 参数指定的条目的直接子条目,但不搜索该条目本身 subtree - 搜索使用 -b 参数指定的条目及其所有后代, 这是使用 -b 而不使用 -s 时的缺省行为。 指定 -b 和 -s 的顺序并不重要。
-S attribute 按指定的属性排序结果。
-z sizelimit 指定返回条目的最大数目。 如果没有指定此参数或指定的限制为 0,那么返回的条目没有数量限制。但是,ldapsearch 返回的条目决不会多于服务器允许的数量。
-u 指定 ldapsearch 以用户友好格式返回专有名称。
-v 指定 ldapsearch 以详尽方式运行。
-w password 指定与 -D 参数一起使用的专有名称的关联密码。
-x 与 -S 一起使用以指定该 LDAP 服务器在先对结果排序再将其返回。如果使用 -S 而不使用 -x,ldapsearch 将对结果排序。

使用搜索过滤器

搜索过滤器的语法为:"attribute operator value"

例如,下面的搜索过滤器可以找到所有的特定条目,只要该条目中以 Smith 作为 sn(姓)属性的值:"sn=Smith"

请注意,如果 LDAP 目录(如 Domino® 目录)支持语言标记,那么可以在搜索过滤器中包含语言标记。例如:"givenName;lang-fr=Etienne"

使用布尔运算符的多个搜索过滤器

您可以使用多个搜索过滤器以及布尔运算符。 使用下列语法: "(operator(filter)(filter))"

例如,使用下面的搜索过滤器查找姓为 Browning、位置为 Dallas 的条目。"(&(sn=Browning)(l=Dallas))"

布尔运算符可以嵌套。 例如,使用下面的搜索过滤器在邮件域 MDN 中查找 surname 为 Caneel 或 givenname 为 Alfred 的条目:"(&(maildomain=MDN)(|(sn=caneel)(givenname=alfred)))"

使用ldapsearch实例

  • 指定ldapuri进行查询:
ldapsearch -x -h 192.168.31.242 -p 389 -b dc=example,dc=org -D "cn=admin,dc=example,dc=org" -w admin
  • 指定hostname和端口号进行查询
ldapsearch -x -h 192.168.31.242 -p 389 -b dc=example,dc=org -D "cn=admin,dc=example,dc=org" -w admin
  • 指定过滤条件:按照dn进行过滤
ldapsearch -x -H ldap://192.168.31.242:389 -b dc=example,dc=org -D "cn=admin,dc=example,dc=org" -w admin "cn=admin"

Java SDK Client 查询方式:

通过 novell研发的jldap产品

<dependency>
    <groupId>com.novell.ldap</groupId>
    <artifactId>jldap</artifactId>
    <version>4.3</version>
</dependency>
public void start() throws LDAPException, UnsupportedEncodingException {
    LDAPConnection lc = new LDAPConnection();

    lc.connect("########",389);      // connect
    lc.bind(LDAPConnection.LDAP_V3, "###########", "########".getBytes("UTF8"));   // dn - password

    // search : base , scope, fileters, attr, name:value。
    LDAPSearchResults rs = lc.search("ou=acs,dc=mobvista,dc=com",LDAPConnection.SCOPE_SUB,"uid=tao.dong@mobvista.com",null,false);

    int count = 0;
    while(rs.hasMore()){

        LDAPEntry entry = rs.next();
        System.out.println(entry.getDN());
        System.out.println(">>>");
        Iterator<LDAPAttribute> iterator = entry.getAttributeSet().iterator();

        iterator.forEachRemaining(attr->System.out.println(attr.getName() + ":" + attr.getStringValue()));
        System.out.println("==============================");

        count++;
    }
    System.out.println("共有 ["+count+"] 条记录。");
}
通过 JDNI 访问 ldap :
 DirContext getDirContext() throws NamingException {

        // Set up the initial environment for LDAP connectivity
        Hashtable<String, String> env = new Hashtable<String, String>();
        env.put(Context.INITIAL_CONTEXT_FACTORY,
                com.sun.jndi.ldap.LdapCtxFactory.class.getName());
        env.put(Context.PROVIDER_URL, "########");
        env.put(Context.SECURITY_AUTHENTICATION, "simple");

        // Set up SSL security, if necessary
//        if (useSsl) {
//            env.put(Context.SECURITY_PROTOCOL, "ssl");
//            System.setProperty("javax.net.ssl.keyStore", keystore);
//            System.setProperty("javax.net.ssl.keyStorePassword", keystorePass);
//        }

        env.put(Context.SECURITY_PRINCIPAL, "########");
        env.put(Context.SECURITY_CREDENTIALS, "########");

        env.put("com.sun.jndi.ldap.connect.timeout", "100000");
        env.put("com.sun.jndi.ldap.read.timeout", "100000");

        DirContext ctx = new InitialDirContext(env);

        return ctx;
    }


    public void start() throws NamingException {

        DirContext c = getDirContext();
        SearchControls SEARCH_CONTROLS = new SearchControls();
        SEARCH_CONTROLS.setSearchScope(SearchControls.SUBTREE_SCOPE);

        NamingEnumeration<SearchResult> results = c.search("ou=acs,dc=mobvista,dc=com",
                "uid={0}", new Object[]{"tao.dong@mobvista.com"}, SEARCH_CONTROLS);
        // return empty list if the user can not be found.
        if (!results.hasMoreElements()) {
           return;
        }

        SearchResult result = results.nextElement();

        String uidAttribute = result.getAttributes().get("uidNumber").get().toString();
        String gidAttribute = result.getAttributes().get("gidNumber").get().toString();

        System.out.println("uid : " + uidAttribute);
        System.out.println("gid : " + gidAttribute);
    }
}

参考文档

LDAP服务器的概念和原理简单介绍 - Sean’s Notes - SegmentFault 思否

IBM Knowledge Center