Java LDAP操做

命名和目录操做

您可使用JNDI执行如下操做:读取操做和更新命名空间的操做。本节介绍这两个操做:java

l         查询对象数组

l         列出上下文内容浏览器

l         添加、覆盖和移除绑定安全

l         重命名对象服务器

l         建立和销毁子上下文网络

配置

在命名和目录服务中执行操做以前,须要获得初始化上下文――命名空间的开始点。由于命名和目录服务的全部方法都相对于一些上下文执行。架构

为了获得初始化上下文,必须执行如下步骤:app

1.     选择想要访问的访问提供者。函数

2.     指定须要的初始化上下文。工具

3.     调用InitialContext构造函数。

第一步:为初始化上下文选择服务提供者

您能够为初始化上下文指定服务提供者,建立一个环境变量集合(Hashtable),同时将服务提供者的名称加入其中。环境属性在JNDI教程中有详细的介绍。

若是您使用SunLDAP服务提供者,代码以下所示:

 

Hashtable env = new Hashtable();

env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");

 

要指定Sun的文件系统服务提供者,代码以下所示:

 

Hashtable env = new Hashtable();

env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.fscontext.RefFSContextFactory");

 

您可使用一些系统属性描述使用的服务提供者。在JNDI教程中有详细描述。

第二步:提供初始化上下文须要的信息

不一样目录的客户端可能须要提供不一样的信息用来链接目录。例如,您须要指定服务器运行的机器以及识别目录中的用户。这些信息经过环境属性传递给服务提供者。JNDI指定服务提供者使用的通常环境参数。您的服务提供者文档会为须要提供的参数进行详细的说明。

LDAP提供者须要程序提供LDAP服务器的位置,以及认证信息。要提供这些信息,须要以下代码:

 

env.put(Context.PROVIDER_URL, "ldap://ldap.wiz.com:389");

env.put(Context.SECURITY_PRINCIPAL, "joeuser");

env.put(Context.SECURITY_CREDENTIALS, "joepassword");

 

本教程中使用SunLDAP服务提供者。例子中假设服务器设置在本机,使用389端口,根辨别名是“o=JNDITutorial”,修改目录不须要认证。这些信息是设置环境所须要的。

 

env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");

 

若是您使用不一样设置的目录,须要设置相应的环境属性。您须要使用机器名称替换“localhost”。您能够在任何公共的目录服务器或在其余机器上的本身的服务器运行例子。您须要将“localhost”替换成那台机器的名字,将o=JNDITutorial替换成相应的命名上下文。

第三步:建立初始化上下文

您已经建立了初始化上下文。为了作到这一点,您将以前建立的环境属性放到InitialContext构造函数中:

 

Context ctx = new InitialContext(env);

 

如今您有了一个上下文对象的引用,能够开始访问命名服务了。

为了完成目录操做,须要使用InitialDirContext。为了作到这一点,使用它的一个构造函数:

 

DirContext ctx = new InitialDirContext(env);

 

这句话返回了用来进行目录操做的DirContext对象引用。

命名异常

JNDI包中的不少方法当抛出异常时,说明操做请求不能执行。通常状况下,您将看到能够抛出NamingException的方法使用try/catch进行包装。

 

try {

    Context ctx = new InitialContext();

    Object obj = ctx.lookup("somename");

} catch (NamingException e) {

    // Handle the error

    System.err.println(e);

}

 

异常类结构

JNDI有丰富的异常结构,全部异常都从NamingException类中继承。异常类名都是自解释的,在下文中进行列举。

若是要处理特定的NamingException子类,须要分别catch子类。例如,如下代码特别的对待AuthenticationException及其子类。

 

try {

    Context ctx = new InitialContext();

    Object obj = ctx.lookup("somename");

} catch (AuthenticationException e) {

    // attempt to reacquire the authentication information

    ...

} catch (NamingException e) {

    // Handle the error

    System.err.println(e);

}

 

枚举

诸如Context.list()DirContext.search()这种操做返回NamingEnumeration。在这些状况下,若是出现错误而且没有返回结果,NamingException或它的子类会在方法请求时抛出。若是出现错误而且返回了部分结果,返回NamingEnumeration这样您能够取得这些结果。当全部结果都取出来后,再请求NamingEnumeration.hasMore()会致使抛出NamingException(或其子类)异常,表示出现错误。在这种状况下,枚举变得非法而且不能再请求其中任何方法。

例如,若是执行search()而且指定最多返回多少结果,那么search()最多返回n个结果。若是结果超过n个,那么当第n+1次请求NamingEnumeration.hasMore()时,抛出SizeLimitExceededException。请参见本节中的有关limit的示例代码。

本手册中的例子

在本手册文件中的在线示例代码中,一般为了便于阅读省略了try/catch语句。一般,由于只有部分代码片断在这里展现,因此只展现直接表示概念的行。若是您查看本教程附带的源码文件,将看到try/catch语句的合适位置。

javax.naming包中异常在这里能够看到。

查询对象

要从命名服务中查询对象,使用Context.lookup()方法而且传入您想取得的对象名。假设命名服务中有一个对象的名称是cn=Rosanna Lee,ou=People。要取得这个对象,您只须要编写:

 

Object obj = ctx.lookup("cn=Rosanna Lee,ou=People");

 

