一、前言

本文从介绍ldap入手,讲述了ldap的使用场合,并进一步的指导用户进行openldap安装与配置

二、目录服务简介

目录简单来说就是一种树状结构的数据库。
而目录服务是一种以树状结构的目录数据库为基础,外加各种访问协议的信息查询服务。
顾名思义,目录天生就是用来查询的。
与关系型数据库相比,目录服务最大的优点就是读取性能极高。
但是,目录服务的写入性能就非常差了,而且不支持事务处理等容错功能,因此不适合频繁修改数据。
在现实世界中,资源的分布形式很多都是树状的、有层次的。因此,目录服务有着非常广泛的用途。
像企业员工信息、企业设备信息、证书公钥等具有层次性、且不需要频繁改写的数据都适合使用目录服务来存储。
X.500 是 ISO 制定的一套目录服务的标准,它是一个协议族,定义了一个机构如何在全局范围内共享名称和与名称相关联的对象。通过它,可以将局部的目录服务连接起来,构建基于 Internet 的分布在全球的目录服务系统……而目录访问协议(DAP)是 X.500 的核心组成之一。

三、LDAP 简介

DAP 非常复杂,所以人们都不喜欢使用它。
所以,轻量级目录访问协议(LDAP)应运而生!LDAP 是基于 X.500 的 DAP 发展而来的,目前最新版本是第 3 版。
LDAP 协议的主要特点如下:

  • 基于 TCP/IP。
  • 以树状结构存储数据。
  • 读取速度快,写入速度慢。
  • 服务器用于存放数据,客户端用于操作数据。
  • 跨平台、维护简单。
  • 支持 SSL/TLS 加密。
  • 协议是开放的。

LDAP 的四大模型:

  • 信息模型:规定了 LDAP 目录信息的表示方式以及数据的存储结构。
  • 命名模型:规定了 LDAP 目录中数据如何进行组织和区分。
  • 功能模型:规定了可以对 LDAP 目录进行的操作(如查询、更新、认证等)。
  • 安全模型:规定了保护 LDAP 目录中的数据的安全措施。

基于 LDAP 协议的产品有很多,最有名两个的是开源的 OpenDirectory 以及微软开发的 ActiveDirectory。

四、LDAP 基本结构和相关术语

4.1 基本结构

LDAP 基础学习笔记 - 图1
上图表示的就是一颗 LDAP 目录树。

4.2 条目(Entry)

4.1 的图中的每一个方框就是一个条目。
一个条目有若干个属性和若干个值。有些条目还能包含子条目。

4.3 识别名(Distinguished Name, DN)

它表示条目在目录树中从根出发的绝对路径,是条目的唯一标识。
可以跟 UNIX 文件系统中文件或目录的完整路径做类比。
例如:4.1 的图中右下角的条目的 DN 是 cn=group1,dc=zenandidi,dc=com

4.4 相对识别名(Relative Distinguished Name, RDN)

相对识别名就是识别名第一个逗号左侧的内容。
可以跟 UNIX 文件系统中文件或目录名做类比。
例如:4.1 的图中右下角的条目的 RDN 是 cn=group1
在一般情况下,RDN 以 dc=ou=c=o= 开头的条目为容器。也就是说,它们可以包含子条目。

4.5 基准识别名(Base Distinguished Name, Base DN)

一般指整个目录树的根。
例如,4.1 的图的 Base DN 是 dc=zenandidi,dc=com

4.6 模式(Schema)

模式是对象类(ObjectClass)、属性类型(AttributeType)、属性语法(Syntax)和匹配规则(MatchingRules)的集合。
可以跟关系型数据库的数据表结构做类比。
LDAP 协议定义了一些标准的模式,一般直接使用即可。用户也可以根据自己的需求自行定义模式。
4.6.1 对象类(ObjectClass)
学过面向对象编程语言的人都知道,类是属性的封装。
对象类封装了必选的属性和可选的属性,同时对象类也是支持继承的。
通过对象类可以很方便地指定条目的类型。一个条目也可以绑定多个对象类。
对象类又分为了结构类型(Structural)、抽象类型(Abstract)、辅助类型(Auxiliary)这三类。
下图是一个对象类所包含的内容。
LDAP 基础学习笔记 - 图2
4.6.2 属性类型(AttributeType)
属性类型定义了属性值的设定规则(属性语法),以及同一个属性的各个数据相互比较的规则等。
下图是一个属性类型包含的内容。
LDAP 基础学习笔记 - 图3
4.6.3 属性语法(Syntax)
下图是 LDAP 协议预定义的一些属性语法,例如二进制、字符串、电话号码类型等。
LDAP 基础学习笔记 - 图4
4.6.4 匹配规则(MatchingRule)
这个我把它理解为各种类型属性的集合。
下图是 LDAP 协议预定义的一些匹配规则。
LDAP 基础学习笔记 - 图5

