什么是JNDI
在一个企业中,命名服务为读者的应用程序在网络上定位对象提供了一种方法。一个命名服务将对象和名称联系在了一起,并且可以通过它们指定的名称找到相应的对象。
JNDI是Java命名和目录接口,是一个为Java应用程序提供命名服务的应用程序编程接口(API)。它为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口,类似于JDBC都是构建在抽象层上。要使用JNDI,必须要安装jdk 1.3以上版本。
JNDI包含了大量的命名和目录服务,它使用通用接口来访问不同种类的服务,可以同时连接到多个命名或目录服务上并建立起逻辑关联。
命名服务
命 名服务是一种服务,它提供了为给定的数据集创建一个标准名字的能力。它允许把名称同Java对象或资源关联起来,而不必指出对象或资源的物理ID。这类似 于字典结构(或者是Java的map结构),该结构中键映射到值。例如在Internet上的域名服务(domain naming service,DNS)就是提供将域名映射到IP地址的命名服务,在打开网站时一般都是在浏览器中输入名字,通过DNS找到相应的IP地址,然后打开。
所有的因特网通信都使用TCP、UDP或IP协议。IP地址由4个字节32位二进制数字组成,数字和名字相比,对于人来说名字比数字要容易记忆,但对于计算机来讲,它更善于处理数字。
其实所有的命名服务都提供DNS这种基本功能,即一个系统向命名服务注册,命名服务提供一个值到另一个值的映射。然后,另外一个系统访问命名服务就可以取得映射信息。这种交互关系对分布式企业级应用来讲显得非常重要。
在Java中,基本的名字操作包含在Context接口中。
目录服务
目 录服务是一种特殊类型的数据库,与SQL Server、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接口中。
LDAP的介绍
轻量目录访问协议(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、Active Directory等都有某种类型的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(distinguished name)和任意一个属性/值对组成。DN是一个条目的名字,它必须是唯一的,它类似于一个关系型数据库的唯一关键字。DN也表明了该条目与DIT树的其 他部分之间的关系,它类似于这种方式:一个文件的全路径名表明硬盘上的一个特定文件与系统中的其他文件之间的关系。当从根目录读取文件时,读取系统上的文 件路径是从左到右读取的,但是当从根目录读取DN时,是从右到左读DN的。如:
uid = jordan,ou = nba,o = american
表示定义了在组织american中的小组为 nba的用户jordan的用户。其中一个DN名的最左边部分叫相对区分名称RDN(relative distinguished name),它由一个条目内的属性/值组成,如前面的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。
LDAP
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 添加新特性时不需要修改协议。
LDAP v3
国际化
国际化是通过国际化字符集( 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 的情况下使用。
referrals
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 教程。
Controls
添加新特性的另一种方法是使用 control 。 LDAP v3 允许通过使用 control 修改所有操作的行为。一个操作中可以发送任意多个 control ,同时结果中也可以返回任意数量的 control 。例如,您可以和“ search ”操作一起发送排序 control ,告诉服务器根据“ name ”属性对结果进行。
和扩展一样, control 可以是标准的也可以是私有的。标准 control 由平台提供。编写使用 control 的应用程序请参考 JNDI 教程。
将 JNDI 作为 LDAP 的 API
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 操作如何映射到 JNDI API
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 错误码如何映射到 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 |
65 |
对象类型违例 |
padding: 0cm 5.4pt; border-left-color: #d4d0c8; width: 252.35pt; border-top-color: #d4d0c8;
发表评论
最新评论
|
评论