lookup()返回的对象类型依赖于命名服务以及对象关联的数据。命名服务能够包含许多不一样类型的对象,同时在系统的不一样部分查询的对象可能获得不一样的类型。例如,“cn=Rosanna Lee,ou=People”绑定到上下文对象中(javax.naming.ldap.LdapContext)。您能够对lookup()方法的结果cast成须要的类。

例如,如下代码查询“cn=Rosanna Lee,ou=People”对象而且castLdapContext

 

import javax.naming.ldap.LdapContext;

...

LdapContext ctx = (LdapContext) ctx.lookup("cn=Rosanna Lee,ou=People");

 

完整的例子在Lookup.java文件中。

Java SE 6中查询名称引入了两个新的静态方法:

l         InitialContext.doLookup(Name name)

l         InitialContext.doLookup(String name)

 

这些方法提供了不实例InitialContext查找对象的快捷方式。

列举上下文

代替Context.lookup()一次取得一个对象的方法,您能够在一个单一的操做中列举整个剩下文。列举上下文有两个方法:一个返回了绑定关系,另外一个只返回名-对象类型名。

Context.List()方法

Context.list()返回NameClassPair的枚举。每一个NameClassPair包含对象名和对象类型名。下列代码列举了“ou=People”目录的内容(例如,“ou=People”目录中找到的文件和目录)。

 

NamingEnumeration list = ctx.list("ou=People");

 

while (list.hasMore()) {

    NameClassPair nc = (NameClassPair)list.next();

    System.out.println(nc);

}

 

返回值以下:

 

# java List

cn=Jon Ruiz: javax.naming.directory.DirContext

cn=Scott Seligman: javax.naming.directory.DirContext

cn=Samuel Clemens: javax.naming.directory.DirContext

cn=Rosanna Lee: javax.naming.directory.DirContext

cn=Maxine Erlund: javax.naming.directory.DirContext

cn=Niels Bohr: javax.naming.directory.DirContext

cn=Uri Geller: javax.naming.directory.DirContext

cn=Colleen Sullivan: javax.naming.directory.DirContext

cn=Vinnie Ryan: javax.naming.directory.DirContext

cn=Rod Serling: javax.naming.directory.DirContext

cn=Jonathan Wood: javax.naming.directory.DirContext

cn=Aravindan Ranganathan: javax.naming.directory.DirContext

cn=Ian Anderson: javax.naming.directory.DirContext

cn=Lao Tzu: javax.naming.directory.DirContext

cn=Don Knuth: javax.naming.directory.DirContext

cn=Roger Waters: javax.naming.directory.DirContext

cn=Ben Dubin: javax.naming.directory.DirContext

cn=Spuds Mackenzie: javax.naming.directory.DirContext

cn=John Fowler: javax.naming.directory.DirContext

cn=Londo Mollari: javax.naming.directory.DirContext

cn=Ted Geisel: javax.naming.directory.DirContext

 

Context.listBindings()方法

Context.listBindings()方法返回绑定的枚举。绑定是NameClassPair的子类。绑定不止包含对象名和对象类名,还包含对象。如下代码片断枚举了“ou=People”上下文,打印出每个绑定名称和对象。

 

NamingEnumeration bindings = ctx.listBindings("ou=People");

 

while (bindings.hasMore()) {

    Binding bd = (Binding)bindings.next();

    System.out.println(bd.getName() + ": " + bd.getObject());

}

 

返回的结果以下:

 

# java ListBindings

cn=Jon Ruiz: com.sun.jndi.ldap.LdapCtx@1d4c61c

cn=Scott Seligman: com.sun.jndi.ldap.LdapCtx@1a626f

cn=Samuel Clemens: com.sun.jndi.ldap.LdapCtx@34a1fc

cn=Rosanna Lee: com.sun.jndi.ldap.LdapCtx@176c74b

cn=Maxine Erlund: com.sun.jndi.ldap.LdapCtx@11b9fb1

cn=Niels Bohr: com.sun.jndi.ldap.LdapCtx@913fe2

cn=Uri Geller: com.sun.jndi.ldap.LdapCtx@12558d6

cn=Colleen Sullivan: com.sun.jndi.ldap.LdapCtx@eb7859

cn=Vinnie Ryan: com.sun.jndi.ldap.LdapCtx@12a54f9

cn=Rod Serling: com.sun.jndi.ldap.LdapCtx@30e280

cn=Jonathan Wood: com.sun.jndi.ldap.LdapCtx@16672d6

cn=Aravindan Ranganathan: com.sun.jndi.ldap.LdapCtx@fd54d6

cn=Ian Anderson: com.sun.jndi.ldap.LdapCtx@1415de6

cn=Lao Tzu: com.sun.jndi.ldap.LdapCtx@7bd9f2

cn=Don Knuth: com.sun.jndi.ldap.LdapCtx@121cc40

cn=Roger Waters: com.sun.jndi.ldap.LdapCtx@443226

cn=Ben Dubin: com.sun.jndi.ldap.LdapCtx@1386000

cn=Spuds Mackenzie: com.sun.jndi.ldap.LdapCtx@26d4f1

cn=John Fowler: com.sun.jndi.ldap.LdapCtx@1662dc8

cn=Londo Mollari: com.sun.jndi.ldap.LdapCtx@147c5fc

cn=Ted Geisel: com.sun.jndi.ldap.LdapCtx@3eca90

 

结束NamingEnumeration