4.7 LDIF(LDAP Data Interchange Format) 文件

LDAP 数据交换格式文件,它以文本形式存储,用于在服务器之间交换数据。
添加数据以及修改数据都需要通过 LDIF 文件来进行。
可以跟关系型数据库的 SQL 文件做类比。
LDIF 文件的格式一般如下:

  1. dn: <识别名>
  2. <属性 1>: <值 1>
  3. <属性 2>: <值 2>
  4. ...

五、在 CentOS 7 上安装并使用 OpenLDAP

注意

  1. 不同版本的 Linux 安装方法差别非常大!
  2. 此次安装目的是快速搭建一个 LDAP 学习和测试环境,没有进行其他高级配置。建议使用虚拟机和全新安装的系统进行操作。

    5.1 所需软件 & 环境

  • 操作系统:CentOS 7.4.1708 最小安装(已关闭 SELinux 和防火墙)
  • 应用软件:openldap 2.4.44

    5.2 安装 OpenLDAP

    1. yum install -y openldap-clients openldap-servers

    5.3 创建数据库配置文件

    直接套用模版即可。

    1. cp /usr/share/openldap-servers/DB_CONFIG.example /var/lib/ldap/DB_CONFIG && chown ldap:ldap /var/lib/ldap/DB_CONFIG

    5.4 启动 OpenLDAP 服务

    1. systemctl start slapd

    如需开机启动,请执行以下命令。

    1. systemctl enable slapd

    如果没有出现错误,那么 OpenLDAP 的安装就算完成了,不过当前的目录数据库还是空的,下面我们要对数据库进行初始化。

    5.5 设置 OpenLDAP 管理员密码

    5.5.1 生成密码

    1. slappasswd

    执行完该命令之后,请输入您要设定的密码。然后会生成 {SSHA}xxxxx 这样一行东西,请把它记下来。
    5.5.2 生成 LDIF 文件

    1. cat << EOF > chrootpw.ldif

    请按实际情况以及注释提示修改以下内容,完成去除 # 号和后面的注释,然后粘贴到命令行窗口中按回车即可。

    1. dn: olcDatabase={0}config,cn=config
    2. changetype: modify
    3. add: olcRootPW
    4. olcRootPW: {SSHA}xxxxx #{SSHA}xxxxx 修改为上一步记下来的值
    5. EOF

    5.5.3 执行 LDIF 文件

    1. ldapadd -Y EXTERNAL -H ldapi:/// -f chrootpw.ldif

    5.6 导入预设的模式

    1. find /etc/openldap/schema/ -name "*.ldif" -exec ldapadd -Y EXTERNAL -H ldapi:/// -D "cn=config" -f {} \;

    5.7 新建一个根节点

    5.7.1 生成根节点管理员密码

    注意

  • 根节点管理员密码与 OpenLDAP 管理员密码不是同一回事!一个 LDAP 数据库可以包含多个目录树。

    1. slappasswd

    执行完该命令之后,请输入您要设定的密码。然后会生成 {SSHA}xxxxx 这样一行东西,请把它记下来。
    5.7.2 生成 LDIF 文件
    首先请想好一个域名。比如我使用的是 zenandidi.com
    zenandidi.com 为例,下面根节点的 DN 应该这样写:dc=zenandidi,dc=com

    1. cat << EOF > chdomain.ldif

    请按实际情况以及注释提示修改以下内容,完成去除 # 号和后面的注释,然后粘贴到命令行窗口中按回车即可。

    1. dn: olcDatabase={1}monitor,cn=config
    2. changetype: modify
    3. replace: olcAccess
    4. olcAccess: {0}to * by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth"
    5. read by dn.base="cn=Manager,dc=xxx,dc=xxx" read by * none #修改 dc=xxx,dc=xxx 为自己的域名
    6. dn: olcDatabase={2}hdb,cn=config
    7. changetype: modify
    8. replace: olcSuffix
    9. olcSuffix: dc=xxx,dc=xxx #修改 dc=xxx,dc=xxx 为自己的域名
    10. dn: olcDatabase={2}hdb,cn=config
    11. changetype: modify
    12. replace: olcRootDN
    13. olcRootDN: cn=Manager,dc=xxx,dc=xxx #修改 dc=xxx,dc=xxx 为自己的域名
    14. dn: olcDatabase={2}hdb,cn=config
    15. changetype: modify
    16. add: olcRootPW
    17. olcRootPW: {SSHA}xxxxx #{SSHA}xxxxx 修改为上一步记下来的值
    18. dn: olcDatabase={2}hdb,cn=config
    19. changetype: modify
    20. add: olcAccess
    21. olcAccess: {0}to attrs=userPassword,shadowLastChange by
    22. dn="cn=Manager,dc=xxx,dc=xxx" write by anonymous auth by self write by * none #修改 dc=xxx,dc=xxx 为自己的域名
    23. olcAccess: {1}to dn.base="" by * read
    24. olcAccess: {2}to * by dn="cn=Manager,dc=xxx,dc=xxx" write by * read #修改 dc=xxx,dc=xxx 为自己的域名
    25. EOF

    5.7.3 执行 LDIF 文件

    1. ldapmodify -Y EXTERNAL -H ldapi:/// -f chdomain.ldif

    5.8 添加用户、组节点

    5.8.1 生成 LDIF 文件

    1. cat << EOF > basedomain.ldif

    请按实际情况以及注释提示修改以下内容,完成去除 # 号和后面的注释,然后粘贴到命令行窗口中按回车即可。

    1. dn: dc=xxx,dc=xxx #修改 dc=xxx,dc=xxx 为自己的域名
    2. objectClass: top
    3. objectClass: dcObject
    4. objectclass: organization
    5. o: root_ldap
    6. dc: xxx #修改 xxx 为自己域名第一个点左边的内容
    7. dn: cn=Manager,dc=xxx,dc=xxx #修改 dc=xxx,dc=xxx 为自己的域名
    8. objectClass: organizationalRole
    9. cn: Manager
    10. description: Directory Manager
    11. dn: ou=People,dc=xxx,dc=xxx #修改 dc=xxx,dc=xxx 为自己的域名
    12. objectClass: organizationalUnit
    13. ou: People
    14. dn: ou=Group,dc=xxx,dc=xxx #修改 dc=xxx,dc=xxx 为自己的域名
    15. objectClass: organizationalUnit
    16. ou: Group
    17. EOF

    5.8.2 执行 LDIF 文件
    请先修改下面命令的 dc=xxx,dc=xxx 为自己的域名然后再执行。

    1. ldapadd -x -D cn=Manager,dc=xxx,dc=xxx -W -f basedomain.ldif

    然后需要输入 5.7.1 设定的根节点管理员密码。

    5.9 清理 LDIF 文件

    1. rm basedomain.ldif chdomain.ldif chrootpw.ldif

    至此,一个 LDAP 目录树就构建完毕了。

