在一个企业中,命名服务为读者的应用程序在网络上定位对象提供了一种方法。一个命名服务将对象和名称联系在了一起,并且可以通过它们指定的名称找到相应的对象。
JNDI是Java命名和目录接口,是一个为Java应用程序提供命名服务的应用程序编程接口(API)。它为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口,类似于JDBC都是构建在抽象层上。要使用JNDI,必须要安装jdk 1.3以上版本。
JNDI包含了大量的命名和目录服务,它使用通用接口来访问不同种类的服务,可以同时连接到多个命名或目录服务上并建立起逻辑关联。
命名服务是一种服务,它提供了为给定的数据集创建一个标准名字的能力。它允许把名称同Java对象或资源关联起来,而不必指出对象或资源的物理ID。这类似于字典结构(或者是Java的map结构),该结构中键映射到值。例如在Internet上的域名服务(domain namingservice,DNS)就是提供将域名映射到IP地址的命名服务,在打开网站时一般都是在浏览器中输入名字,通过DNS找到相应的IP地址,然后打开。
所有的因特网通信都使用TCP、UDP或IP协议。IP地址由4个字节32位二进制数字组成,数字和名字相比,对于人来说名字比数字要容易记忆,但对于计算机来讲,它更善于处理数字。
其实所有的命名服务都提供DNS这种基本功能,即一个系统向命名服务注册,命名服务提供一个值到另一个值的映射。然后,另外一个系统访问命名服务就可以取得映射信息。这种交互关系对分布式企业级应用来讲显得非常重要。
在Java中,基本的名字操作包含在Context接口中。
目录服务是一种特殊类型的数据库,与SQLServer、Access、Oracle等关系数据库管理系统相反,构造目录服务的目的是为了处理基于行为的事务,并且使用一种关系信息模型。目录服务将命名服务的概念进一步引申为提供具有层次结构的信息库,这一信息库除了包含一对一的关系外,还有信息的层次结构。对目录服务而言,这种层次结构通常用于优化搜索操作,并且也可以按实际情况进行分布或者跨网络复制。
一个目录服务通常拥有一个名字服务(但是一个名字服务不必具有一个目录服务)。如电话簿就是一个典型的目录服务,一般先在电话簿里找到相关的人名,再找到这个人的电话号码。
每一种目录服务都可以存储有关用户名、用户密码、用户组(如有关访问控制的 信息)、以太网地址、IP地址等信息。它所支持的信息和操作会因为所使用的目录服务的不同而不同。遗憾的是,访问不同目录服务的协议也会不同,所以读者需要了解多 种API。
这就是JNDI的起源,就像JDBC一样,JNDI充当不同名称和目录服务的通用API或者说是前端,然后使用不同的后端适配器来连接实际服务。如图6-1显示了JNDI和LDAP如何共同合作,为客户提供一种完美的解决方案。
在这里,使用JNDI完成与LDAP服务器之间的通信,对开发者来说他们只担心一个特殊协议(LDAP)和一个API(JNDI),而由开发商给他们自己的各个协议提供LDAP接口。事实上对这些流行的目录服务中来说,都有产品可让开发者通过LDAP与这些目录服务通信。
JNDI是J2EE技术中的一个完整的组件。它支持通过一个单一的方法访问不同的、新的和已经存在的服务的方法。这种支持允许任何服务提供商执行通过标准服务提供商接口(SPI)协定插入JNDI框架。另外,JNDI允许Weblogic服务器上的Java应用程序通过插入适当的服务提供者来访问像LDAP这样的标准化方式的外部目录服务。
基本的目录服务操作包含在DirContext接口中。
轻量目录访问协议(lightweight directory access protocol,LDAP)是在20世纪90年代早期作为标准目录协议进行开发的。它是目前最流行的目录协议,与厂商跟平台无关。
LDAP可以追溯到X.500协议,而X.500协议最初是基于OSI网络协议发展起来的。LDAP的第3版协议是在RFC2251中定义的,并且已经非常成熟,它的最新补充部分包含LDAP的XML规范,称为目录服务标记语言。
Java语言通过使用LDAP API,如Netscape Directory 服务器可以直接使用LDAP,或者通过JNDI来使用LDAP。JNDI是J2SE中的标准API,是通用的API,不必绑定到LDAP。
LDAP定义客户应当如何访问服务器中的数据,它并不指定数据应当如何存储在服务器上。大多数情况下,开发者只需要和一个专为LDAP设计的目录服务,或现有目录服务的LDAP前端打交道。LDAP能够成为任何数据存储类型的前端。目前最流行的目录服务有NIS、NDS、ActiveDirectory等都有某种类型的LDAP前端。
LDAP和关系数据库是两种不同层次的概念,后者是存储方式(同一层次如网格数据库,对象数据库),前者是存储模式和访问协议。LDAP是一个比关系数据库抽象层次更高的存储概念,与关系数据库的查询语言SQL属于同一级别。LDAP最基本的形式是一个连接数据库的标准方式,该数据库为读查询作了优化。因此它可以很快地得到查询结果,不过在其他方面,例如更新操作等就慢得多。
从另一个意义上来讲,LDAP是实现了指定的数据结构的存储,它是一种特殊的数据库。但是LDAP和一般的数据库不同,明白这一点是很重要的。LDAP对查询进行了优化,与写性能相比,LDAP的读性能要优秀很多。LDAP服务器也是用来处理查询和更新LDAP目录的。换句话说,LDAP目录也是一种类型的数据库,但不是关系型数据库。要特别注意的是,LDAP通常作为一个hierarchal数据库使用,而不是一个关系数据库。
1.LDAP数据
在LDAP中,数据被组织成一棵树的形式,叫做目录信息树(directory information tree,DIT)。DIT中的第一个“叶子”叫做一个条目(entry),第一个条目叫根条目(root entry)。
一个条目是由一个区分名称DN(distinguishedname)和任意一个属性/值对组成。DN是一个条目的名字,它必须是唯一的,它类似于一个关系型数据库的唯一关键字。DN也表明了该条目与DIT树的其他部分之间的关系,它类似于这种方式:一个文件的全路径名表明硬盘上的一个特定文件与系统中的其他文件之间的关系。当从根目录读取文件时,读取系统上的文件路径是从左到右读取的,但是当从根目录读取DN时,是从右到左读DN的。如:
uid = jordan,ou = nba,o = american
表示定义了在组织american中的小组为nba的用户jordan的用户。其中一个DN名的最左边部分叫相对区分名称RDN(relative distinguishedname),它由一个条目内的属性/值组成,如前面的uid = jordan是RDN,后面的可有可无。
LDAP通常使用简写形式的助记符表示其名称,常用的LDAP属性及其定义如表6-1所示。
LDAP属性及其定义
LDAP属性 |
定义 |
o |
Organization:组织 |
ou |
Organization unit:组织单元 |
续表
LDAP属性 |
定义 |
uid |
Userid:用户id |
cn |
Common name:常见名称 |
sn |
姓 |
givenname |
首名 |
dn |
Distinguished Name:区分名称 |
|
E-mail Address:电子邮件地址 |
其中一个属性可以有一个或多个值,如一个用户可以有多个mail。
X.500,一个CCITT的目录服务器标准,是OSI否为套件的一部分。X.500标准定义了客户端应用程序访问X.500目录的协议,叫做目录访问协议(DAP)。它在开放系统互连(OSI)协议栈的顶层。
因特网委员会认为需要X.500类型的访问但底层的网络基础架构(是TCP/IP而不是OSI)不同,基于X.500 DAP协议设计了一个新协议,叫做轻量级DAP或LDAP。RFC 2251定义了成为版本3的LDAP(LDAPv3),这是它的前身LDAP v2(RFC 1777)的改进。
LDAP协议的目的是容易实现,特别是创建小的简单的客户端。一种尝试简化的成果是大量使用字符串来减少结构的使用。例如DN,在协议中以字符串表示,属性类型名和大多数属性值也是如此。
这个协议包含客户端向服务器发送的请求,对于服务器的应答,不需要按照请求的顺序进行。每一个请求有一个ID,所以请求和应答可以匹配。这个协议可以工作在TCP或UDP中,最常用的是TCP。
因为焦点在客户端,LDAP组织同时定义了DN的字符串表达(RFC 2553),搜索过滤器(RFC 1960),属性语法(RFC 1778),为C语言提供的API(RFC 1823),访问LDAP访问的URL格式(RFC 1959)。
LDAP v3支持国际化,多种认证技术,referral,以及一般的部署技术。使用extensions和controls添加新特性时不需要修改协议。
国际化是通过国际化字符集(ISO 10646)表示协议中的字符串元素(例如DN)。v3与v2不同,v2使用UTF-8编码字符串。
除了匿名,简单(明文密码)认证,LDAPv3使用简单认证以及安全层(SASL)认证架构(RFC 2222)允许在LDAP中使用不同的认证技术。SASL定义了客户端和服务器之间数据交换的认证的挑战-应答协议。
现在定义了一些SASL技术:DIGEST-MD5, CRAM-MD5,匿名,扩展,S/Key, GSSAPI以及Kerberos v4。LDAP v3客户端可以使用任意一种SASL技术,提供给支持这种技术的LDAP v3服务器。而且,新的(还没有定义的)SASL技术可以在不改变LDAP的情况下使用。
referral是服务器发送个客户端的信息,表示请求的信息可以在其他地方发现(很可能是其他服务器)。在LDAP v2中,服务器由服务器处理referral,不返回到客户端。因为实现referral是非常负载的并且可能导致很复杂的客户端。当服务器构建以及部署后,referral就变得十分有用,但不是很多服务器支持服务端的referral处理。所以,使用一种方法修改协议允许返回referral。通过在将referral放置在“partial result”错误应答的错误信息中实现。
LDAP v3显式支持referrals,允许服务器直接向客户端返回referrals。referrals不再本课中介绍,您可以求助于JNDI教程关于符合在应用程序中控制referrals的信息。
LDAP这种普通协议可以用来确保目录客户端和服务器“说同一种语言”。当不同的目录客户端和服务器部署在网络中时,这些实体都说同样的对象是非常有用的。
目录架构描述,在其他东西中间,对象的类型,目录可能有的对象的类型,对象的每个必须的或可选属性。LDAP v3定义架构(RFC 2252 和 RFC 2256)基于X.500标准为了在网络中查找基本对象定义,例如,国家,地区,组织,用户/人,用户组和设备。它定义了客户端访问服务器架构的方法,所以可以发现对象的类型以及服务器支持的属性。
LDAPv3进一步定义了表示属性值的一组语法(RFC 2252)。要编写访问架构信息的Java应用程序,请参考JNDI教程。
除了预定义的所有操作,例如“search”和“modify”之外,LDAPv3定义了“extended”操作。“extended”将请求当作参数并且返回应答。请求中包含标识请求的标识符和请求参数。应答中包含请求执行的结果。“extended”操作中的请求和应答叫做扩展。例如,可以为开始TLS定义扩展,它是客户端向服务器的请求,用来开始Start TLS协议。
扩展可以是标准的(LDAP委员会定义的)或者私有的(由供应商定义)。要编写使用扩展的程序请参考JNDI教程。
添加新特性的另一种方法是使用control。LDAP v3允许通过使用control修改所有操作的行为。一个操作中可以发送任意多个control,同时结果中也可以返回任意数量的control。例如,您可以和“search”操作一起发送排序control,告诉服务器根据“name”属性对结果进行。
和扩展一样,control可以是标准的也可以是私有的。标准control由平台提供。编写使用control的应用程序请参考JNDI教程。
JNDI和LDAP的模型在命名对象时都使用层次结构的名字空间。名字空间中的每一个对象都可能有属性,而且这些属性可以用来搜索对象。在这种层次上,两个模型是类似的,所以JNDI可以对LDAP进行很好的映射也没有什么稀奇的。
您可以将LDAP的条目想象成JNDI的DirContext。每个LDAP条目包含名称和一组属性,以及可选的子条目集合。例如,LDAP条目“o=JNDITutorial”可能有属性“objectclass”和“o”,同时可能有子条目“ou=Groups”和“ou=People”。
在JNDI中,LDAP条目“o=JNDITutorial”可以表示成名为“o=JNDITutorial”的上下文,它有两个子上下文,名叫“ou=Groups”和“ou=People”。LDAP条目属性使用Attributes接口表示,而独立的属性通过Attribute接口表示。
关于LDAP操作如何通过JNDI进行访问的详细信息请参考下一部分。
为了联合查询,您提供给JNDI上下文方法的名称可以跨越多个命名空间。这些叫做混合名字。当使用JNDI访问LDAP服务时,您需要明白字符串中的斜杠(“/”)在JNDI中特殊的含义。如果JNDI条目包含这个字符,需要进行转义(使用“\”)。例如,使用名称“cn=O/R”的LDAP条目在JNDI context的方法中,必须使用“cn=O\\/R”标识。关于名字的更多信息,请参考JNDI教程。LdapName和Rdn类简要介绍了创建和操作LDAP名字的方法。
协议中LDAP的名称总是完全的名字,表示从LDAP命名空间根开始的唯一条目(由服务器定义)。以下是一些LDAP全名的例子:
cn=John Smith, ou=Marketing, o=Some Corporation, c=gb cn=Vinnie Ryan, ou=People, o=JNDITutorial |
然而,在JNDI中,名字是相对的,即,您总是相对于上下文命名对象。例如,您可以相对于上下文“ou=People, o=JNDITutorial”对“cn=Vinnie Ryan”条目进行命名。或者,您可以相对于上下文“o=JNDITutorial”对条目“cn=Vinnie Ryan, ou=People”进行命名。或者,您可以创建初始上下文指向LDAP服务器命名空间的根,然后命名条目“cn=Vinnie Ryan, ou=People, o=JNDITutorial”。
在JNDI中,您同样可以使用LDAP的URL来命名LDAP条目。请参考JNDI教程中的LDAP URL讨论。
LDAP定义了一组操作或请求(RFC 2251)。在JNDI中,这些操作被映射到DirContext和LdapContext接口中,它们都是Context的子接口。例如,当请求DirContext中的方法时。LDAP服务提供者通过将LDAP请求发送给LDAP服务器实现这个操作。
下表描述和LDAP操作对应的JNDI方法:
LDAP操作 |
对应的JNDI方法 |
bind |
这是创建LDAP服务器初始连接的方式,对应JNDI中创建InitialDirContext对象。当应用程序创建初始上下文,它在环境参数中向服务器提供客户端认证信息。要修改一个已经存在上下文的认证信息,使用Context.addToEnvironment()和Context.removeFromEnvironment()。 |
Unbind |
Context.close()用来释放上下文使用的资源。服务提供者的实现和LDAP的unbind操作有一些不同,资源在上下文之间共享,所以关闭上下文当资源被其他上下文使用时就不会释放。如果您的意图是释放所有资源,需要关闭所有上下文。 |
Search |
JNDI中对应方法是DirContext.search()中接收搜索过滤器(RFC 2254)的重载形式。 |
modify |
JNDI中对应方法是DirContext.modifyAttributes()中接收DirContext.ModificationItems数组的重载形式。示例请看修改属性一节。 |
Add |
JNDI中对应方法是DirContext.bind()和DirContext.createSubcontext()。您可以使用它们添加一个新的LDAP条目。使用bind(),您不但需要指定新条目的属性集合同时需要和属性一起的Java对象。示例请参考关联属性的添加、替换绑定一节。 |
Delete |
JNDI中对应的方法是Context.unbind()和Context.destroySubcontext()。您可以使用它们移除LDAP条目。 |
modify DN/RDN |
JNDI中对应方法是Context.rename()。请参考重命名对象一节得到详细信息。 |
compare |
在JNDI中可以使用DirContext.search()代替。示例请参考LDAP比较操作一节。 |
abandon |
当您关闭上下文时,所有没有应答的请求都被放弃。类似的,当关闭NamingEnumeration,相应的LDAP“search”请求也放弃了。 |
Extended操作 |
JNDI中对应的方法是LdapContext.extendedOperation()。详细信息请参考JNDI教程。 |
LDAP定义了一组状态码,它们是由LDAP服务器作为应答发送给客户端的(RFC 2251)。在JNDI中,错误条件由Naming Exceptions子类的检查的异常标识。请参考JNDI异常类概述中的Naming Exceptions。
LDAP状态码 |
含义 |
异常或操作 |
0 |
成功 |
报告成功 |
1 |
操作错误 |
NamingException |
2 |
协议错误 |
CommunicationException |
3 |
达到时间限制 |
TimeLimitExceededException |
4 |
达到大小限制 |
SizeLimitExceededException |
5 |
比较失败 |
被DirContext.search()方法使用,不产生异常。 |
6 |
比较成功 |
被DirContext.search()方法使用,不产生异常。 |
7 |
认证方式不支持 |
AuthenticationNotSupportedException |
8 |
需要更强的认证 |
AuthenticationNotSupportedException |
9 |
只返回部分数据 |
如果环境参数“java.naming.referral”是“ignore”或错误的内容不包含referral,抛出PartialResultException。否则,使用内容创建一个referral。 |
10 |
发生referral |
如果环境参数“java.naming.referral”是“ignore”,则忽略。如果参数是“throw”,抛出ReferralException。如果属性是“follow”,由LDAP提供者处理referral。如果超过“java.naming.ldap.referral.limit”限制,抛出LimitExceededException。 |
11 |
达到管理限制 |
LimitExceededException |
12 |
不支持的关键扩展请求 |
OperationNotSupportedException |
13 |
需要机密信息 |
AuthenticationNotSupportedException |
14 |
SASL绑定中 |
由LDAP提供者在认证过程中使用。 |
16 |
属性不存在 |
NoSuchAttributeException |
17 |
未定义属性类型 |
InvalidAttributeIdentifierException |
18 |
不合适的匹配 |
InvalidSearchFilterException |
19 |
常量违例 |
InvalidAttributeValueException |
20 |
属性值正在使用中 |
AttributeInUseException |
21 |
属性语法错误 |
InvalidAttributeValueException |
32 |
对象不存在 |
NameNotFoundException |
33 |
别名错误 |
NamingException |
34 |
DN语法非法 |
InvalidNameException |
35 |
是叶子节点 |
LDAP提供者使用,通常不产生异常。 |
36 |
别名解析错误 |
NamingException |
48 |
不合适的认证 |
AuthenticationNotSupportedException |
49 |
机密信息非法 |
AuthenticationException |
50 |
访问权限不足 |
NoPermissionException |
51 |
忙 |
ServiceUnavailableException |
52 |
不可得 |
ServiceUnavailableException |
52 |
服务器不愿执行 |
OperationNotSupportedException |
54 |
检测到循环 |
NamingException |
64 |
命名违例 |
InvalidNameException |
转自:http://spiritfrog.iteye.com/blog/391023