NamingEnumeration能够经过三种方式终止:通常的,显式的,非显式的。

l         NamingEnumeration.hasMore()返回false,枚举结束同时终止。

l         您能够在枚举终止前请求NamingEnumeration.close()方法显式的终止一个枚举。这样作提示底层实现释听任何和枚举有关的资源。

l         若是hasMore()next()方法抛出任何异常,枚举当即终止。

无论如何终止枚举,枚举一旦被终止就不能再使用。在一个已终止的枚举中请求任何方法都会致使不肯定的结果。

为何使用两个不一样的方法?

list()为浏览类型的应用程序准备,只返回上下文中对象的名字。例如,浏览器可能列出上下文中的名字,期待用户选择一个或多个显示的名称来进行后续操做。这些应用程序通常不须要访问上下文中全部的对象。

listBindings()为须要在上下文对象中进行操做的应用程序准备。例如,备份程序须要在文件目录中全部对象中执行“file stats”操做。或者,一个打印机管理员程序可能想要重启建筑物内的全部打印机。为了执行这些操做,须要获得上下文中的全部对象。所以,将对象做为枚举的一部分返回是权宜之计。

应用程序能够依据须要的信息类型选择list()listBindings()

添加、替换、或删除绑定

Context接口包含在上下文中添加、替换、删除绑定的方法。

添加绑定

Context.bind()为了向上下文中添加绑定,它以对象类型以及须要绑定的对象做为参数。

 

在继续前:本教程中的例子须要您对架构作额外的修改。您必须关闭LDAP服务器的架构检测或将符合本教程的架构添加到服务器中。这种工做通常由目录服务器管理员执行。请看课程。

 

// Create the object to be bound

Fruit fruit = new Fruit("orange");

 

// Perform the bind

ctx.bind("cn=Favorite Fruit", fruit);

 

这个例子建立了一个Fruit类的对象同时在上下文ctx中将他绑定到名称“cn=Favorite Fruit”中。若是您随后在ctx中查询“cn=Favorite Fruit”,那么您将获得fruit兑现。注意,编译Fruit类须要FruitFactory类。

若是您运行这个例子两次,那么第二次会由于NameAlreadyBoundException异常失败。由于“cn=Favorite Fruit”已经绑定了。要第二次运行时不失败,须要使用rebind()

添加或修改绑定

rebind()用来添加或替换绑定。它的参数列表和bind()同样,但若是名称已经绑定,那么首先会unbound而后再从新绑定新的对象。

 

// Create the object to be bound

Fruit fruit = new Fruit("lemon");

 

// Perform the bind

ctx.rebind("cn=Favorite Fruit", fruit);

 

当您运行这个例子时,将会替换bind()例子中已经建立的绑定关系。

删除绑定

要删除绑定,使用unbind()

 

// Remove the binding

ctx.unbind("cn=Favorite Fruit");

 

当这个例子运行时,删除bind()unbind()建立的绑定关系。

重命名

您使用Context.rename()对上下文中的对象进行重命名。

 

// Rename to Scott S

ctx.rename("cn=Scott Seligman", "cn=Scott S");

 

这个例子将绑定到“cn=Scott Seligman”的对象绑定到了“cn=Scott S”中。在验证对象被重命名后,程序再将其重命名回原来的名字(“cn=Scott Seligman”),以下所示:

 

// Rename back to Scott Seligman

ctx.rename("cn=Scott S", "cn=Scott Seligman");

 

更多关于LDAP中重命名的例子请参考LDAP的高级注意。

建立和销毁子上下文

Context接口包含建立和销毁一个子上下文。子上下文是绑定到其余上下文的上下文。

这个例子使用一个有属性的对象,而后在目录中建立子上下文。您可使用DirContext的方法将属性和对象在绑定或子上下文添加到名字空间时进行关联。例如,您能够建立Person对象,而后在为Person对象关联属性的同时将他绑定到命名空间中。命名等于没有任何属性。

createSubcontext()bind()不一样,他建立了一个新对象,例如一个要绑定到目录中的新上下文,但bind()在目录中绑定了给定的对象。

 

建立上下文

要建立命名上下文,您向createSubcontext()提供要建立上下文的名称。要建立有属性的上下文,向DirContext.createSubcontext()提供想要建立上下文的名称以及须要的属性。

 

在继续前:本教程中的例子须要您对架构作额外的修改。您必须关闭LDAP服务器的架构检测或将符合本教程的架构添加到服务器中。这种工做通常由目录服务器管理员执行。请看课程。

 

// Create attributes to be associated with the new context

Attributes attrs = new BasicAttributes(true); // case-ignore

Attribute objclass = new BasicAttribute("objectclass");

objclass.add("top");

objclass.add("organizationalUnit");

attrs.put(objclass);

 

// Create the context

Context result = ctx.createSubcontext("NewOu", attrs);

 

这个例子建立了名称为“ou=NewO”的上下文,而且有属性“objectclass”,属性“top”和“organizationalUnit”,其中“objectclass”有两个值。

 

# java Create

ou=Groups: javax.naming.directory.DirContext

ou=People: javax.naming.directory.DirContext

ou=NewOu: javax.naming.directory.DirContext

 

这个例子建立了一个新的上下文,叫“NewOu”,是ctx的子上下文。

销毁上下文

要销毁上下文,须要向destroySubcontext()提供须要销毁上下文的名称。

 

// Destroy the context

