Java2瀹炵幇Applet鏁板瓧绛惧悕 浼佷笟绾ava浠嬬粛 Java涔嬫暀鎺堢瘒 Java鐧界毊涔� JNDI鎶€鏈櫧鐨功 Servlet浠嬬粛 Servlet2.1瑙勮寖 Java鍦–/S缁撴瀯涓殑搴旂敤 Java涓眽瀛楅棶棰樺垎鏋愪笌瀵圭瓥 鍒╃敤Java瀹炵幇瑙嗛鐐规挱鏈嶅姟鍣� Java澶氱嚎绋嬭璁� Java鍙嶇紪璇� java IDL鎶€鏈� 鏁版嵁搴撶紦鍐叉睜 ASP璋冪敤Servlet Applet浠嬬粛 Applet鍜孲ervlet 4绉嶉€氳鏂规硶 Applet璋冪敤jdbc XML+Servlet寤哄湪绾跨郴缁� java鏁版嵁搴撴煡璇㈢殑琛ㄦ牸鏄剧ず 寮€鍙戝伐鍏风瘒 Forte for Java浠嬬粛 Jbuilder4鏂板姛鑳� Jbuilder浠嬬粛 Jbuilder3娴嬭瘯鎶ュ憡 VAJ寮€鍙憇ervlets |
JNDI技术白皮书
引 言
目录服务在Internet和Intranet应用中扮演着关键的角色,它提供对用户、机器、网络、服务和应用等信息的访问,目录服务利用名字来提供一种可以被人们理解的对不同实体的标识和分布进行描述的名字空间。
在现代的企业计算环境中通常包括许多不同的命名工具,它们维护着各自的名字空间。例如,DNS可能被企业用作对不同组织进行命名的顶层工具,各个组织则可能使用像LDAP、NDS、NIS之类的目录服务。
从用户的角度来看,他看到的是一个包含不同名字的统一的命名空间,比如URL,在用户看来它就是跨越不同的命名机制的统一的名字空间,使用目录服务的应用程序必须满足用户的这种观点。
如果有一种目录接口不但独立于特定的目录名字服务实现,而且对不同命名机制下的名字空间提供的类似的访问方法,则许多Java开发人员都将因此而受益匪浅,因为这样一来任何应用都能将它的对象绑定到名字空间中,而且这种机制使得任何Java应用都能够发现和获取任何类型的对象。
JNDI 就是一个基于Java语言的提供目录和命名服务的编程接口,它独立于任何特定的目录服务实现,这样就可以为不同的目录服务提供相同的访问途径。
下面的例子显示了在JNDI中查找并访问一个打印机对象的过程:
prt = (Printer) building7.lookup("puffin");
prt.print(document);
这里所有查找和定位该打印机对象的工作都由JNDI来完成了。
另一个例子显示了查找存贮在目录中的个人电话号码的代码:
String[] attrs = {"workPhone", "cellPhone", "faxNumber"};
bobsPhones = directory.getAttributes("cn=Bob, o=Widget, c=US", attrs);
如果有几个人都叫Bob,则可以用下列方法来查找正确的Bob。 bob = directory.search("o=Widget, c=US", "cn=Bob", searchctls);
目 标 和 设 计 原 则
JNDI 编程接口的设计遵循以下原则。
2.1 保持兼容性和直观性
JNDI尽量使用在其它Java标准或扩展中存在的组件,坚持这一原则不仅使JNDI与Java平台的其他核心类保持兼容,而且减少了不必要的类的派生。Java语言的面向对象性使得JNDI接口能够被设计得简单而且直观。
2.2 层次性
JNDI编程接口以层次结构使得编程人员只需注重特定层次的服务接口,而不需要了解低层接口的实现细节。
2.3 JNDI 必须实现通用的目录和名称服务接口以及协议
坚持这一原则首先使得Java应用能够使用许多已有的目录服务,例如DNS, NDS, NIS (YP), X.500,和 LDAP 服务等,其次,它避免了在编程接口中出现针对不同服务的特殊实现。但提供通用的接口并不表示用户不能访问特定协议的特有功能,熟悉某种特定协议的开发人员仍然可以在JNDI中使用该协议的特殊功能。
2.4 使不同的目录服务能以相似的插件方式插入到JNDI接口中 由于现有的目录和名字服务的多样性,以及必须为新的名字服务和目录服务提供通用的接口,JNDI接口必须能使不同的目录服务能以相似的插件方式插入到JNDI接口中。另外,JNDI 接口还必须独立于应用提供一种对不同目录服务实现的选择,例如一台瘦客户机可能会通过一台代理来访问目录服务,而一台胖客户机则可能选择使用一个服务实现来直接访问不同的服务器。最后,应用程序应该能够在运行时来决定对不同目录服务实现方式的选择。
基 础
目录服务在网络环境中提供对不同的用户和资源信息的访问,它使用名字系统来识别和组织代表信息的目录对象,目录对象提供值和属性之间的关联。这样,目录服务使用一种层次结构来组织信息,以提供一种在可以被人们理解的名字和目录对象之间的映射。
3.1 名字 - 关键的基石
在任何计算机系统中,名字服务都是一个基本的服务。在传统的计算机系统中,名字服务一般都是与一些其他的服务如文件系统、目录服务、数据库、桌面、邮件系统、电子表格或日历等一起提供的。比如,文件系统包含了对文件和目录的名字服务,而电子表格具有单元格和宏的名字服务 。
在企业计算环境中,一般总有多个名字服务,其中有对企业中的普通实体如组织、地理位置、用户、计算机提供上下文的服务,也有与应用程序一起提供文件服务、邮件服务、打印服务等等地服务。
而在用户看来,这些名字服务又存在着一些逻辑上的关系,例如,用户会自然的想到在用户的上下文环境中来命名如文件、邮件、任务表等,同样可以很自然的想到在一个部门上下文环境中来命名用户,同样的方法还能被表达其他一些类似的关系。
每个名字都由一组被称作名字转换协定的语法来产生,一个元名字是名字的最小的不可分割的元素,一个组合名字由零个或多个元名字根据名字转换组合而成。
例如,在一个UNIX路径名字中,元名字由左向右排列,中间以除号(/)隔开,如usr/local/bin ;而在DNS域名系统中,元名字则右向左排列,中间以点号(.)隔开,如j2ee.yeah.net。
元名字与对应对象的关联称为绑定。上下文就是一个保存一系列不同元名字的绑定的对象。每个上下文对应有一个名字转换的关联。上下文提供查找名字对象操作,绑定名字,取消绑定,绑定名字列表等功能。
上下文中的元名字通过组合名字能够被到同类型的子上下文中。 组合名字的解析通过在相应的上下文中查找相应的元名字来进行,这与我们在文件系统中通过目录层次查找文件是类似的。
名字系统代表了一个由同类上下文联系组成的提供同样操作的上下文集合,而名字空间则表示在一个名字系统中所有名字的集合。那些跨越名字系统的名字被称作复合名字。它包括了零个或多个来自单个名字系统的名字。
例如,jurassic.eng:/export/home/oe/.signature 是一个复合名字,在这个复合名字中,jurassic.eng来自主机名字空间而/export/home/oe/.signature 则来自UNIX文件名字空间。在另一个例子http://j2ee.yeah.net.index.htm中,http来自URL协议列表空间,j2ee.yeah.net来自主机DNS名字空间,而index.htm则来自于文件系统名字空间。
每个名字都是相对于上下文的,而每个针对名字的操作也都是在某个特定的上下文对象中进行的。客户端在运行时将能够获得一个指向名字解析起点的初始上下文。
3.2目录对象
名字系统的基本功能就是建立名字到对象的映射,这里的对象可以是任意类型的。目录是一种用来表示信息的多样性的特殊对象,它与属性相关联。属性是一个具有标识的值的集合。目录对象提供对对象关联的属性的建立、增加、修改、删除等操作。
如果将一个目录对象看成一个名字上下文的话,则我们就能得到一些目录信息的树状结构,这些树的叶子节点即代表名字上下文同时也包括一定的属性。
·一个复合名字空间能够表示多个名字系统,在图中DNS被用于全局名字系统,两个分部一个使用NDS,另一个使用LDAP。
·每个名字空间都包括许多代表名字上下文的内部节点,这些节点可能是目录对象或是任意类型的对象。
· 初始上下文(InitialContext) 在不同的名字和目录系统中被绑定到不同的起始上下文。
·在此例中应用程序只看到一个名字空间,它们可以访问任何绑定这个复合名字空间的对象。
· 任意目录服务都可以添加到这个复合名字空间而不需要应用程序作任何改动。
3.3 URL和复合名字
URL是一种复合名字,JNDI的客户端可以使用URL来引用任意类型的对象,比如要引用一个通过NFS协议访问的文件对象可以用nfs://nfs.com/export/jndi/README 来表示等。
JNDI 体 系 概 述
JNDI 体系包括JNDI API 和 JNDI SPI两大部分。Java 应用通过JNDI API访问各种名字和目录服务,而JNDI SPI则是供各种名字和目录服务的提供商使用的接口。这种体系架构将Java应用程序与名字目录服务隔离开,使得名字和目录服务对于Java应用是透明的,这样应用程序就不必考虑名字目录服务的具体实现和选择了。
JNDI API 接口包括两个包(package):javax.naming 和 javax.naming.directory 。其中javax.naming 用于名字操作而javax.naming.directory 用于目录操作。
JNDI SPI 接口包括于javax.naming.spi包中。 下面章节对JNDI API做了概述性的说明,其它信息参看相关资料。
5.1 名字接口 - javax.naming 1
5.1.1 上下文Context 与 名字Name
Context 是名字上下文的关键接口,它定义了诸如增加名字到对象的绑定、查找绑定到某个名字的、绑定列表、取消绑定、创建和删除某类子上下文(subcontext)等的基本操作。
Context 接口定义: public interface Context {
public Object lookup(Name name) throws NamingException;
public void bind(Name name, Object obj) throws NamingException;
public void rebind(Name name, Object obj) throws NamingException;
public void unbind(Name name) throws NamingException;
public void rename(Name old, Name new) throws NamingException;
public NamingEnumeration listBindings() throws NamingException;
...
public Context createSubcontext(Name name) throws NamingException;
public void destroySubcontext(Name name) throws NamingException;
...
};
在Context接口中,每个方法都有一个名字作为参数,这个名字映射到该方法所要操作的对象,可以是元名字也可以是复合名字。如果名字参数为空字串("")则该方法操作于当时的上下文本身。
在JNDI中每个名字都是相对于一个上下文的,没有绝对名字的概念。应用程序启动时自动获得它的第一个上下文类InitialContext,
public class InitialContext implements Context {
public InitialContext() throws NamingException; ...
}
Initialcontext类为客户提供一些连接到URL名字空间或DNS等名字系统的绑定。
Name接口代表名字。每个以Name类作参数的Context方法都有一个同名的以字符串为参数的方法。通常那些需要对名字进行维护的应用会使用Name参数进行调用,而那些只需简单地通过名字查找对应的对象的应用则倾向于使用以字符串为参数的方法。
CompositeName 类代表不同名字空间的名字组成的名字序列,一般在Context的方法中使用的Name参数都是复合名字。
CompoundName 类表示一个名字空间的带有层次性的名字,一个名字解析器被用来根据该上下文解析混合名字。
public interface Context {
... public NameParser getNameParser(Name name) throws NamingException; ...
}
事实上,大多数应用程序都使用字符串或复合名字,只有像名字空间浏览器之类的应用才使用混合名字。
5.1.2 绑定
Context.lookup() 是最常使用的操作,这个方法返回根据名字查找到的对象。例如,客户端可以使用打印机的名字来查找对应的打印机对象,然后直接使用返回的对象进行打印:
Printer printer = (Printer) ctx.lookup("treekiller");
printer.print(report);
Context.listBindings()返回一个名字到对象的绑定的集合,其中每个绑定都是Binding类的一个对象。Binding类的对象包括了绑定对象的名字、该对象所属类的类名以及该对象本身。
Context.list()方法与listBindings()类似,不同的是它一个NameClassPair类的对象集合。每个NameClassPair对象了一个对象的名字和该对象的类名。list()方法对于那些只需了解对象绑定,而不需返回实际对象的应用十分有用。
public class NameClassPair {
public String getName() ...;
public String getClassName() ...;
...
}
public class Binding extends NameClassPair {
public Object getObject() ...;
...
}
5.1.3 引用
不同的Context实现可能会绑定到不同的本地对象上,而应该被任何context实现所支持的对象是Reference类。Reference类十分有用,它代表了存在于目录之外的对象,正是Reference的使用,才能使在JNDI客户看来,任意类型的对象,比如X.500等都能被绑定到名字目录服务上。当Context.lookup()或Binding.getObject()等方法的结果是一个Reference对象时,JNDI在把结果返回给客户端之前会将该引用转换为它实际代表的对象。这种情况最典型的就是一个代表绑定在另一个名字系统中的名字的引用,实际上这就是JNDI用来将不同的独立名字系统组合成复合名字的方式,有关这方面的细节在JNDI SPI 文档中详细介绍。能够被引用的对象应该实现Referenceable接口,它只有一个方法,就是getReference(),它返回对对象的引用。当一个能够被引用的对象绑定到名字上时,如果该对象本身不能被系统所保存,则系统会改为保存该对象的引用。每个引用一般应包括对应对象的类名以及该对象的class文件的存放位置,如URL等等。另外,引用中还会包含一系列RefAddr类的对象,其中每个RefAddr对象都包含一个类型字符串和一些地址数据。
还有一类引用被称作LinkRef,用于向JNDI名字空间中加入符合连接,其中包含了实际JNDI对象的名字,当这些引用被解析时它就会指向连接的实际对象。
5.2 目录接口
5.2.1 目录对象及属性
DirContext接口使得目录对象能够获取和修改目录的属性,每个目录对象都能拥有若干属性对象,每个属性都有一个字符串标识并且包括若干值。
public interface DirContext extends Context {
public Attributes getAttributes(Name name) throws NamingException;
public Attributes getAttributes(Name name,String[] attrIds) throws NamingException;
...
public void modifyAttributes(Name name,int modOp,Attributes attrs) throws NamingException;
public void modifyAttributes(Name name,ModificationItem[] mods) throws NamingException;
...
}
public class Attribute ... {
...
public String getID();
public Object get() throws NamingException;
public NamingEnumerationEnumeration getAll() throws NamingException;
...
}
getAttributes()方法返回目录对象的一些或所用属性。
ModifyAttributes()方法用于对属性值进行修改,每个ModifyAttributes()方法都指定对属性的操作类型,这些类型可以是:
ADD_ATTRIBUTE 用于添加属性的值
REPLACE_ATTRIBUTE 用于替换属性的值
REMOVE_ATTRIBUTE 用于删除属性的值
modifyAttributes()方法有两种形式,第一种形式对一组属性的每个元素都施加同一种操作,第二种形式从一个ModificationItem类的对象中提前要施行的操作和对应的属性。
public class ModificationItem {
public ModificationItem(int modOp, Attribute attr) ...;
...
}
另外,应该保证每次对modifyAttributes()的调用都在一个原子操作中完成。
5.2.2 作为名字上下文的目录对象
由于DirContext接口继承了Context接口,因此目录对象同时也能作为名字上下文出现,就是说任何目录对象都能提供名字上下文。比如,一个目录对象可以在保存个人的一些信息的同时,作为与个人的打印机、文件、日历等资源关联的上下文。
对目录进行操作的应用可以通过InitialDirContext而不是InitialContext来获取初始上下文。
public class InitialDirContext extends InitialContext implements DirContext {
public InitialDirContext() throws NamingException;
...
}
DirContext提供的方法有bind、rebind、createSubcontext等。
public interface DirContext extends Context {
...
public void bind(Name name, Object obj, Attributes attrs) throws NamingException;
...
}
5.2.3 搜索
DirContext接口支持基于上下文的目录搜索,最常用的应用就是指定一定的需匹配的属性值,调用DirContext.search(),然后等待返回匹配成功的目录对象。
public interface DirContext extends Context {
...
public NamingEnumeration search(Name name,Attributes matchingAttributes) throws NamingException;
public NamingEnumeration search(Name name,Attributes matchingAttributes, String[] attributesToReturn) throws
NamingException;
...
}
搜索的结果返回的是一个NamingEnumeration类的对象,其中包括一些SearchResult类的对象:
public class SearchResult extends Binding {
...
public Attributes getAttributes() ...;
}
在更高级的应用中,还可以让DirContext根据程序提供的遵从Internet RFC 2254 for LDAP标准的查询过滤器(filter)来指定查询的范围、返回记录的最多条数等信息,然后进行查询。参数SearchControls可以指明查询的范围,比如可以包括单个目录对象、它的所以子对象或该对象所衍生出的所有对象等。
public interface DirContext extends Context {
...
public NamingEnumeration search(Name name,String filter, SearchControls ctls) throws NamingException;
public NamingEnumeration search(Name name, String filter, Object[] filterArgs, SearchControls ctls) throws
NamingException;
...
}
5.2.4 Schema
Schema描述了名字空间的结构及存储于其中的属性的规则,Schema的使用既可以是针对整个名字空间的也可以是针对单个属性的。由于schema能被表示成信息树,所以在JNDI的名字和目录接口中就能使用这一概念,这样应用程序就能够以一种通用的方式来访问名字空间的schema。比如,浏览器可以象访问其他目录对象一样访问schema树中的信息,当然这必须要求对应的上下文实现提供相应的支持。
DirContext.getSchema()用来获取目录对象的schema树的根节点,在根节点下面有诸如"ClassDefinition", "AttributeDefinition", "Syn-taxDefinition"和"MatchingRules"之类的子节点,这些子节点分别代表了定义的种类。Schema树的节点都是DirContext类的对象,DirCon-text.getSchemaClassDefinition() 返回返回"ClassDefinition"下的一个节点,其中包含了该目录对象的有关信息。
public interface DirContext extends Context {
...
public DirContext getSchema(Name name) throws NamingException;
public DirContext getSchemaClassDefinition(Name name) throws NamingException;
...
}
另外,与属性关联的schema还能通过属性类的方法getAttributeDefinition() 和 getAttributeSyntaxDefinition()来访问:
public class Attribute ... {
...
public DirContext getAttributeDefinition() throws NamingException;
public DirContext getAttributeSyntaxDefinition() throws NamingException;
...
}
下图说明了访问schema信息的不同关联方式:
5.3 上下文环境
JNDI 需要一种方式来交流一些信息,这些信息定义了访问名字和目录服务的环境。比如,希望指定目录服务访问安全级的应用可以通过设定java.naming.security.* 环境属性来达到该目的。再比如,在分布式的目录名字服务中,应用程序可能需要认证才能访问相关的信息,那么就需要使用java.naming.authoritative 环境属性。
在JNDI的定义中,对于环境属性取值的定义是很笼统的,比如对于环境属性java.naming.security.authentication 的取值定义为none, simple 或strong,而由具体的目录服务提供商来解释每个取值的具体含义。
上下文环境一般定义为Hashtable类或其派生类(如Properties类)的对象,通常它会作为参数传给InitialContext和InitialDirContext类的构造函数。
Hashtable env = new Hashtable(5, 0.75);
env.put(Context.SECURITY_PRINCIPAL, "jsmith");
env.put(Context.SECURITY_CREDENTIALS, "xxxxxxx");
Context ctx = new InitialContext(env);
或者将环境定义为Properties类的对象:
Properties env = new Properties();
env.put(Context.SECURITY_PRINCIPAL, "jsmith");
env.put(Context.SECURITY_CREDENTIALS, "xxxxxxx");
Context ctx = new InitialContext(env);
在Context接口中有三个与环境相关的方法:
Object addToEnvironment(String propName, Object propValue) throws NamingException;
Object removeFromEnvironment(String propName) throws NamingException;
Hashtable getEnvironment() throws NamingException;
前两个方法用于增加和删除该上下文的环境属性,一个方法用于返回当前的环境。
5.4 推荐
一些目录服务支持推荐的概念,就是当客户端发出请求时服务能够被重定向到另一台服务器。如果环境属性java.naming.referral被设为"follow",则当前服务将把客户的请求重定向给所有列举的服务器;如果设为"ignore",则重定向就被忽略了;如果设为"throw",则当前服务是否将客户的请求重定向给其他服务器将取决于客户端程序在ReferralException异常处理中行为。
public abstract class ReferralException extends NamingException {
public abstract Context getReferralContext() throws NamingException;
public abstract Object getReferralInfo();
public abstract boolean skipReferral();
}
当一个referral发生但是客户端既没有指定忽略该推荐又没有指定自动执行该推荐时,系统就将触发ReferralException异常。
getReferralInfo()方法提供了重定向服务器的信息。应用程序可能需要将该信息交给正在操作的人员来确定是否真的进行重定向。
skipReferral()方法将跳过当前的重定向转而执行下面的操作。
如果要进行重定向则可以通过以同样的参数调用重定向目标的相关方法来实现。 下例表明了如何使用ReferralException进行异常处理:
while (true) {
try {
bindings = ctx.listBindings(name);
while (bindings.hasMore()) {
b = (Binding)bindings.next();
...
}
break;
} catch (ReferralException e) {
ctx = e.getReferralContext();
}
}
案 例
本章将通过一些案例来说明JNDI的作用。
6.1 用户认证
在一些安全系统中,用户欲访问计算机、网络或服务时必须通过一定的安全认证,例如登录到UNIX系统的用户就需要提供相应的口令,使用SSL的用户也需要提供他的X.509证书。在目录中这些认证信息就可以作为与用户关联的属性进行存储,需要进行认证的系统就能通过在目录中查找有关用户的相应属性(如口令)来进行安全认证。
DirContext ctx = new InitialDirContext();
Attribute attr = ctx.getAttributes(userName).get("password");
String password = (String)attr.get();
6.2 电子邮件
在电子邮件系统中可以建立一种在用户和email地址之间进行映射的目录服务,当用户要给某人发送电子邮件时就可以通过目录服务查找该人的电子邮件地址,然后将邮件发送给他,就象我们平时在电话号码簿中查找电话号码一样。
NamingEnumeration matches = deptCtx.search("user", new BasicAttributes("name", "John Smith"));
// use matches to construct a selectable list for end-user while (matches.hasMore()) {
SearchResult item = (SearchResult) matches.next();
Attributes info = item.getAttributes();
/* display attributes */
...
}
目录服务还能让我们维护自己的个性化的电子邮件地址簿,这样就不用每次发送邮件都进行查找了。
6.3 数据库
数据库应用程序可以通过目录服务来查找和定位数据库服务器。
比如,一个财务应用需要使用JDBC从股票报价服务器中获取股票报价,应用程序可能允许用户输入一系列查询条件,然后在股票报价服务器的目录中查找相应的报价服务器,根据返回的位置信息(JDBC URL)来与该服务器建立连接和获取信息。
NamingEnumeration matches = ctx.search("service/stockQuotes", "(&(market=NASDAQ)(updateFreqency<=300))",searchctls);
while (matches.hasMore()) {
SearchResult item = (SearchResult)matches.next();
Attribute location = item.getAttributes().get("location");
...
}
6.4 浏览
在任何交互式应用中,当要求用户输入名字等信息时,如果能有一个合适的名字空间浏览器那么用户会感到更方便,这种名字浏览器既可以是嵌入在应用程序之中的,也可能就是一个WEB浏览器。这样的浏览器可以让用户在名字空间中进行浏览,就象在Jbuilder3中一样。
// Start at the top -- the initial context. Context ctx = new InitialContext();
while (ctx != null) {
// display one level NamingEnumeration items = ctx.list();
while (items.hasMoreElements()) {
NameClassPair item = (NameClassPair)items.next();
if (isContext(item.getClassName())) {
System.out.print("*");
}
else {
System.out.print(" ");
}
System.out.println(" " + item.getName());
}
// Take the next step down into the namespace. String target = input.readLine();
try {
ctx = (Context)ctx.lookup(target);
}
catch (NamingException e) {
...
}
catch (ClassCastException e) {
// not a context; cannot traverse
...
}
}
6.5 网络打印
打印服务的一项重要的服务就是提供使用者一个简便的方法在网络中找到和选择合适的打印机,另外,一个需要进行打印的应用程序应该在新的打印机加入到网络时无须进行什么配置就能使用它进行打印,这里网络的范围可能从工作组到全球网络。目录服务可以使得打印服务具有这些能力。
假设打印机由Printer接口来代表,其中一个方法就应该是打印print(),它接受输入的InputStream参数,从InputStream读取数据并打印到对应的打印机上。
interface Printer { void print(InputStream data) throws PrinterException;
...
}
用户通过指定或缺省设置来指定一个打印机的逻辑名字,应用程序根据这个名字在目录服务中查找相应的打印机,获取返回的打印机对象,进行打印。
void myAppPrint(String printerName, String fileName) throws IOException {
try {
DirContext ctx = new InitialDirContext();
Printer prt = (Printer) ctx.lookup(printerName);
prt.print(new FileInputStream(fileName));
}
catch (NamingException e){
System.err.println("Could not locate printer: " + e);
}
catch (ClassCastException e) {
System.err.println(printerName + "does not name a printer");
}
}
6.5.1 Browsing and searching for printers
用户除了像上面描述的通过指定打印机名字来选择打印机,还能够通过对打印机目录进行浏览或进行条件搜索来获得打印机对象,比如用户可以指定需要一台具有600DPI分辨率的彩色激光打印机进行打印。从应用程序来看,就只不过是变成了接受一个符合条件的打印机对象集合而已。
安 全 问 题
JNDI会在两类主要应用中使用:Java 应用程序 和 Applet。 在Java 应用程序中,代码是可信任的,应用程序能够访问本地CLASSPATH环境,而且对于应用程序对本地文件及与其他网络服务器进行连接都没有任何限制。 而对于Applet,则分为可信任的和不可信的Applet,就算在同一个Applet中,也可以分为可信任部分和不可信部分。
下面的共享上下文句柄和环境一节,将着重讨论在同一个Applet中同时包含可信任部分和不可信部分的情况。应该严格限制Applet对受限资源的访问。
7.1 JNDI 类
在javax.naming, javax.naming.directory和javax.naming.spi等包中包含的类均不包含本地方法,因此,无论在应用程序还是Applet中运行这些类都不需要在进行特别的安装。
JNDI 会使用一些系统属性,这使得不需要进行编程就可以对应用程序或Applet进行一些配置。但是对于Applet来说,可能由于安全原因而被限制无法访问某些系统属性,因此,JNDI同时将这些需要访问的环境属性放在上下文中供Applet访问。在JDK1.2中JNDI类被限制在只能够使用附录A中的Program Configuration and Access Configuration部分中的begin privileged 到end privileged之间的部分属性。
7.2 安全模型
JNDI 并没有定义一种访问名字和目录服务的通用的安全接口或者安全模型。用户认证和访问控制等与安全有关的功能由服务提供商来提供。JNDI提供了一种方法使得与安全有关的信息能够提供给服务提供商进行认证,但是JNDI本身却并不参与这样的安全认证。同样,当应用由于安全问题而出错时,JNDI也提供了一种使得出错信息能返回给客户端的途径。当JNDI服务需要访问被保护的资源如文件系统或网络时,这些JNDI服务将被安全管理器所控制。
7.3 访问服务器
一般来说名字和目录服务都有它们自己的安全机制来保存所存储的信息。例如,一些目录服务可能要求用户在读写所存储的信息之前必须先以某种方式登录到该目录中,有些服务则可能允许它的部分名字空间或目录被匿名访问。用户一旦登录到某个服务,应用程序就应该十分注意安全问题,不应该随意将它的权利共享给无法信任的代码。
7.4 共享上下文句柄
下面我们以上下文句柄来代表一个实际的上下文实例。上下文句柄应当被看作一种受保护的资源,当一段可信任代码获取了一个上下文句柄时,它应当小心的使用这个句柄,不要将它提供给没有认证的或不可信的代码。比如,当一段可信任代码获得某个上下文的句柄并其进行读写时,应当十分小心,不要将这个上下文句柄作为参数传给其他任何代码,以免出现安全漏洞。
7.5 上下文环境
JNDI允许应用程序或Applet在上下文中对环境属性进行读写,而在上下文中的环境属性可能包含十分敏感需要保护的信息,特别是象java.naming.se-curity.principal 和java.naming.security.credentials 等关键信息,绝对不能传递给不可信任的代码。服务提供商必须十分小心地保护这些信息,客户端的应用程序和Applet也应当十分注意不要将这些敏感数据传递给不可信的代码。
7.6 Class 加载
JNDI允许类文件被动态地加载,这种加载由RMI类加载器来完成。但是,动态加载类必须在安装了安全管理器而且通过其认证之后才能被加载,加载后,这些类在安全管理器指定的安全上下文中运行。
7.7 可串行化对象
某些JNDI类是可串行化的,这使得这些类的对象能以比特流的形式被访问,也就是可能在它们被创建的环境之外被访问,这样就产生了相关的安全问题。有关可串行化对象的安全问题可以参看下面网址的专门描述。 http://java.sun.com/products/jdk/1.1/docs/guide/serialization/spec/security.doc.html
7.8 服务提供商的责任
7.8.1 上下文环境
当创建上下文句柄时,会为它指定一些环境属性;有时,还需要一些与安全有关的属性才能创建上下文句柄。这时,服务提供商必须十分小心保护这些信息以免被不可信的代码获取,另外服务提供商还必须保护上下文环境以免被恶意修改。可以通过不允许那些与创建上下文句柄的线程具有不同的上下文或信任度的线程使用该上下文句柄,或限制某些操作等方法来实现上述保护。
7.8.2 网络安全
不可信的代码被限制访问网络,比如不可信的Applet只能与它的原始主机之间建立网络连接。但是,一般服务提供商本身都是可信任的代码,因此可能被允许连接到那些关键的主机。在这种情况下,服务提供商必须保证不破坏安全管理器的安全环境。
7.8.3 本地文件访问
与网络访问一样,不可信的代码也应当被限制访问本地文件系统。如果服务提供商有权可以访问本地文件,它必须十分小心,以免破坏安全。
7.8.4 特权代码和本地方法
如果服务提供商本身带有特权代码或本地方法,那么必须十分小心不应该破坏运行平台的安全环境。 .
JNDI 设 计 内 幕
8.1 将接口分为Context 和 DirContext
JNDI有两个核心接口Context和DirContext,Context中包含了基本的名字操作,而DirContext则将这些操作扩展到目录服务。将这些操作分为两个包一方面为了模块化,另一方面也可以使服务减少不必要的开销。名字是计算服务中的一个基本功能,使用基本的名字服务就可以获得文件系统、电子表格、日历服务等功能;DirContext 对Context进行了扩展,提供了基本的目录服务操作,对名字对象属性的维护、基于属性的名字查找等等。
8.2 将JNDI 分成多个功能包
JNDI 分为两个客户端包(javax.naming,javax.naming.directory) 和一个服务端包 (javax.naming.spi)。这样做也同样是为了减少应用程序不必要的开销,使得应用程序只需要包括所必须的包。
8.3 将客户端API 和 服务端的API 分开
JNDI 将客户端接口与服务提供商需要的接口分开为不同的包。比如,客户端程序只需要使用javax.naming包中提供的类,而服务提供商可能需要javax.naming和javax.naming.spi两中包。这样分开可以使客户和服务器端只专注于与自身有关的类信息。
8.4 上下文列表的多种方法
一般来说有两种进行上下文列表的应用:上下文浏览应用和对上下文中的对象进行实际操作的应用。
上下文浏览应用一般只需要显示上下文中包含内容的名字,或者再获取一些诸如对象的类型之类的信息。这种类型的应用一般都是交互式的,可以允许用户在列举的上下文列表中选择一些进行进一步的显示。
另外有一些应用需要对上下文中的对象进行实际的操作,比如,一个备份程序需要对目录中所有文件的状态进行操作,或者某打印机管理员可能需要对大楼中的所有打印机进行复位。为了进行这样的操作,程序需要获取上下文中的实际对象。
对于这样两种类型的应用,Context接口提供了两种上下文列表方法list()和listBindings()。其中list()只返回一系列名字/类映射,而listBindings() 则返回名字、类和对象本身。显然list()用于上下文浏览应用而listBindings()用于那些需要对对象进行实际操作的应用。
8.5 对联合的支持
联合是JNDI的一个基本概念,在客户端接口中可以支持跨越多个名字空间的名字,调用名字接口的程序不需要知道细节问题,只需要指定有关的名字,有关在几个名字系统中如何解析复合名字的问题留给服务提供商来解决,与客户无关。
8.6 DirContext 与 DirObject
对于目录服务的实现来说,如果不用扩展Context的DirContext接口,也可以使用一个单独的包含了所有目录相关方法的接口如DirObject,这样的话如果应用只使用目录服务就可以只包括DirObject,而如果名字服务和目录服务都使用,则可以包括Context和DirObject。这样当然条理比较清晰,但是对于某些混合操作,比如一些对目录和名字都有效的操作就不太方便了,所以JNDI采用了DirContext而不是DirObject。
8.7 Schema的支持
DirContext接口包含对schema的支持,例如,客户可以通过DirContext对象获得指向该DirContext实例的schema的定义空间的schema对象,或者获取该schema对象的类定义。Attribute类还更进一步地支持获取属性类型信息、属性定义等的方法。服务提供商既可以动态地返回这些schema信息,也可以静态地事先准备好有关的schema信息。
8.8 Context 和 DirContext
接口中的方法重载 在Context和DirContext接口中的每个接受Name参数的方法都有一个接受字符串参数的同名方法。 设计以字符串为参数的方法的原因是由于有很多应用只通过对象的名字来访问这些对象,对于这些应用来说,直接使用名字来访问这些方法当然是最直观的。
而设计以Name类对象为参数的方法的动机,也是由于有不少对名字进行维护的应用并不关系名字的字面表达,所以需要以Name对象作为参数。 在JNDI中这两种形式的方法都可以调用,以方便各种不同的应用。
8.9 引用
JNDI包容了多种使用目录来定位对象的方法,例如,一些应用直接将对象自身绑定在目录中;一些应用可能动态地产生目录树,当应用退出时就删去该树;另外一些应用可能只是将指向对象的URL存贮在名字空间里;还有一些系统可能将一些引用信息绑定到树中,当使用时再用这些信息来访问实际的对象。
针对这些不同的方式,JNDI定义了一个Reference类来为应用信息的表达提供一种统一的方式。Reference类包含了诸如地址、类型信息等用于访问具体对象的信息。为了能将对象的引用绑定到目录树中,该对象的类必须实现Referenceable接口,其中包含了方法 getReference() 。 Serializable接口与Referenceable接口有颇多相似之处,不同在于可引用的对象只包含一些用于创建实际对象的信息而Serializable会包含更多的甚至不适合存储在目录结构中的信息。
8.10 引用到实际对象的自动定位
对于作为引用绑定在目录树中的对象,JNDI SPI 指定针对引用创建实际的对象。因此,在程序中只需要认为用lookup()方法返回的对象就是实际对象,而不用在调用什么方法来将引用转换为实际对象了,所有的工作都由JNDI内部完成了。
(本文来自网上,作者,译者和时间不详)
|