六、连接LDAP服务器

  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import java.util.Hashtable;
  3. import javax.naming.*;
  4. import javax.naming.directory.Attribute;
  5. import javax.naming.directory.Attributes;
  6. import javax.naming.directory.DirContext;
  7. import javax.naming.directory.InitialDirContext;
  8. public class LdapJNDI {
  9. public void JNDILookup() {
  10. String rootFilter = "o=xxx.com,o=isp";
  11. String username = "uid=USER_NAME,ou=Authorization,ou=People,o=cc.com,o=isp";//xxx为申请的对接账户
  12. String password = "PASSW";
  13. Hashtable env = new Hashtable();
  14. env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");//设置连接LDAP的实现工厂
  15. env.put(Context.PROVIDER_URL, "ldap://127.0.0.1:389”);// 指定LDAP服务器的主机名和端口号
  16. env.put(Context.SECURITY_AUTHENTICATION, "simple");//给环境提供认证方法,有SIMPLE、SSL/TLS和SASL
  17. env.put(Context.SECURITY_PRINCIPAL, username);//指定进入的目录识别名DN
  18. env.put(Context.SECURITY_CREDENTIALS, password); //进入的目录密码
  19. DirContext ctx = null;
  20. try {
  21. // 得到初始目录环境的一个引用
  22. ctx = new InitialDirContext(env);
  23. Attributes attrs = ctx.getAttributes("uid=00012047,ou=Internal,ou=People");//获取到一个人员,
  24. NamingEnumeration bindings = ctx.listBindings("ou=Internal,ou=People");//列举 内部人员
  25. while (bindings.hasMore()) {
  26. Binding bd = (Binding)bindings.next();
  27. System.out.println(bd.getName() + ": " + bd.getObject());
  28. }
  29. /*根据结点的DN来查找它的所有属性, 然后再从属性中得到所有的值,注意一个属性可以有多个值*/
  30. // for (NamingEnumeration ae = attrs.getAll(); ae.hasMore(); ) {
  31. // //获取一个属性
  32. // Attribute attr = (Attribute) ae.next();
  33. // for (NamingEnumeration ve = attr.getAll(); ve.hasMore(); ) {
  34. // System.out.println(String.format("Attribute=%s,Value=%s",attr.getID(),ve.next()) );
  35. // }
  36. // }
  37. } catch (javax.naming.AuthenticationException e) {
  38. System.out.println("认证失败");
  39. e.printStackTrace();
  40. } catch (Exception e) {
  41. System.out.println("认证出错:");
  42. e.printStackTrace();
  43. }finally {
  44. if (ctx != null) {
  45. try {
  46. ctx.close();
  47. } catch (NamingException e) {
  48. e.printStackTrace();
  49. }
  50. }
  51. }
  52. }
  53. }