ctx.destroySubcontext("NewOu");

 

这个例子在上下文ctx中删除上下文“NewOu”。

属性名

属性由属性标识符和一组属性值组成。属性表示符叫属性名,是表示属性的字符串。属性值是属性的内容,它的类型不必定是字符串。当您想要指定获取、搜索、或修改指定属性时,您使用属性名。名称同时在返回属性的操做中返回(例如,当您执行目录的读取或搜索操做时)。

当使用属性名时,您须要知道特定目录服务器的特性,因此您不会为结果惊奇。这些特定在下一子节中描述。

属性类型

在诸如LDAP之类的目录中,属性名表示了属性的类型,一般叫作属性类型名。例如,属性名“cn”同时叫作属性类型名。属性类型定义了属性值的语法,是否容许多值,相等性,以及对属性值执行比较和排序时的排序规则,

属性子类

一些目录实现支持目录子类型,就是服务器容许属性类型使用其余属性类型定义。例如,“name”属性多是全部name相关属性的超类型:“commonName”是name的子类。对于支持这种特斯娜格的目录实现,访问“name”属性可能返回“commonName”属性。

当访问支持子类型的属性的目录时,要知道服务器可能返回和您请求不一致的类型。为了减小这种概率,使用最派生类。

属性名同义词

一些目录实现支持属性名的同义词。例如,“cn”多是“commonName”的同义词。因此请求“cn”属性可能返回“commonName”属性。

当访问支持属性同义词的目录,您必须意识到服务器可能返回和您请求不一样的属性名。要防止这种状况发生,使用官方的属性名代替使用同义词。官方的属性名是定义属性的属性名;同义词是是定义中引用官方属性名的名称。

语言参数选择

LDAP v3的扩展(RFC 2596)容许您和属性名一块儿指定语言编码。相似于子类属性,一个属性名能够表示多个不一样的属性。例如“description”属性有两个不一样的语言变体:

 

description: software

description;lang-en: software products

description;lang-de: Softwareprodukte

 

对“description”属性的请求会返回全部三种属性。当访问支持这种特性的目录时,您必须意识到服务器可能返回和请求时不一样的名称。

读取属性

为了从目录中读取对象的属性,使用DirContext.getAttributes()而且将您想读取的属性名称传递进去就能够了。假设在命名服务中的一个对象的名称是“cn=Ted Geisel, ou=People”。要获取对象的属性要使用以下代码:

 

Attributes answer = ctx.getAttributes("cn=Ted Geisel, ou=People");

 

您能够按照以下方式打印应答的内容:

 

for (NamingEnumeration ae = answer.getAll(); ae.hasMore();) {

    Attribute attr = (Attribute)ae.next();

    System.out.println("attribute: " + attr.getID());

    /* Print each value */

    for (NamingEnumeration e = attr.getAll(); e.hasMore();

     System.out.println("value: " + e.next()))

    ;

}

 

输出以下:

 

# java GetattrsAll

attribute: sn

value: Geisel

attribute: objectclass

value: top

value: person

value: organizationalPerson

value: inetOrgPerson

attribute: jpegphoto

value: [B@1dacd78b

attribute: mail

value: Ted.Geisel@JNDITutorial.com

attribute: facsimiletelephonenumber

value: +1 408 555 2329

attribute: telephonenumber

value: +1 408 555 5252

attribute: cn

value: Ted Geisel

 

返回选中属性

为了读取选中子集的属性,您须要提供想要获取的属性标识符的数组。

 

// Specify the ids of the attributes to return

String[] attrIDs = {"sn", "telephonenumber", "golfhandicap", "mail"};

 

// Get the attributes requested

Attributes answer = ctx.getAttributes("cn=Ted Geisel, ou=People", attrIDs);

 

这个例子请求对象“cn=Ted Geisel, ou=People”的“sn,telephonenumber”,“golfhandicap”和“mail”属性,因此应答中返回这三个属性。

如下是输出结果:

 

# java Getattrs

attribute: sn

value: Geisel

attribute: mail

value: Ted.Geisel@JNDITutorial.com

attribute: telephonenumber

value: +1 408 555 5252

 

修改属性

DirContext接口包含修改目录中对象的属性和属性值的方法。

使用修改列表

修改对象属性的一个方法是提供修改列表(ModificationItem)。每个ModificationItem包含数字常量表示修改的类型以及描述须要修改的属性。如下是修改的类型。

l         ADD_ATTRIBUTE

l         REPLACE_ATTRIBUTE

l         REMOVE_ATTRIBUTE

修改以列表中提供的类型进行。或者全部的修改都执行,或者一个都不执行。

如下代码建立修改列表。它将“mail”属性值替换成geisel@wizards.com,为“telephonenumber”属性添加附加值,而且删除“jpegphoto”属性。

 

// Specify the changes to make

ModificationItem[] mods = new ModificationItem[3];

 

// Replace the "mail" attribute with a new value

mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE,

    new BasicAttribute("mail", "geisel@wizards.com"));

 

// Add an additional value to "telephonenumber"

mods[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE,

    new BasicAttribute("telephonenumber", "+1 555 555 5555"));

 

// Remove the "jpegphoto" attribute

mods[2] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE,

    new BasicAttribute("jpegphoto"));

 

