LDAP(轻量级目录访问协议,Lightweight Directory Access Protocol)是实现提供被称为目录服务的信息服务。目录服务是一种特殊的数据库系统,其专门针对读取,浏览和搜索操做进行了特定的优化。目录通常用来包含描述性的,基于属性的信息并支持精细复杂的过滤能力。目录通常不支持通用数据库针对大量更新操做操做须要的复杂的事务管理或回卷策略。而目录服务的更新则通常都很是简单。这种目录能够存储包括我的信息、web链结、jpeg图像等各类信息。为了访问存储在目录中的信息,就须要使用运行在TCP/IP 之上的访问协议—LDAP。php
LDAP目录中的信息是是按照树型结构组织,具体信息存储在条目(entry)的数据结构中。常见的例子是通信簿,由以字母顺序排列的名字、地址和电话号码组成。html
目录服务与关系数据库之间的主要区别在于:两者都容许对存储数据进行访问,只是目录主要用于读取,其查询的效率很高,而关系数据库则是为读写而设计的。也就是目录服务不适于进行频繁的更新,属于典型的分布式结构。java
总结:对于查询操做多于更新操做的(认证)系统来讲,使用OpenLDAP是一个比关系数据库如MySq、PostgreSQL等更好的选择。linux
在LDAP的功能模型中定义了一系列利用LDAP协议的操做,主要包含如下4部分:web
查询操做:容许查询目录和取得数据,其查询性能比关系数据库好。
sql
更新操做:目录的更新操做不要紧数据库方便,更新性能较差,但也一样容许进行添加、删除、修改等操做。数据库
复制操做:前面也提到过,LDAP是一种典型的分布式结构,提供复制操做,可将主服务器的数据的更新复制到设置的从服务器中。
ubuntu
认证和管理操做:容许客户端在目录中识别本身,而且可以控制一个会话的性质。
windows
而本文所要将的OpenLDAP就是一个优秀的开源的LDAP实现。后端
安装软件很是简单,但在配置过程当中遇到了很多坎坷,不是服务启动不成功就是验证不成功。
具体的安装和配置方法网上一大把,但都良莠不齐,主要是由于新旧版本的OpenLDAP不一样,配置方法有很大的改动。
下面给出网上几个还算靠谱的Linux和Windows两个平台下安装该软件的方法:
1)ubuntu安装LDAP:安装方法靠谱,但配置说的不太清楚,配置注意事项看后面。
2)Ubuntu OpenLDAP Server:官方教程,最值得借鉴,是英文的,这里有中文版的,但没英文的清晰,说的比较简单。
3)Linux下安装openldap:二进制包安装方法,适用于非Ubuntu的Linux系统,稍微有点麻烦,在安装OpenlDAP以前还须要安装Berkeley DB,但配置灵活,能够自定义安装路径什么的。后面的配置也没说清楚,主要看安装方法。
4)Linux服务器部署系列之七—OpenLDAP篇:另外一篇较详细的二进制安装方法及配置。
4)Windows下OpenLDAP的安装及使用:介绍了LDAP的一些基础知识和Windows下安装方法。
5)图文介绍openLDAP在windows上的安装配置:比较详细,值得一看。
上面给出的这几个连接虽然还不错,但仍是欠缺了些什么?对,就是讲解,网上给出的教程都是手把手教你如何安装和配置,而没有说明版本差别、具体配置的含义及为何这样配置,若是由于版本或环境差别,你按其方法配置不成功,你也不知道哪里出的问题,所以建议仍是先熟悉LDAP的基础知识,配置文件含义而后再试着安装。
下面根据我本身的经验,给出几个安装和配置注意事项,供参考。
疑惑1:细心的人会发现有的教程说要配置主机DNS,添加与LDAP相关的域名,而大部分教程都没有说起这个,那么到底要不要配置呢?
解答:固然须要配置。安装好OpenLDAP后首先须要配置slapd.conf这个文件,其中里面有
suffix "dc=example, dc=com"
这样一句须要本身配置,这两个dc表明什么意思呢?其实dc就是“domainComponent”,也就是域名的组成部分,准确的说是主机域名的 后缀组成部分,若是这里的配置与你的主机域名不对应的话,服务通常是启动不了的。那么怎么配置域名呢?Linux和Windows下的配置文件以下:
Linux下:/etc/hosts
Windows下:C:\Windows\System32\drivers\etc\hosts
须要在hosts文件里添加一条域名(若是没配置的话),格式以下:
127.0.1.1 hostname.example.com hostname
好比个人主机名是min,并添加的域名配置是:
127.0.1.1 min.alexia.cn min
那么相应的我就须要在slapd.conf里这样配置suffix:
suffix "dc=alexia, dc=cn"
固然这里域名后缀不必定只有两级,也能够是hostname.example.com.cn,而后suffix就应该是“dc=example, dc=com, dc=cn”,这随便你怎么设置了,只要对应就行。
疑惑2:不少版本的slapd.conf里默认都配置了下面两个变量:
modulepath /usr/lib/ldap moduleload back_@BACKEND@
这是什么意思?须要改动吗?
解答:这是数据库database的backend,通常slapd.conf里配置的database都是 bdb,也就是Berkeley DB,有的也许是hdb,其实也是Berkeley DB,只是两个不一样的存储引擎(就像Mysql有MyISAM和InnoDB两个不一样的存储引擎同样)。而modulepath和moduleload指 定了动态模块路径及动态装载的后端模块,由于OpenLDAP默认是用Berkeley DB存储数据的,若是你有动态的数据须要装载,那么就须要配置这两个参数,对于通常用户将这两个注释掉便可。
疑惑3:OpenLDAP默认采用Berkeley DB存储数据,那么能够换用其它的关系数据库吗?具体如何配置呢?
解答:固然能够。首先须要明确ldap数据模型来自RDBMS(关系数据库模型),而并无指定必定是哪一个 DB,只要是关系数据库均可以做为LDAP的后台,那么你为何会想用其它的数据库代替自带的Berkeley DB呢?我想多是性能相关了,对于少许数据你用哪一个均可以,但若涉及到稍大点的数据,好比成千上万的用户查询,那么Berkeley DB的性能就不可观了,并且Berkeley DB管理起来也不太方便,毕竟对这个数据库熟悉的人很少,若是能换做咱们常用的数据库,不只性能获得提高,管理起来也十分容易,岂不是一举多得。
具体怎么配置了,请参考这篇文章:用postgresql做后台的openldap,以PostgreSQL做为例子进行讲解。
疑惑4:新旧版本的OpenLDAP到底有什么差别呢?
解答:简单一句话就是:旧版本的OpenLDAP配置文件通常是slapd.conf(路径多是/etc/openldap,也多是/usr/local/openldap,甚至多是/usr/share/slapd/,不一样版本不一样安装不一样系统均可能不一样,可以使用locate slapd.conf进行查找正确的路径),而新版本(我测试的新版本是2.4.31)的OpenLDAP服务运行时并不会读取该配置文件,而是从slapd.d目录(通常与slapd.conf在同一目录下)中读取相关信息,咱们须要把该目录下的数据删掉,而后利用咱们在slapd.conf里配置的信息从新生成配置数据。这也多是你启动服务后运行ldap相关命令却出现“ldap_bind: Invalid credentials (49)”错误的主要缘由。具体怎么从新生成配置数据请看参考资料。
疑惑5:自定义的ldif数据文件中的objectclass后的domain、top、organizationalUnit、inetOrgPerson等等都是什么意思,能够随便写吗?
解答:存储LDAP配置信息及目录内容的标准文本文件格式是LDIF(LDAP Interchange Format),使用文本文件来格式来存储这些信息是为了方便读取和修改,这也是其它大多数服务配置文件所采起的格式。LDIF文件经常使用来向目录导入或更 改记录信息,这些信息须要按照LDAP中schema的格式进行组织,并会接受schema 的检查,若是不符合其要求的格式将会出现报错信息。所以,ldif文件中的属性都定义在各大schema中,其中objectclass是对象的类属性, 不能随便填写,而应与schema中一致。通常slapd.conf文件的头部都包含了这些schema:
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/krb5-kdc.schema include ../etc/openldap/schema/RADIUS-LDAPv3.schema include ../etc/openldap/schema/samba.schema
其中前三个是比较重要的schema,定义了咱们所须要的各个类,好比ldif中通常先定义一个根节点,其相应的objectclass通常是 domain和top,而根节点下的ou属性即定义组节点(group)的objectclass通常是 organizationalUnit,group下能够是group也能够是用户节点,用户节点的objectclass通常是 inetOrgPerson。而各个节点的一系列属性如用户节点的uid、mail、userPassword、sn等等都定义在schema中相关的 objectclass里,能够本身查找看看。
疑惑6:OpenLDAP认证用户uid时默认是不区分大小写的,也就是“alexia”与“AleXia”是同一个用户,在有些状况下这并不合理,能配置使得认证时能区分大小写吗?
解答:以我目前的经验来看,旧版本的OpenLDAP是能够配置区分大小写的,而新版本的OpenLDAP却配置不了。为何这么说呢?
这里就涉及到“matching rules”这个概念了,即匹配规则,就是各个属性按什么样的规则进行匹配,好比是否区分大小写、是否进行数字匹配等等,这里有详细的官方匹配规则描述。好比旧版本的core.schema里有下面这样一段:
attributetype ( 0.9.2342.19200300.100.1.1 NAME ( 'uid' 'userid' ) DESC 'RFC1274: user identifier' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
从字面上也能够看出,其中caseIgnoreMatch
和caseIgnoreSubstringsMatch
就定义了uid或userid属性匹配时不区分大小写,若是咱们将其改成caseExactMatch
和caseExactSubstringsMatch
就表示用户uid认证时须要区分大小写,也就是“alexia”与“AleXia”同不一样的用户,这很简单,在旧版本的OpenLDAP也行得通。
但是在新版本的OpenLDAP中却不行,新版本的core.schema文件中也包含这样一段:
#attributetype ( 2.16.840.1.113730.3.1.217 # NAME ( 'uid' 'userid' ) # DESC 'RFC1274: user identifier' # EQUALITY caseIgnoreMatch # SUBSTR caseIgnoreSubstringsMatch # SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{256} )
惋惜是注释掉的,那咱们取消注释而后改属性行不行呢?答案是不行,会报错:Duplicate attributeType: "2.16.840.1.113730.3.1.217”,也就是说该属性已经被定义了,而后我就去包含的全部schema中搜索uid属性的定义,结果却找不到定义,那么为何还会报这个错误呢?后来一阵搜索,终于在这个帖子“slapd: built-in schema for uidNumber/gidNumber does not have ordering directive”知道了答案,原来新版本的OpenLDAP已经把uid属性定义schema硬编码到了slapd程序中,也就是没法在配置文件中修改了,真是坑!
针对这个问题,我给出两个不太好的解决方案:
个人主要经验也就这些。OpenLDAP也有客户端,若是你配置成功后,能够用客户端或写Java程序进行验证。
OpenLDAP既有图形客户端也有网页客户端。
主要有两个图形客户端:LdapBrowser282 (下载:LdapBrowser282.zip,下载解压后直接双击:lbe.bat 文件便可运行)和LdapAdmin(官方下载),使用都很是简单。
以下是两个客户端的界面,都须要先创建一个连接,填上相应的IP地址、端口和dn配置,而后链接便可得到你配置的数据。
LDAP Admin客户端:
即 phpLDAPadmin,基于PHP的一个web应用,须要配置Apache服务器和PHP,具体的配置方法可参考“phpLDAPadmin 安装配置讲解,经过 Web 端来管理您的 LDAP 服务器”,我比较偷懒,直接使用的PHPnow全套服务,安装成功后大概是下面这样一个界面:
下面借鉴网上资料提供一个简单的认证程序以下:
import java.util.Hashtable; import javax.naming.AuthenticationException; import javax.naming.Context; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import javax.naming.ldap.Control; import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.LdapContext; public class LDAPAuthentication { private final String URL = "ldap://127.0.0.1:389/"; private final String BASEDN = "ou=Tester,dc=alexia,dc=cn"; // 根据本身状况进行修改 private final String FACTORY = "com.sun.jndi.ldap.LdapCtxFactory"; private LdapContext ctx = null; private final Control[] connCtls = null; private void LDAP_connect() { Hashtable<String, String> env = new Hashtable<String, String>(); env.put(Context.INITIAL_CONTEXT_FACTORY, FACTORY); env.put(Context.PROVIDER_URL, URL + BASEDN); env.put(Context.SECURITY_AUTHENTICATION, "simple"); String root = "cn=manager,dc=alexia,dc=cn"; // 根,根据本身状况修改 env.put(Context.SECURITY_PRINCIPAL, root); // 管理员 env.put(Context.SECURITY_CREDENTIALS, "123456"); // 管理员密码 try { ctx = new InitialLdapContext(env, connCtls); System.out.println( "认证成功" ); } catch (javax.naming.AuthenticationException e) { System.out.println("认证失败:"); e.printStackTrace(); } catch (Exception e) { System.out.println("认证出错:"); e.printStackTrace(); } if (ctx != null) { try { ctx.close(); } catch (NamingException e) { e.printStackTrace(); } } } private String getUserDN(String uid) { String userDN = ""; LDAP_connect(); try { SearchControls constraints = new SearchControls(); constraints.setSearchScope(SearchControls.SUBTREE_SCOPE); NamingEnumeration<SearchResult> en = ctx.search("", "uid=" + uid, constraints); if (en == null || !en.hasMoreElements()) { System.out.println("未找到该用户"); } // maybe more than one element while (en != null && en.hasMoreElements()) { Object obj = en.nextElement(); if (obj instanceof SearchResult) { SearchResult si = (SearchResult) obj; userDN += si.getName(); userDN += "," + BASEDN; } else { System.out.println(obj); } } } catch (Exception e) { System.out.println("查找用户时产生异常。"); e.printStackTrace(); } return userDN; } public boolean authenricate(String UID, String password) { boolean valide = false; String userDN = getUserDN(UID); try { ctx.addToEnvironment(Context.SECURITY_PRINCIPAL, userDN); ctx.addToEnvironment(Context.SECURITY_CREDENTIALS, password); ctx.reconnect(connCtls); System.out.println(userDN + " 验证经过"); valide = true; } catch (AuthenticationException e) { System.out.println(userDN + " 验证失败"); System.out.println(e.toString()); valide = false; } catch (NamingException e) { System.out.println(userDN + " 验证失败"); valide = false; } return valide; } public static void main(String[] args) { LDAPAuthentication ldap = new LDAPAuthentication(); if(ldap.authenricate("gygtest", "jmwang") == true){ System.out.println( "该用户认证成功" ); } } }
既能够做为普通程序的认证,也能够经过输出检查本身的配置是否正确。
LDAP的实现除了OpenLDAP外,还有其它,好比OpenDJ(Open source Directory services for the Java platform),它是一个新的LDAPv3相容目录服务,为Java平台开发,提供了一个高性能的,高度可用和安全的企业管理的身份商店。其简单的安 装过程当中,结合了Java平台的力量,使OpenDJ简单和最快的目录服务器部署和管理。有兴趣的能够查阅相关资料。