一、前言
本文从介绍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 基本结构
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)这三类。
下图是一个对象类所包含的内容。
4.6.2 属性类型(AttributeType)
属性类型定义了属性值的设定规则(属性语法),以及同一个属性的各个数据相互比较的规则等。
下图是一个属性类型包含的内容。
4.6.3 属性语法(Syntax)
下图是 LDAP 协议预定义的一些属性语法,例如二进制、字符串、电话号码类型等。
4.6.4 匹配规则(MatchingRule)
这个我把它理解为各种类型属性的集合。
下图是 LDAP 协议预定义的一些匹配规则。
4.7 LDIF(LDAP Data Interchange Format) 文件
LDAP 数据交换格式文件,它以文本形式存储,用于在服务器之间交换数据。
添加数据以及修改数据都需要通过 LDIF 文件来进行。
可以跟关系型数据库的 SQL 文件做类比。
LDIF 文件的格式一般如下:
dn: <识别名>
<属性 1>: <值 1>
<属性 2>: <值 2>
...
五、在 CentOS 7 上安装并使用 OpenLDAP
注意
- 操作系统:CentOS 7.4.1708 最小安装(已关闭 SELinux 和防火墙)
-
5.2 安装 OpenLDAP
yum install -y openldap-clients openldap-servers
5.3 创建数据库配置文件
直接套用模版即可。
cp /usr/share/openldap-servers/DB_CONFIG.example /var/lib/ldap/DB_CONFIG && chown ldap:ldap /var/lib/ldap/DB_CONFIG
5.4 启动 OpenLDAP 服务
systemctl start slapd
如需开机启动,请执行以下命令。
systemctl enable slapd
如果没有出现错误,那么 OpenLDAP 的安装就算完成了,不过当前的目录数据库还是空的,下面我们要对数据库进行初始化。
5.5 设置 OpenLDAP 管理员密码
5.5.1 生成密码
slappasswd
执行完该命令之后,请输入您要设定的密码。然后会生成
{SSHA}xxxxx
这样一行东西,请把它记下来。
5.5.2 生成 LDIF 文件cat << EOF > chrootpw.ldif
请按实际情况以及注释提示修改以下内容,完成去除
#
号和后面的注释,然后粘贴到命令行窗口中按回车即可。dn: olcDatabase={0}config,cn=config
changetype: modify
add: olcRootPW
olcRootPW: {SSHA}xxxxx #{SSHA}xxxxx 修改为上一步记下来的值
EOF
5.5.3 执行 LDIF 文件
ldapadd -Y EXTERNAL -H ldapi:/// -f chrootpw.ldif
5.6 导入预设的模式
find /etc/openldap/schema/ -name "*.ldif" -exec ldapadd -Y EXTERNAL -H ldapi:/// -D "cn=config" -f {} \;
5.7 新建一个根节点
5.7.1 生成根节点管理员密码
注意
根节点管理员密码与 OpenLDAP 管理员密码不是同一回事!一个 LDAP 数据库可以包含多个目录树。
slappasswd
执行完该命令之后,请输入您要设定的密码。然后会生成
{SSHA}xxxxx
这样一行东西,请把它记下来。
5.7.2 生成 LDIF 文件
首先请想好一个域名。比如我使用的是zenandidi.com
。
以zenandidi.com
为例,下面根节点的 DN 应该这样写:dc=zenandidi,dc=com
。cat << EOF > chdomain.ldif
请按实际情况以及注释提示修改以下内容,完成去除
#
号和后面的注释,然后粘贴到命令行窗口中按回车即可。dn: olcDatabase={1}monitor,cn=config
changetype: modify
replace: olcAccess
olcAccess: {0}to * by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth"
read by dn.base="cn=Manager,dc=xxx,dc=xxx" read by * none #修改 dc=xxx,dc=xxx 为自己的域名
dn: olcDatabase={2}hdb,cn=config
changetype: modify
replace: olcSuffix
olcSuffix: dc=xxx,dc=xxx #修改 dc=xxx,dc=xxx 为自己的域名
dn: olcDatabase={2}hdb,cn=config
changetype: modify
replace: olcRootDN
olcRootDN: cn=Manager,dc=xxx,dc=xxx #修改 dc=xxx,dc=xxx 为自己的域名
dn: olcDatabase={2}hdb,cn=config
changetype: modify
add: olcRootPW
olcRootPW: {SSHA}xxxxx #{SSHA}xxxxx 修改为上一步记下来的值
dn: olcDatabase={2}hdb,cn=config
changetype: modify
add: olcAccess
olcAccess: {0}to attrs=userPassword,shadowLastChange by
dn="cn=Manager,dc=xxx,dc=xxx" write by anonymous auth by self write by * none #修改 dc=xxx,dc=xxx 为自己的域名
olcAccess: {1}to dn.base="" by * read
olcAccess: {2}to * by dn="cn=Manager,dc=xxx,dc=xxx" write by * read #修改 dc=xxx,dc=xxx 为自己的域名
EOF
5.7.3 执行 LDIF 文件
ldapmodify -Y EXTERNAL -H ldapi:/// -f chdomain.ldif
5.8 添加用户、组节点
5.8.1 生成 LDIF 文件
cat << EOF > basedomain.ldif
请按实际情况以及注释提示修改以下内容,完成去除
#
号和后面的注释,然后粘贴到命令行窗口中按回车即可。dn: dc=xxx,dc=xxx #修改 dc=xxx,dc=xxx 为自己的域名
objectClass: top
objectClass: dcObject
objectclass: organization
o: root_ldap
dc: xxx #修改 xxx 为自己域名第一个点左边的内容
dn: cn=Manager,dc=xxx,dc=xxx #修改 dc=xxx,dc=xxx 为自己的域名
objectClass: organizationalRole
cn: Manager
description: Directory Manager
dn: ou=People,dc=xxx,dc=xxx #修改 dc=xxx,dc=xxx 为自己的域名
objectClass: organizationalUnit
ou: People
dn: ou=Group,dc=xxx,dc=xxx #修改 dc=xxx,dc=xxx 为自己的域名
objectClass: organizationalUnit
ou: Group
EOF
5.8.2 执行 LDIF 文件
请先修改下面命令的dc=xxx,dc=xxx
为自己的域名然后再执行。ldapadd -x -D cn=Manager,dc=xxx,dc=xxx -W -f basedomain.ldif
5.9 清理 LDIF 文件
rm basedomain.ldif chdomain.ldif chrootpw.ldif
至此,一个 LDAP 目录树就构建完毕了。
六、连接LDAP服务器
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Hashtable;
import javax.naming.*;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
public class LdapJNDI {
public void JNDILookup() {
String rootFilter = "o=xxx.com,o=isp";
String username = "uid=USER_NAME,ou=Authorization,ou=People,o=cc.com,o=isp";//xxx为申请的对接账户
String password = "PASSW";
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");//设置连接LDAP的实现工厂
env.put(Context.PROVIDER_URL, "ldap://127.0.0.1:389”);// 指定LDAP服务器的主机名和端口号
env.put(Context.SECURITY_AUTHENTICATION, "simple");//给环境提供认证方法,有SIMPLE、SSL/TLS和SASL
env.put(Context.SECURITY_PRINCIPAL, username);//指定进入的目录识别名DN
env.put(Context.SECURITY_CREDENTIALS, password); //进入的目录密码
DirContext ctx = null;
try {
// 得到初始目录环境的一个引用
ctx = new InitialDirContext(env);
Attributes attrs = ctx.getAttributes("uid=00012047,ou=Internal,ou=People");//获取到一个人员,
NamingEnumeration bindings = ctx.listBindings("ou=Internal,ou=People");//列举 内部人员
while (bindings.hasMore()) {
Binding bd = (Binding)bindings.next();
System.out.println(bd.getName() + ": " + bd.getObject());
}
/*根据结点的DN来查找它的所有属性, 然后再从属性中得到所有的值,注意一个属性可以有多个值*/
// for (NamingEnumeration ae = attrs.getAll(); ae.hasMore(); ) {
// //获取一个属性
// Attribute attr = (Attribute) ae.next();
// for (NamingEnumeration ve = attr.getAll(); ve.hasMore(); ) {
// System.out.println(String.format("Attribute=%s,Value=%s",attr.getID(),ve.next()) );
// }
// }
} catch (javax.naming.AuthenticationException e) {
System.out.println("认证失败");
e.printStackTrace();
} catch (Exception e) {
System.out.println("认证出错:");
e.printStackTrace();
}finally {
if (ctx != null) {
try {
ctx.close();
} catch (NamingException e) {
e.printStackTrace();
}
}
}
}
}