Windows活动目录:活动目录将“telephonenumber”属性定义为单值属性,这和RFC 2256不符。为了让这个例子在活动目录中执行,您必须或者使用其余属性代替“telephonenumber”或将DirContext.ADD_ATTRIBUTE改成DirContext.REPLACE_ATTRIBUTE

 

在建立修改列表后,您能够按照以下方式提供给modifyAttributes()

 

// Perform the requested modifications on the named object

ctx.modifyAttributes(name, mods);

 

使用属性

可选的,您能够经过指定修改类型以及修改属性的方式进行修改。

例如,如下行使用orig中的name关联须要替换的属性:

 

ctx.modifyAttributes(name, DirContext.REPLACE_ATTRIBUTE, orig);

 

其余属性的名称没有改变。

两种对于modifyAttributes()的使用在示例程序中都有。使用修改列表修改属性的程序在第二部分modifyAttributes()中恢复了原来的属性。

有属性的添加、删除绑定

命名的例子讨论如何使用bind()unbind()DirContext这两方法的重载版本。您使用DirContext的方法关联对象的属性,在绑定或子上下文时将她们添加到名字空间中。例如,您可能建立了Person对象,而后在为Person对象关联属性时将他绑定到名字空间中。

添加有属性的绑定

DirContext.bind()用来将属性绑定添加到上下文中。它的参数为须要绑定的对象名称, 以及属性集合。

 

// Create the object to be bound

Fruit fruit = new Fruit("orange");

 

// Create attributes to be associated with the object

Attributes attrs = new BasicAttributes(true); // case-ignore

Attribute objclass = new BasicAttribute("objectclass");

objclass.add("top");

objclass.add("organizationalUnit");

attrs.put(objclass);

 

// Perform bind

ctx.bind("ou=favorite, ou=Fruits", fruit, attrs);

 

这个例子建立了Fruit类的对象而且将他绑定到“ou=Fruits”上下文中,名称为“ou=favorite”。绑定有“objectclass”属性。若是接下来在ctx中查询“ou=favorite, ou=Fruits”,那么您将获得fruit对象。若是您想、获得“ou=favorite, ou=Fruits”的属性,您将获得刚才为对象添加的属性。如下是例子的输出:

 

# java Bind

orange

attribute: objectclass

value: top

value: organizationalUnit

value: javaObject

value: javaNamingReference

attribute: javaclassname

value: Fruit

attribute: javafactory

value: FruitFactory

attribute: javareferenceaddress

value: #0#fruit#orange

attribute: ou

value: favorite

 

显示的多于属性使用来保存关于对象(fruit)的一些信息。这些多于信息随后会进行详细介绍。

若是您两次运行这个例子,那么第二次运行时将会失败并抛出NameAlreadyBoundException。这是由于“ou=favorite”已经绑定到上下文“ou=Fruits”中。若是要成功,须要使用rebind()

替换有属性的绑定

DirContext.rebind()的做用是添加或修改绑定以及属性。它接受和bind()同样的参数。然而,使用rebind()时若是名称已经存在,那么将会首先Unbind而后再绑定新的对象和属性。

 

// Create the object to be bound

Fruit fruit = new Fruit("lemon");

 

// Create attributes to be associated with the object

Attributes attrs = new BasicAttributes(true); // case-ignore

Attribute objclass = new BasicAttribute("objectclass");

objclass.add("top");

objclass.add("organizationalUnit");

attrs.put(objclass);

 

// Perform bind

ctx.rebind("ou=favorite, ou=Fruits", fruit, attrs);

 

运行这个例子时,它替换了bind()例子中建立的绑定关系。

 

# java Rebind

lemon

attribute: objectclass

value: top

value: organizationalUnit

value: javaObject

value: javaNamingReference

attribute: javaclassname

value: Fruit

attribute: javafactory

value: FruitFactory

attribute: javareferenceaddress

value: #0#fruit#lemon

attribute: ou

value: favorite

 

搜索

目录提供的最有用的好处就是黄页功能,或搜索服务。您能够组合一个包含条目属性的查询,而后提交查询到目录中。而后目录返回知足查询的条目列表。例如,您能够访问目录,查询出保龄球平均成绩大于200的条目,或全部姓以“Sch”开头的人。

DirContext接口提供查询条目的方法,这些方法很复杂也很强大。搜索目录的不一样方面在如下章节中描述:

l         基本搜索

l         搜索过滤器

l         搜索控制

基本搜索

最简单的查询需须要您指定条目必须含有的属性集合以及进行查询的目标上下文。

如下代码建立了属性集合matchAttrs,其中有两个属性“sn”和“mail”。表名搜索条目必须有姓(sn)属性,其值为“Geisel”,以及“mail”属性能够是任意值。而后调用DirContext.search()查询上下文“ou=People”中和matchAttrs匹配的条目。

 

// Specify the attributes to match

// Ask for objects that has a surname ("sn") attribute with

// the value "Geisel" and the "mail" attribute

Attributes matchAttrs = new BasicAttributes(true); // ignore attribute name case

matchAttrs.put(new BasicAttribute("sn", "Geisel"));

matchAttrs.put(new BasicAttribute("mail"));

 

// Search for objects that have those matching attributes

NamingEnumeration answer = ctx.search("ou=People", matchAttrs);

 

You can then print the results as follows.

while (answer.hasMore()) {

    SearchResult sr = (SearchResult)answer.next();

    System.out.println(">>>" + sr.getName());

    printAttrs(sr.getAttributes());

}

 

printAttrs()方法和getAttributes()方法打印出属性集合相似。

返回结果以下:

 

# java SearchRetAll

>>>cn=Ted Geisel

attribute: sn

value: Geisel

attribute: objectclass

value: top

value: person

value: organizationalPerson

value: inetOrgPerson

attribute: jpegphoto

value: [B@1dacd78b

attribute: mail

value: Ted.Geisel@JNDITutorial.com

attribute: facsimiletelephonenumber

value: +1 408 555 2329

attribute: cn

value: Ted Geisel

attribute: telephonenumber

value: +1 408 555 5252

 

返回选中属性

上一个例子返回知足指定查询条件条目的全部属性。您能够经过向search()传递属性标识符数组的方式选择想要包含在结果集中的属性。在建立matchAttrs只有,您应该建立属性标识符的数组,以下所示:

 

// Specify the ids of the attributes to return

String[] attrIDs = {"sn", "telephonenumber", "golfhandicap", "mail"};

 

// Search for objects that have those matching attributes

NamingEnumeration answer = ctx.search("ou=People", matchAttrs, attrIDs);

 

这个例子返回条目的属性“sn”,“telephonenumber”,“golfhandicap”以及“mail”,其中条目有属性“mail”而且“sn”属性的值是“Geisel”。这个例子产生以下结果。(条目中没有“golfhandicap”属性,因此没有返回)。

 

# java Search

>>>cn=Ted Geisel

attribute: sn

value: Geisel

attribute: mail

value: Ted.Geisel@JNDITutorial.com

attribute: telephonenumber

value: +1 408 555 5252

 

过滤器

除了使用指定属性集合进行搜索外,您能够用搜索过滤器的形式进行搜索。搜索过滤器是一种搜索用的逻辑表达式。DirContext.search()可接受的过滤器语法在RFC 2254中定义。

如下搜索过滤器指定了知足查询条件的条目必须有值为“Geisel”的“sn”属性,以及值为任意的“mail”属性:

 

(&(sn=Geisel)(mail=*))

 

如下代码建立了过滤器以及默认的SearchControls,使用他们执行查询。这个查询的结果和基本查询中的一致。

 

// Create the default search controls

SearchControls ctls = new SearchControls();

 

// Specify the search filter to match

// Ask for objects that have the attribute "sn" == "Geisel"

// and the "mail" attribute

String filter = "(&(sn=Geisel)(mail=*))";

 

// Search for objects using the filter

NamingEnumeration answer = ctx.search("ou=People", filter, ctls);

 

搜索结果以下:

 

# java SearchWithFilterRetAll

>>>cn=Ted Geisel

attribute: sn

value: Geisel

attribute: objectclass

value: top

value: person

value: organizationalPerson

value: inetOrgPerson

attribute: jpegphoto

value: [B@1dacd75e

attribute: mail

value: Ted.Geisel@JNDITutorial.com

attribute: facsimiletelephonenumber

value: +1 408 555 2329

attribute: cn

value: Ted Geisel

attribute: telephonenumber

value: +1 408 555 5252

 

搜索过滤器语法概述

搜索过滤器是前缀标记的搜索表达式(逻辑运算符在表达式前面)。下表列举了建立过滤器使用的符号。

 

符号

描述

&

与(列表中全部项必须为true

|

或(列表中至少一个必须为true

!

非(求反的项不能为true

=

相等(根据属性的匹配规则)

~=

近似等于(根据属性的匹配规则)

>=

大于(根据属性的匹配规则)

<=

小于(根据属性的匹配规则)

=*

存在(条目中必须有这个属性,但值不作限制)

*

通配符(表示这个位置能够有一个或多个字符),当指定属性值时用到

\

转义符(当遇到“*”,“(”,“)”时进行转义)

 

过滤器中的每个条目使用属性标识符和属性值或符号表示属性值组成。例如,项“sn=Geisel”表示“sn”属性的值必须为“Geisel”,同时项“mail=*”表示“mail”属性必须存在。

每项必须包含在括号以内,例如“(sn=Geisel)”。这些项使用逻辑运算符,例如&,建立逻辑表达式,例如“(& (sn=Geisel) (mail=*))”。

每个逻辑表达式能够进一步组成其余项,例如“(| (& (sn=Geisel) (mail=*)) (sn=L*))”。这个例子请求的条目中或者含有值为“Geisel”的“sn”属性和“mail”属性或者“sn”属性以字母“L”开头。

关于语法的详细描述,请参考RFC 2254

返回选中属性

上一个例子返回知足指定过滤器的条目中的全部属性。您能够经过设置搜索控制参数的方法选择返回属性。您建立想要包含在结果中的属性标识符集合,而后将他传递到SearchControls.setReturningAttributes()中。以下所示:

 

// Specify the ids of the attributes to return

String[] attrIDs = {"sn", "telephonenumber", "golfhandicap", "mail"};

SearchControls ctls = new SearchControls();

ctls.setReturningAttributes(attrIDs);

 

这个例子和基本搜索一节中返回选择的属性部分的结果一致。运行后的结果以下。(这个条目没有“golfhandicap”属性,因此没有返回)。

 

# java SearchWithFilter

>>>cn=Ted Geisel

attribute: sn

value: Geisel

attribute: mail

value: Ted.Geisel@JNDITutorial.com

attribute: telephonenumber

value: +1 408 555 5252

 

范围

默认的SearchControls指定搜索只在命名空间中进行(SearchControls.ONELEVEL_SCOPE)。这个默认选项在搜索过滤器一节中使用。

除了默认选项以外,您能够指定搜索在整个子树或只在命名对象中执行。

搜索子树

对于整个子树的搜索不但搜索命名对象并且搜索它的后代。要按照这种方式进行搜索,按照下面的方式向SearchControls.setSearchScope()中传递SearchControls.SUBTREE_SCOPE参数:

 

// Specify the ids of the attributes to return

String[] attrIDs = {"sn", "telephonenumber", "golfhandicap", "mail"};

SearchControls ctls = new SearchControls();

ctls.setReturningAttributes(attrIDs);

ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);

 

// Specify the search filter to match

// Ask for objects that have the attribute "sn" == "Geisel"

// and the "mail" attribute

String filter = "(&(sn=Geisel)(mail=*))";

 

// Search the subtree for objects by using the filter

NamingEnumeration answer = ctx.search("", filter, ctls);

 

这个例子搜索了ctx上下文的子树,获得知足搜索过滤器的条目。它在子树中找到了知足过滤器的“cn= Ted Geisel, ou=People”条目。

 

# java SearchSubtree

>>>cn=Ted Geisel, ou=People

attribute: sn

value: Geisel

attribute: mail

value: Ted.Geisel@JNDITutorial.com

attribute: telephonenumber

value: +1 408 555 5252

 

搜索命名对象

您也能够搜索命名对象。这样作是颇有用的,例如,测试命名对象是否知足搜索过滤器。为了搜索命名对象,将SearchControls.OBJECT_SCOPE传递到setSearchScope()中。

 

// Specify the ids of the attributes to return

String[] attrIDs = {"sn", "telephonenumber", "golfhandicap", "mail"};

SearchControls ctls = new SearchControls();

ctls.setReturningAttributes(attrIDs);

ctls.setSearchScope(SearchControls.OBJECT_SCOPE);

 

// Specify the search filter to match

// Ask for objects that have the attribute "sn" == "Geisel"

// and the "mail" attribute

String filter = "(&(sn=Geisel)(mail=*))";

 

// Search the subtree for objects by using the filter

NamingEnumeration answer =

    ctx.search("cn=Ted Geisel, ou=People", filter, ctls);

 

这个例子测试是否对象“cn=Ted Geisel, ou=People”知足给定的过滤器。

 

# java SearchObject

>>> 

attribute: sn

value: Geisel

attribute: mail

value: Ted.Geisel@JNDITutorial.com

attribute: telephonenumber

value: +1 408 555 5252

 

例子找到了一个结果并进行打印。注意结果中的名称字段为空。由于对象的名称是进行查询的上下文(cn=Ted Geisel, ou=People)。

结果数量

有时,查询可能返回了太多的结果同时您想限制结果集的大小。这时您可使用限制数量的搜索控制。默认状况下,搜索没有数量限制――它将会返回找到的全部结果。要设置搜索的数量限制,将数字传递到SearchControls.setCountLimit()中。

如下例子设置数量限制为1

 

// Set the search controls to limit the count to 1

SearchControls ctls = new SearchControls();

ctls.setCountLimit(1);

 

若是程序尝试获得更多的结果,那么会抛出SizeLimitExceededException。若是程序设置数量限制,那么或者将这个异常和NamingExceptions异常区别对待或者按照数量限制的大小,不请求超过数量的结果。

知足搜索数量限制是控制程序消耗资源的一种方法(例如内存和网络带宽)。其余控制资源的方法是尽可能使用小的搜索过滤器,在合适的上下文中开始搜索,使用合适的范围。

时间限制

搜索时间限制是搜索操做等待应答的时间上限。当您不想为应答等待太长时间时这颇有用。若是搜索操做超过期间限制还没完成,那么将会抛出TimeLimitExceededException异常。

为了设置搜索的时间限制,将毫秒数传递到SearchControls.setTimeLimit()便可。如下例子将时间限制设置为1秒。

 

// Set the search controls to limit the time to 1 second (1000 ms)

SearchControls ctls = new SearchControls();

ctls.setTimeLimit(1000);

 

要让这个例子运行超时,须要从新配置,或者使用一个慢的服务器或这使用有不少条目的服务器。您还可使用其余方式让搜索超过1秒。

时间限制为0表示不进行时间限制,这样请求将会进行无限等待。

常见问题解答

当您使用JNDI类运行成功编译的程序时可能遇到的主要问题以下:

 

l         没有初始化上下文

l         链接被拒绝

l         链接失败

l         程序挂起

l         名字没有找到

l         不能链接任意主机

l         不能配置访问系统属性

l         不能使用CRAM-MD5认证

 

1.    获得NoInitialContextException

缘由:您没有指定使用的初始化上下文。特别的,Context.INITIAL_CONTEXT_FACTORY环境变量没有设置成为初始化上下文的工厂类名。后者,找不到Context.INITIAL_CONTEXT_FACTORY配置的可获得的服务提供者。

解决方案:将环境变量Context.INITIAL_CONTEXT_FACTORY设置为您使用的初始化上下文类名。详细信息请参考配置一节。

若是属性已经设置,那么确认类名没有输入错误,而且类在您的程序中可见(或者在classpath中或者在JREjre/lib/ext目录下)。Java平台包含服务提供者有LDAPCOS命名,DNS以及RMI注册。全部其余的服务提供者必须安装而且添加到执行环境中。

2.    获得CommunicationException异常,表示“链接被拒绝”。

缘由Context.PROVIDER_URL环境参数表示的服务器和端口没有提供访问。可能有人禁用或关闭了服务。或者输入了错误的服务器名称或端口号。

解决方案:检查端口上确实运行了服务,若是须要就重启服务器。这种检查依赖于您使用的LDAP服务器。一般,可使用管理控制台或工具管理服务器。您可使用工具确认服务器的状态。

3.    LDAP服务器向其余工具应答(例如管理控制台)但不该答您程序的请求。

缘由:服务器没有应答LDAP v3的链接全逆光球。一些服务器(尤为是公共服务器)不能正确的应答LDAP v3,使用忽略的方式代替拒绝。同时,一些LDAP v3服务器有错误处理机制,SunLDAP服务提供者自动发送而且一般返回特定服务器错误码。

解决方案:尝试设置环境参数“java.naming.ldap.version”为“2”。LDAP服务提供者默认尝试使用LDAP v3链接LDAP服务器,而后使用LDAP v2。若是服务器静默忽略v3的请求,那么提供者假设请求生效了。使用这种服务器,您必须显式的设置协议版本,确保服务器有正确的行为。

若是服务器是v3服务器,那么尝试在建立初始化上下文以前设置这些环境参数:

 

env.put(Context.REFERRAL, "throw");

 

这样关闭了LDAP提供者自动发送的控制(更多信息请参考JNDI教程)。

4.    程序挂起。

缘由:当您尝试执行的查实会生成太多结果或须要服务器查询不少条目才能生成结果时,服务器(尤为是公共的)不该答(不是一个失败应答)。这种服务器基于预请求计算花费的方式尝试减小资源消耗。

或者,您尝试使用安全套接字层(SSL)但服务器/端口不支持,反之(您尝试使用普通套接字与SSL端口对话)。

最终,服务器或者由于负载缘由很是慢的应答,或彻底不该答某些请求。

解决方案:若是程序由于服务器为了减小资源消耗而挂起,那么重试请求会获得单一应答或只有不多的应答。这样能够帮助您判断服务器是否还在活动。这样,您能够加宽原有查询,从新发送。

若是您的程序由于SSL问题挂起,那么您须要找到SSL端口而后正确设置Context.SECURITY_PROTOCOL环境参数。若是端口是SSL端口,那么这个参数应该设置成“ssl”。若是不是SSL端口,这个参数不该该设置。

若是程序不是由于上述缘由挂起,使用com.sun.jndi.ldap.read.timeout表示读取超时。这个参数的值是一个字符串,表示LDAP请求读取超时的毫秒数。若是LDAP提供者不能在周期内获得应答,那么放弃读取尝试。数字应该大于0。等于或小于0表示不指定读取超时时间,等于无限等待获得应答。

若是没有指定这个参数,默认状况下会一直等待,直到获得应答。

例如:

 

env.put("com.sun.jndi.ldap.read.timeout", "5000");

 

表示若是LDAP服务器不能在5秒中内应答,将放弃读取请求。

5.    您获得NameNotFoundException异常。

缘由:当您为LDAP初始化了初始上下文,提供了根辨别名。例如,若是您为初始上下文设置Context.PROVIDER_URL为“ldap://ldapserver:389/o=JNDITutorial”,而后提供名称“cn=Joe,c=us”,那么您向LDAP服务传递的全名为“cn=Joe,c=us,o=JNDITutorial”。若是这确实是您想要的名称,那么您应该检验服务器肯定包含这个条目。

同时,若是您在认证时提供错误的辨别名,Sun Java目录服务器返回错误。例如,若是您设置Context.SECURITY_PRINCIPAL 环境参数为“cn=Admin, o=Tutorial”,而且“cn=Admin, o=Tutorial”不是LDAP服务器的条目,LDAP提供者将要抛出NameNotFoundExceptionSun Java目录服务器返回的其实是一些认证异常,不是“name not found”。

解决方案:确认您提供的名字是服务器上存在的。您能够经过列举父上下文的全部条目或使用其余工具例如服务器的管理员控制台来确认。

 

如下是在部署applet时可能遇到的问题。

 

6.    当您的applet尝试链接目录服务器时获得AppletSecurityException异常,服务器正运行在和下载applet不一样的机器上。

缘由applet没有签名,因此只能链接到加载它的机器。或者,若是appet已经签名,浏览器没有授予applet链接目录服务器的权限。

解决方案:若是您想容许applet链接任意机器上的目录服务器,那么须要签名整个applet以及applet使用的JNDI jar。关于jar的签名,请参考签名和验证jar文件。

7.    当您的applet尝试使用系统属性设置环境属性时出现AppletSecurityException异常。

缘由:浏览器限制访问系统参数,而且当您尝试读取时抛出SecurityException

解决方案:若是您须要为applet获得输入,尝试使用applet params代替。

8.    applet运行在Firefox中尝试使用CRAM-MD5LDAP进行认证时抛出AppletSecurityException

缘由Firefox禁止访问java.security包。LDAP提供者使用java.security.MessageDigest提供的信息摘要功能来实现CRAM-MD5

解决方案:使用Java插件。

相关文章
相关标签/搜索