Java 中文官方教程 2022 版(四十七)

原文:docs.oracle.com/javase/tutorial/reallybigindex.html

命名异常

原文:docs.oracle.com/javase/tutorial/jndi/ops/exception.html

JNDI 包中的许多方法在需要指示无法执行请求的操作时会抛出NamingException。通常,您会看到围绕可能引发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的特定子类,您需要单独捕获子类。例如,以下代码特别处理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()并指定要返回多少个答案的计数限制(n),则search()将返回最多 n 个结果的枚举。如果结果数量超过 n,则当第 n+1 次调用NamingEnumeration.hasMore()时,将抛出SizeLimitExceededException。请参阅本课程的结果计数示例代码。

本教程中的示例

在本教程文本中嵌入的内联示例代码中,通常为了可读性而省略了try/catch子句。通常,因为这里只显示代码片段,所以只包含直接用于说明概念的行。如果查看本教程附带的源文件,您将看到用于NamingExceptiontry/catch子句的适当位置。

javax.naming 包中的异常可以在这里找到。

查找一个对象

原文:docs.oracle.com/javase/tutorial/jndi/ops/lookup.html

要从命名服务中查找对象,请使用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()的结果转换为目标类。

例如,以下代码查找对象"cn=Rosanna Lee,ou=People"并将其转换为LdapContext

import javax.naming.ldap.LdapContext;
...
LdapContext ctx = (LdapContext) ctx.lookup("cn=Rosanna Lee,ou=People");

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

查找示例的图表

Java SE 6 中有两个新的静态方法可用于查找名称:

这些方法提供了一种快捷的查找名称的方式,而无需实例化 InitialContext。

列出上下文

原文:docs.oracle.com/javase/tutorial/jndi/ops/list.html

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()返回一个Binding的枚举。BindingNameClassPair的子类。一个绑定不仅包含对象的名称和类名,还包含对象本身。以下代码枚举了"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可以以自然、显式或意外的方式终止。

无论枚举如何被终止,一旦终止,就不能再使用。在终止的枚举上调用方法会产生未定义的结果。

为什么有两种不同的列出方法?

list()适用于浏览器样式的应用程序,只需显示上下文中对象的名称。例如,浏览器可能会列出上下文中的名称,并等待用户选择其中一个或几个名称以执行进一步操作。这类应用程序通常不需要访问上下文中的所有对象。

listBindings() 适用于需要对上下文中的对象进行批量操作的应用程序。例如,备份应用程序可能需要对文件目录中的所有对象执行“文件统计”操作。或者打印机管理程序可能希望重新启动建筑物中的所有打印机。为了执行这些操作,这些应用程序需要获取上下文中绑定的所有对象。因此,将对象作为枚举的一部分返回更为方便。

应用程序可以使用list()或可能更昂贵的listBindings(),具体取决于它所需的信息类型。

添加、替换或移除绑定

原文:docs.oracle.com/javase/tutorial/jndi/ops/bind.html

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

添加绑定

Context.bind() 用于向上下文添加绑定。它接受对象的名称和要绑定的对象作为参数。


在继续之前: 本课程中的示例需要您对模式进行添加。您必须在 LDAP 服务器中关闭模式检查,或者将伴随本教程的模式添加到服务器中。这两项任务通常由目录服务器的管理员执行。请参阅 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() 相同的参数,但语义是如果名称已经绑定,则将解绑并绑定新给定的对象。

// 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()rebind()例子创建的绑定。

重命名

原文:docs.oracle.com/javase/tutorial/jndi/ops/rename.html

您可以使用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 用户的高级主题课程。

创建和销毁子上下文

原文:docs.oracle.com/javase/tutorial/jndi/ops/create.html

Context接口包含用于创建和销毁 子上下文 的方法,即绑定在同一类型的另一个上下文中的上下文。

此处描述的示例使用具有属性的对象,并在目录中创建一个子上下文。您可以使用这些DirContext方法在将绑定或子上下文添加到命名空间时将属性与对象关联起来。例如,您可以创建一个Person对象并将其绑定到命名空间,并同时关联有关该Person对象的属性。命名等效项将没有属性。

createSubcontext()与 bind()的不同之处在于它创建一个新对象,即一个新的上下文,将其绑定到目录,而 bind()将给定对象绑定到目录。

创建上下文

要创建命名上下文,您需要向createSubcontext() 提供要创建的上下文的名称。要创建具有属性的上下文,您需要向DirContext.createSubcontext() 提供要创建的上下文的名称和其属性。


在继续之前: 本课程中的示例需要您对模式进行添加。您必须在 LDAP 服务器中关闭模式检查,或者将伴随本教程的模式添加到服务器中。这两项任务通常由目录服务器的管理员执行。请参阅 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);

This example 创建了一个名为"ou=NewOu"的新上下文,在上下文ctx中有一个属性"objectclass",其值为"top""organizationalUnit"

# java Create
ou=Groups: javax.naming.directory.DirContext
ou=People: javax.naming.directory.DirContext
ou=NewOu: javax.naming.directory.DirContext

This example 创建了一个名为"NewOu"的新上下文,它是ctx的子上下文。

图表显示新子上下文。

销毁上下文

要销毁一个上下文,您需要向destroySubcontext() 提供要销毁的上下文的名称。

// Destroy the context
ctx.destroySubcontext("NewOu");

This example 在上下文ctx中销毁了上下文"NewOu"

属性名称

原文:docs.oracle.com/javase/tutorial/jndi/ops/attrnames.html

一个属性由一个属性标识符和一组属性值组成。属性标识符,也称为属性名称,是一个字符串,用于标识属性。属性值是属性的内容,其类型不限于字符串。当您想要为检索、搜索或修改指定特定属性时,您使用属性名称。名称也会被返回操作返回的属性(例如在目录中执行读取或搜索时)。

在使用属性名称时,您需要了解某些目录服务器功能,以免对结果感到惊讶。这些功能在下一小节中描述。

属性类型

在 LDAP 等目录中,属性的名称标识属性的类型,通常称为属性类型名称。例如,属性名称"cn"也称为属性类型名称。属性的类型定义指定属性值应具有的语法,它是否可以具有多个值,以及在执行比较和排序操作时使用的相等性和排序规则。

属性子类化

一些目录实现支持属性子类化,其中服务器允许以其他属性类型定义属性类型。例如,"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"属性将返回所有三个属性。

当访问支持此功能的目录时,您必须注意服务器可能返回与您请求的名称不同的属性。

读取属性

原文:docs.oracle.com/javase/tutorial/jndi/ops/getattrs.html

要从目录中读取对象的属性,请使用DirContext.getAttributes()并传递您想要属性的对象的名称。假设命名服务中的一个对象的名称为"cn=Ted Geisel, ou=People"。要检索此对象的属性,您需要类似于以下的code

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.example.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"属性。此对象除了"golfhandicap"属性外,其余属性都存在,因此答案中返回了三个属性。以下是示例的输出。

# java Getattrs
attribute: sn
value: Geisel
attribute: mail
value: Ted.Geisel@JNDITutorial.example.com
attribute: telephonenumber
value: +1 408 555 5252

修改属性

原文:docs.oracle.com/javase/tutorial/jndi/ops/modattrs.html

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

使用修改列表

修改对象属性的一种方法是提供修改请求的列表(ModificationItem)。每个ModificationItem包含一个数字常量,指示要进行的修改类型,以及描述要进行的修改的Attribute。以下是三种修改类型:

修改按列表中出现的顺序应用。要么执行所有修改,要么一个也不执行。

以下代码创建了一个修改列表。它用值"geisel@wizards.com"替换了"mail"属性的值,向"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 Active Directory: Active Directory 将"telephonenumber"定义为单值属性,与RFC 2256相悖。要使此示例针对 Active Directory 正常工作,您必须使用除"telephonenumber"之外的属性,或将DirContext.ADD_ATTRIBUTE更改为DirContext.REPLACE_ATTRIBUTE


创建此修改列表后,您可以将其提供给modifyAttributes() 如下所示。

// Perform the requested modifications on the named object
ctx.modifyAttributes(name, mods);

使用属性

或者,您可以通过指定修改类型和要应用修改的属性来执行修改。

例如,以下行将用orig中的属性(在name中标识)替换为orig中的属性:

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

name 的任何其他属性保持不变。

modifyAttributes() 的这两种用法在示例程序中演示。该程序通过使用修改列表修改属性,然后使用modifyAttributes()的第二种形式来恢复原始属性。

添加、替换具有属性的绑定

原文:docs.oracle.com/javase/tutorial/jndi/ops/bindattr.html

讨论了命名示例如何使用bind()rebind()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=favorite"中,相对于ctx命名为"ou=Fruits"的上下文。此绑定具有"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()的语义要求,如果名称已经绑定,则将解除绑定,并绑定新给定的对象和属性。

// 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

搜索

原文:docs.oracle.com/javase/tutorial/jndi/ops/search.html

目录提供的最有用的功能之一是其黄页搜索服务。您可以组成一个由您正在寻找的条目的属性组成的查询,并将该查询提交给目录。然后目录返回满足查询条件的条目列表。例如,您可以要求目录返回所有击球平均分大于 200 的条目,或者所有代表姓氏以"Sch"开头的人的条目。

DirContext接口提供了几种搜索目录的方法,具有逐渐增加的复杂性和功能。搜索目录的各个方面在以下部分中介绍:

  • 基本搜索

  • 搜索过滤器

  • 搜索控件

基本搜索

原文:docs.oracle.com/javase/tutorial/jndi/ops/basicsearch.html

最简单的搜索形式要求您指定条目必须具有的属性集,以及执行搜索的目标上下文的名称。

以下代码创建了一个属性集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

// ignore attribute name case
Attributes matchAttrs = new BasicAttributes(true); 
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);

你可以按照以下方式打印结果。

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.example.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",并且具有一个值为"Geisel""sn"属性。 这个例子产生以下结果。(该条目没有"golfhandicap"属性,因此不返回。)

# java Search 
>>>cn=Ted Geisel
attribute: sn
value: Geisel
attribute: mail
value: Ted.Geisel@JNDITutorial.example.com
attribute: telephonenumber
value: +1 408 555 5252

过滤器

原文:docs.oracle.com/javase/tutorial/jndi/ops/filter.html

除了使用一组属性指定搜索外,还可以以搜索过滤器的形式指定搜索。搜索过滤器是以逻辑表达式形式表达的搜索查询。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.example.com
attribute: facsimiletelephonenumber
value: +1 408 555 2329
attribute: cn
value: Ted Geisel
attribute: telephonenumber
value: +1 408 555 5252

搜索过滤器语法快速概述

搜索过滤器语法基本上是前缀表示法中的逻辑表达式(即,逻辑运算符出现在其参数之前)。以下表列出了用于创建过滤器的符号。

符号 描述
& 合取(即, — 列表中的所有项目必须为真)
| 析取(即, — 一个或多个备选项必须为真)
! 否定(即, — 被否定的项目必须为假)
= 等于(根据属性匹配规则)
~= 大致相等(根据属性匹配规则)
>= 大于(根据属性匹配规则)
<= 小于(根据属性匹配规则)
=* 存在(即,条目必须具有属性,但其值无关紧要)
* 通配符(表示该位置可以出现零个或多个字符);用于指定要匹配的属性值时使用
\ 转义(用于在属性值中出现'*'、'('或')'时进行转义)

每个过滤器中的项目都是使用属性标识符和属性值或表示属性值的符号组成的。例如,项目"sn=Geisel"表示"sn"属性必须具有属性值"Geisel",而项目"mail=*"表示"mail"属性必须存在。

每个项目必须用一组括号括起来,如"(sn=Geisel)"。这些项目使用逻辑运算符(如"&"(合取))组合,以创建逻辑表达式,如"(& (sn=Geisel) (mail=*))"

每个逻辑表达式都可以进一步由其他本身是逻辑表达式的项目组成,就像"(| (& (sn=Geisel) (mail=*)) (sn=L*))"中那样。最后一个示例请求具有"sn"属性为"Geisel""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.example.com
attribute: telephonenumber
value: +1 408 555 5252

范围

原文:docs.oracle.com/javase/tutorial/jndi/ops/scope.html

默认的SearchControls指定搜索在命名上下文中执行(SearchControls.ONELEVEL_SCOPE)。这个默认设置在搜索过滤器部分的示例中使用。

除了这个默认设置外,您还可以指定搜索在整个子树或仅在命名对象中执行。

搜索子树

对整个子树进行搜索将搜索命名对象及其所有后代。要使搜索以这种方式执行,请将SearchControls.SUBTREE_SCOPE传递给SearchControls.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.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.example.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.example.com
attribute: telephonenumber
value: +1 408 555 5252

该示例找到一个答案并将其打印出来。请注意,结果的名称是空字符串。这是因为对象的名称始终相对于搜索上下文命名(在本例中为"cn=Ted Geisel, ou=People")。

结果计数

原文:docs.oracle.com/javase/tutorial/jndi/ops/countlimit.html

有时,查询可能产生太多答案,你希望限制返回的答案数量。你可以通过使用计数限制搜索控件来实现这一点。默认情况下,搜索没有计数限制 - 它将返回它找到的所有答案。要设置搜索的计数限制,请将数字传递给SearchControls.setCountLimit()

以下示例将计数限制设置为 1。

// Set the search controls to limit the count to 1
SearchControls ctls = new SearchControls();
ctls.setCountLimit(1);

如果程序尝试获取超过计数限制数量的结果,那么将抛出SizeLimitExceededException。因此,如果程序设置了计数限制,则应该区分此异常和其他NamingException,或者跟踪计数限制并不请求超过该数量的结果。

指定搜索的计数限制是控制应用程序消耗资源(如内存和网络带宽)的一种方式。控制消耗资源的其他方法包括缩小你的搜索过滤器(更具体地说明你要寻找什么)、在适当的上下文中开始搜索,并使用适当的范围。

时间限制

原文:docs.oracle.com/javase/tutorial/jndi/ops/timelimit.html

对搜索设置时间限制会对搜索操作等待答复的时间上限。当您不希望等待太长时间以获取答复时,这很有用。如果在搜索操作完成之前超过了指定的时间限制,则会抛出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 秒。

时间限制为零意味着没有设置时间限制,对目录的调用将无限期等待答复。

故障排除提示

原文:docs.oracle.com/javase/tutorial/jndi/ops/faq.html

运行成功编译的使用 JNDI 类的程序时可能遇到的最常见问题如下。


  1. 没有初始上下文

  2. 连接被拒绝

  3. 连接失败

  4. 程序挂起

  5. 找不到名称

  6. 无法连接到任意主机

  7. 无法访问系统属性进行配置

  8. 无法使用 CRAM-MD5 进行身份验证


1. 您收到了NoInitialContextException

原因:您没有指定用于初始上下文的实现。具体来说,Context.INITIAL_CONTEXT_FACTORY 环境属性未设置为将创建初始上下文的工厂类名。或者,您没有使程序可用于服务提供者类(由Context.INITIAL_CONTEXT_FACTORY命名)。

解决方案:将Context.INITIAL_CONTEXT_FACTORY环境属性设置为您正在使用的初始上下文实现的类名。有关详细信息,请参阅配置部分。

如果已设置属性,请确保类名未拼写错误,并且所命名的类对您的程序可用(在其类路径中或安装在 JRE 的 jre/lib/ext 目录中)。Java 平台包括用于 LDAP、COS 命名、DNS 和 RMI 注册表的服务提供者。所有其他服务提供者必须安装并添加到执行环境中。

2. 您收到了CommunicationException,指示“连接被拒绝”。

原因:由Context.PROVIDER_URL环境属性标识的服务器和端口未被服务器提供。也许有人已禁用或关闭了运行服务器的机器。或者,您可能拼写错误了服务器的名称或端口号。

解决方案:检查该端口上确实有服务器在运行,并在必要时重新启动服务器。您执行此检查的方式取决于您使用的 LDAP 服务器。通常,可用管理控制台或工具来管理服务器。您可以使用该工具验证服务器的状态。

3. LDAP 服务器对其他实用程序(如其管理控制台)做出响应,但似乎没有对您程序的请求做出响应。

原因:服务器未对 LDAP v3 连接请求做出响应。一些服务器(特别是公共服务器)未正确响应 LDAP v3,而是忽略请求而不是拒绝它们。此外,一些 LDAP v3 服务器在处理 Oracle 的 LDAP 服务提供者自动发送的控件时存在问题,并经常返回服务器特定的失败代码。

解决方案。尝试将环境属性"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 提供程序在该时间段内无法获得 LDAP 响应,则会中止读取尝试。整数应大于零。小于或等于零的整数表示未指定读取超时,相当于一直等待响应直到收到。

如果未指定此属性,则默认是等待响应直到收到为止。

例如,

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"。如果这确实是您打算的名称,请检查您的服务器,确保它包含这样的条目。

此外,如果您为认证目的提供了不正确的专有名称,Oracle 目录服务器也会返回此错误。例如,如果您将Context.SECURITY_PRINCIPAL环境属性设置为"cn=Admin, o=Tutorial",而"cn=Admin, o=Tutorial"不是 LDAP 服务器上的条目,那么 LDAP 提供程序将抛出NameNotFoundException。实际上,Oracle 目录服务器应返回与认证相关的错误,而不是“未找到名称”。

解决方案:验证您提供的名称是否是服务器上已存在的条目。您可以通过列出条目的父上下文或使用其他工具(如目录服务器的管理控制台)来实现这一点。


在尝试部署使用 JNDI 类的小程序时,您可能会遇到一些问题。

6. 当您的小程序尝试与在与加载小程序的机器不同的机器上运行的目录服务器通信时,您会收到AppletSecurityException

原因:您的小程序未经签名,因此只能连接到加载它的机器。或者,如果小程序已经签名,浏览器尚未授予小程序连接到目录服务器机器的权限。

解决方案:如果您希望允许小程序连接到运行在任意机器上的目录服务器,则需要对您的小程序和小程序将使用的所有 JNDI JAR 文件进行签名。有关签署 jar 文件的信息,请参见签署和验证 JAR 文件。

7. 当您的小程序尝试使用系统属性设置环境属性时,您会收到AppletSecurityException

原因:Web 浏览器限制对系统属性的访问,并且如果您尝试读取它们,则会抛出SecurityException

解决方案:如果您需要为您的小程序获取输入,则尝试使用小程序参数。

8. 当在 Firefox 中运行的小程序尝试使用 CRAM-MD5 对 LDAP 服务器进行身份验证时,您会收到AppletSecurityException

原因: Firefox 禁用对 java.security 包的访问。LDAP 提供程序使用了 java.security.MessageDigest 提供的消息摘要功能来实现 CRAM-MD5。

解决方案: 使用 Java 插件。


课程:LDAP 用户的高级主题

原文:docs.oracle.com/javase/tutorial/jndi/ldap/index.html

LDAP教程中的课程提供了 LDAP 和 JNDI 之间的映射细节。它们还提供了通过 JNDI 访问 LDAP 服务的提示和技巧。

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(或 LDAP v3),这是其前身 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 支持国际化、各种认证机制、引荐和通用部署机制。它允许通过使用扩展控制向协议添加新功能,而无需对协议进行更改。

LDAP v3

原文:docs.oracle.com/javase/tutorial/jndi/ldap/ldap.html

国际化

国际化是通过国际字符集(ISO 10646)来处理的,用于表示协议元素是字符串(如 DN)。版本 3 还不同于版本 2,它使用 UTF-8 来编码其字符串。

认证

除了匿名、简单(明文密码)认证外,LDAP v3 还使用简单认证和安全层(SASL)认证框架(RFC 2222)允许使用不同的认证机制与 LDAP 一起使用。SASL 指定了一种挑战-响应协议,其中数据在客户端和服务器之间交换,用于认证目的。

目前定义了几种 SASL 机制:DIGEST-MD5, CRAM-MD5, Anonymous, External, S/Key, GSSAPI, 和 Kerberos v4。LDAP v3 客户端可以使用任何这些 SASL 机制,前提是 LDAP v3 服务器支持它们。此外,可以在不必更改 LDAP 的情况下使用新的(尚未定义的)SASL 机制。

转发

转发是服务器发送回客户端的信息,指示请求的信息可以在另一个位置(可能在另一个服务器上)找到。在 LDAP v2 中,服务器应处理转发而不将其返回给客户端。这是因为处理转发可能非常复杂,因此会导致更复杂的客户端。随着服务器的构建和部署,发现转发很有用,但并不是所有服务器都支持服务器端转发处理。因此,找到了一种方法来改进协议以允许返回转发。这是通过将转发放置在“部分结果”错误响应的错误消息中来完成的。

LDAP v3 明确支持转发,并允许服务器直接将转发返回给客户端。本课程不涵盖转发,但您可以随时参考JNDI 教程来管理应用程序中的转发。

部署

诸如 LDAP 之类的常见协议对确保所有目录客户端和服务器“说同一种语言”非常有用。当在网络中部署许多不同的目录客户端应用程序和目录服务器时,所有这些实体讨论相同的对象也非常有用。

目录模式指定了目录可能具有的对象类型以及每种对象类型可能具有的强制和可选属性,等等。LDAP v3 基于 X.500 标准为网络中常见的对象(如国家、地点、组织、用户/人员、组和设备)定义了一个模式(RFC 2252RFC 2256)。它还定义了客户端应用程序访问服务器模式的方法,以便了解特定服务器支持的对象和属性类型。

LDAP v3 进一步定义了一组用于表示属性值的语法(RFC 2252)。编写需要访问模式的 Java 应用程序,请参考JNDI 教程

扩展

除了预定义的操作集合,如“搜索”和“修改”,LDAP v3 还定义了一个“扩展”操作。 “扩展”操作以请求作为参数并返回响应。请求包含标识请求的标识符和请求的参数。响应包含执行请求的结果。 “扩展”操作请求/响应对称为扩展。例如,可以有一个用于启动 TLS 的扩展,这是客户端向服务器发出的激活启动 TLS 协议的请求。

这些扩展可以是标准的(由 LDAP 社区定义)或专有的(由特定目录供应商定义)。编写需要使用扩展的应用程序,请参考JNDI 教程

控制

另一种添加新功能的方法是使用控制。LDAP v3 允许通过使用控制来修改任何操作的行为。可以在操作中发送任意数量的控制,并且可以在其结果中返回任意数量的控制。例如,您可以在“搜索”操作中发送一个排序控制,告诉服务器根据"name"属性对搜索结果进行排序。

像扩展一样,这些控制可以是标准的或专有的。标准控制在平台中提供。编写需要使用控制的应用程序,请参考JNDI 教程

JNDI 作为 LDAP API

原文:docs.oracle.com/javase/tutorial/jndi/ldap/jndi.html

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 访问 LDAP 操作的详细信息。

名称

由于联邦制度的结果,您提供给 JNDI 上下文方法的名称可以跨越多个命名空间。这些被称为复合名称。当使用 JNDI 访问 LDAP 服务时,您应该意识到字符串名称中的斜杠字符("/")对 JNDI 具有特殊含义。如果 LDAP 条目的名称包含此字符,则需要对其进行转义(使用反斜杠字符"")。例如,具有名称"cn=O/R"的 LDAP 条目必须呈现为字符串"cn=O\\/R"以供 JNDI 上下文方法使用。有关名称的更多信息,请查看JNDI 教程LdapNameRdn类简化了 LDAP 名称的创建和操作。

在协议中使用的 LDAP 名称始终是完全限定的名称,用于标识从 LDAP 命名空间的根(由服务器定义)开始的条目。以下是一些完全限定的 LDAP 名称的示例。

cn=Ted Geisel, ou=Marketing, o=Some Corporation, c=gb
cn=Vinnie Ryan, ou=People, o=JNDITutorial

在 JNDI 中,然而,名称始终是相对的;也就是说,你总是相对于上下文命名一个对象。例如,你可以将条目"cn=Vinnie Ryan"命名为相对于名为"ou=People, o=JNDITutorial"的上下文。或者你可以将条目"cn=Vinnie Ryan, ou=People"命名为相对于名为"o=JNDITutorial"的上下文。或者,你可以创建一个指向 LDAP 服务器命名空间根的初始上下文,并将条目命名为"cn=Vinnie Ryan, ou=People, o=JNDITutorial"

在 JNDI 中,你还可以使用 LDAP URL 来命名 LDAP 条目。请参阅JNDI 教程中关于 LDAP URL 的讨论。

LDAP 操作如何映射到 JNDI API

原文:docs.oracle.com/javase/tutorial/jndi/ldap/operations.html

LDAP 定义了一组操作或请求(参见 RFC 2251)。在 JNDI 中,这些映射到 DirContextLdapContext 接口上的操作(它们是 Context 的子接口)。例如,当调用者调用 DirContext 方法时,LDAP 服务提供程序通过向 LDAP 服务器发送 LDAP 请求来实现该方法。

下表显示了 LDAP 中的操作如何对应到 JNDI 方法。

LDAP 操作 对应的 JNDI 方法
绑定 JNDI 中创建与 LDAP 服务器的初始连接的对应方式是创建一个 InitialDirContext。当应用程序创建初始上下文时,通过环境属性提供客户端身份验证信息。要更改现有上下文的身份验证信息,请使用 Context.addToEnvironment()Context.removeFromEnvironment()
解绑 Context.close() 用于释放上下文使用的资源。它与 LDAP 的 "unbind" 操作不同之处在于,在给定的服务提供程序实现中,资源可以在上下文之间共享,因此关闭一个上下文不会释放所有资源,如果这些资源正在与另一个上下文共享。如果您的意图是释放所有资源,请确保关闭所有上下文。
搜索 JNDI 中对应的方法是重载 DirContext.search(),接受一个搜索过滤器(RFC 2254)。查看 过滤器 示例。
修改 JNDI 中对应的方法是重载 DirContext.modifyAttributes(),接受一个 DirContext.ModificationItem 数组。查看 修改属性 部分的示例。
add JNDI 中对应的方法是DirContext.bind()DirContext.createSubcontext()。您可以使用其中任一方法来添加新的 LDAP 条目。使用bind(),您不仅可以为新条目指定一组属性,还可以指定要与属性一起添加的 Java 对象。请参阅使用 Attributes 添加、替换绑定部分以获取示例。
delete JNDI 中对应的方法是Context.unbind()Context.destroySubcontext()。您可以使用其中任一方法来删除 LDAP 条目。
modify DN/RDN JNDI 中对应的方法是Context.rename()。请参阅重命名对象部分以获取更多详细信息。
compare JNDI 中对应的操作是一个适当受限的DirContext.search()。请参阅 LDAP 比较部分以获取示例。
abandon 当您关闭一个上下文时,所有未完成的请求都会被放弃。同样,当您关闭一个NamingEnumeration时,相应的 LDAP“搜索”请求也会被放弃。
extended operation JNDI 中对应的方法是LdapContext.extendedOperation()。请参阅JNDI 教程以获取更多详细信息。

LDAP 错误代码与 JNDI 异常的映射方式

原文:docs.oracle.com/javase/tutorial/jndi/ldap/exceptions.html

LDAP 定义了一组状态代码,这些代码是 LDAP 服务器发送的 LDAP 响应中返回的(参见RFC 2251)。在 JNDI 中,错误条件被指示为NamingException的子类的已检查异常。请参阅 Naming Exceptions 部分以获取 JNDI 异常类的概述。

LDAP 服务提供程序将从 LDAP 服务器接收的 LDAP 状态代码转换为适当的NamingException子类。以下表格显示了 LDAP 状态代码与 JNDI 异常之间的映射。

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"或错误内容不包含引荐,则抛出PartialResultException。否则,使用内容构建引荐。
10 遇到引荐。 如果环境属性"java.naming.referral"设置为"ignore",则忽略。如果属性设置为"throw",则抛出ReferralException。如果属性设置为"follow",则 LDAP 提供程序处理引荐。如果已超过"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
53 不愿执行 OperationNotSupportedException
54 检测到循环。 NamingException
64 命名违规 InvalidNameException
65 对象类违规 SchemaViolationException
66 非叶子节点不允许。 ContextNotEmptyException
67 RDN 上不允许。 SchemaViolationException
68 条目已存在。 NameAlreadyBoundException
69 禁止对象类修改。 SchemaViolationException
71 影响多个 DSA。 NamingException
80 其他 NamingException

安全性

原文:docs.oracle.com/javase/tutorial/jndi/ldap/security.html

LDAP 服务提供了一个通用的目录服务。它可以用来存储各种信息。所有 LDAP 服务器都有一套系统来控制谁可以读取和更新目录中的信息。

要访问 LDAP 服务,LDAP 客户端首先必须向服务进行身份验证。也就是说,它必须告诉 LDAP 服务器谁将访问数据,以便服务器可以决定客户端被允许看到和执行什么操作。如果客户端成功向 LDAP 服务器进行身份验证,那么当服务器随后收到来自客户端的请求时,它将检查客户端是否被允许执行该请求。这个过程称为访问控制

LDAP 标准提出了 LDAP 客户端可以向 LDAP 服务器进行身份验证的方式(RFC 2251RFC 2829)。这些内容在 LDAP 身份验证部分和身份验证机制部分中进行了概述。本课程还包含了如何使用匿名、简单和 SASL 身份验证机制的描述。

不同的 LDAP 服务器实现以不同的方式支持访问控制。本课程不讨论这个问题。

LDAP 服务的另一个安全方面是支持使用安全通道与客户端通信,例如发送和接收包含密码和密钥等秘密信息的属性。LDAP 服务器为此目的使用 SSL。本课程还展示了如何与 LDAP 服务提供者一起使用 SSL。

LDAP 的认证方式

原文:docs.oracle.com/javase/tutorial/jndi/ldap/authentication.html

在 LDAP 中,认证信息是在"bind"操作中提供的。在 LDAP v2 中,客户端通过发送包含认证信息的"bind"操作与 LDAP 服务器建立连接。

在 LDAP v3 中,此操作具有相同的目的,但是是可选的。发送 LDAP 请求而不执行"bind"操作的客户端被视为匿名客户端(有关详细信息,请参见匿名部分)。在 LDAP v3 中,"bind"操作可以在连接期间的任何时候发送,可能会多次发送。客户端可以在连接的中间发送"bind"请求以更改其身份。如果请求成功,则所有使用旧身份的连接上的未完成请求都将被丢弃,并且连接将与新身份关联。

在"bind"操作中提供的认证信息取决于客户端选择的认证机制。有关认证机制的讨论,请参见认证机制。

使用 JNDI 进行 LDAP 认证

在 JNDI 中,认证信息是在环境属性中指定的。当您使用InitialDirContext类(或其超类或子类)创建初始上下文时,您提供一组环境属性,其中一些可能包含认证信息。您可以使用以下环境属性来指定认证信息。

  • Context.SECURITY_AUTHENTICATION ("java.naming.security.authentication").

    指定要使用的认证机制。对于 JDK 中的 LDAP 服务提供程序,这可以是以下字符串之一:"none","simple",sasl_mech,其中sasl_mech是一组以空格分隔的 SASL 机制名称。有关这些字符串的描述,请参见认证机制。

  • Context.SECURITY_PRINCIPAL ("java.naming.security.principal").

    指定进行认证的用户/程序的名称,取决于Context.SECURITY_AUTHENTICATION属性的值。有关详细信息和示例,请参见本课程的接下来几节。

  • Context.SECURITY_CREDENTIALS ("java.naming.security.credentials").

    指定进行认证的用户/程序的凭据,取决于Context.SECURITY_AUTHENTICATION属性的值。有关详细信息和示例,请参见本课程的接下来几节。

创建初始上下文时,底层 LDAP 服务提供程序从这些环境属性中提取认证信息,并使用 LDAP 的“绑定”操作将其传递给服务器。

以下示例展示了如何通过使用简单的明文密码,客户端向 LDAP 服务器进行身份验证。

// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");

// Authenticate as S. User and password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, 
        "cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

// ... do something useful with ctx

为上下文使用不同的认证信息

如果要为现有上下文使用不同的认证信息,则可以使用Context.addToEnvironment()Context.removeFromEnvironment()来更新包含认证信息的环境属性。随后对上下文的方法调用将使用新的认证信息与服务器通信。

以下示例展示了如何在创建上下文后将上下文的认证信息更改为"none"

// Authenticate as S. User and the password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, 
        "cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

// ... do something useful with ctx

// Change to using no authentication
ctx.addToEnvironment(Context.SECURITY_AUTHENTICATION, "none");

// ... do something useful with ctx

认证失败

认证可能因多种原因而失败。例如,如果提供了不正确的认证信息,比如不正确的密码或主体名称,那么会抛出AuthenticationException

这里是一个变体的示例。这次,不正确的密码导致认证失败。

// Authenticate as S. User and give an incorrect password
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, 
        "cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "notmysecret");

这将产生以下输出。

javax.naming.AuthenticationException: [LDAP: error code 49 - Invalid Credentials]
        ...

因为不同的服务器支持不同的认证机制,您可能请求服务器不支持的认证机制。在这种情况下,将抛出AuthenticationNotSupportedException

这里是一个变体的示例。这次,不支持的认证机制("custom")导致认证失败。

// Authenticate as S. User and the password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "custom");
env.put(Context.SECURITY_PRINCIPAL, 
        "cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");

这将产生以下输出。

javax.naming.AuthenticationNotSupportedException: custom
        ...

身份验证机制

原文:docs.oracle.com/javase/tutorial/jndi/ldap/auth_mechs.html

不同版本的 LDAP 支持不同类型的身份验证。LDAP v2 定义了三种身份验证类型:匿名、简单(明文密码)和 Kerberos v4。

LDAP v3 支持匿名、简单和 SASL 身份验证。SASL 是简单身份验证和安全层(RFC 2222)的缩写。它指定了一种挑战-响应协议,客户端和服务器之间交换数据以进行身份验证,并建立安全层以进行后续通信。通过使用 SASL,LDAP 可以支持 LDAP 客户端和服务器协商的任何类型的身份验证。

本课程包含了如何使用匿名、简单和 SASL 身份验证进行身份验证的描述。

指定身份验证机制

身份验证机制是通过使用Context.SECURITY_AUTHENTICATION环境属性来指定的。该属性可以具有以下值之一。

属性名称 属性值
sasl_mech 一个以空格分隔的 SASL 机制名称列表。使用列出的 SASL 机制之一(例如,"CRAM-MD5"表示使用RFC 2195中描述的 CRAM-MD5 SASL 机制)。
none 不使用身份验证(匿名)
simple 使用弱身份验证(明文密码)

默认机制

如果客户端没有指定任何身份验证环境属性,则默认身份验证机制为"none"。然后客户端将被视为匿名客户端。

如果客户端在不显式指定Context.SECURITY_AUTHENTICATION属性的情况下指定身份验证信息,则默认身份验证机制为"simple"

匿名

原文:docs.oracle.com/javase/tutorial/jndi/ldap/anonymous.html

正如刚才所述,如果没有设置任何身份验证环境属性,那么默认的身份验证机制是"none"。如果客户端将Context.SECURITY_AUTHENTICATION环境属性设置为"none",那么身份验证机制就是"none",所有其他身份验证环境属性都将被忽略。您只有在明确希望忽略可能已设置的任何其他身份验证属性时才需要这样做。无论哪种情况,客户端都将被视为匿名客户端。这意味着服务器不知道也不关心客户端是谁,并且将允许客户端访问(读取和更新)任何已配置为可被任何未经身份验证的客户端访问的数据。

因为 Naming and Directory Operations 课程中的所有目录示例都没有设置任何身份验证环境属性,所以它们都使用匿名身份验证。

这里是一个示例,明确将Context.SECURITY_AUTHENTICATION属性设置为"none"(尽管这样做并不是严格必要的,因为这是默认值)。

// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");

// Use anonymous authentication
env.put(Context.SECURITY_AUTHENTICATION, "none");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

// ... do something useful with ctx

简单

原文:docs.oracle.com/javase/tutorial/jndi/ldap/simple.html

简单身份验证包括向 LDAP 服务器发送客户端(用户)的完全限定 DN 和客户端的明文密码(参见RFC 2251RFC 2829)。这种机制存在安全问题,因为密码可以从网络中读取。为了避免以这种方式暴露密码,您可以在加密通道(如 SSL)中使用简单身份验证机制,前提是 LDAP 服务器支持。

LDAP v2 和 v3 都支持简单身份验证。

要使用简单身份验证机制,必须设置三个身份验证环境属性如下。

Context.SECURITY_AUTHENTICATION

设置为"simple"

Context.SECURITY_PRINCIPAL

设置为正在进行身份验证的实体的完全限定 DN(例如,"cn=S. User, ou=NewHires, o=JNDITutorial")。它的类型为java.lang.String

Context.SECURITY_CREDENTIALS

设置为主体的密码(例如,"mysecret")。它的类型为java.lang.Stringchar数组(char[])或byte数组(byte[])。如果密码是java.lang.Stringchar数组,则在传输到服务器时使用 UTF-8 进行编码以供 LDAP v3 使用,使用 ISO-Latin-1 供 LDAP v2 使用。如果密码是byte[],则按原样传输到服务器。

查看本节中早期的示例,演示如何使用简单身份验证。


注意: 如果您向Context.SECURITY_CREDENTIALS环境属性提供空字符串、空的byte/char数组或null,则身份验证机制将是"none"。这是因为 LDAP 要求简单身份验证的密码不能为空。如果未提供密码,则协议会自动将身份验证转换为"none"

SASL

原文:docs.oracle.com/javase/tutorial/jndi/ldap/sasl.html

LDAP v3 协议使用SASL来支持可插拔身份验证。这意味着 LDAP 客户端和服务器可以根据客户端和服务器所需的保护级别协商和使用可能是非标准和/或定制的身份验证机制。LDAP v2 协议不支持 SASL。

目前定义了几种 SASL 机制:

LDAP 服务器支持的 SASL 机制

在上述列表中,流行的 LDAP 服务器(如 Oracle、OpenLDAP 和 Microsoft)支持外部、摘要-MD5 和 Kerberos V5。RFC 2829提议将摘要-MD5 用作 LDAP v3 服务器的强制默认机制。

这是一个简单的程序,用于查找 LDAP 服务器支持的 SASL 机制列表。

// Create initial context
DirContext ctx = new InitialDirContext();

// Read supportedSASLMechanisms from root DSE
Attributes attrs = ctx.getAttributes(
    "ldap://localhost:389", new String[]{"supportedSASLMechanisms"});

运行此程序针对支持外部 SASL 机制的服务器产生的输出如下。

{supportedsaslmechanisms=supportedSASLMechanisms: 
                         EXTERNAL, GSSAPI, DIGEST-MD5}

指定身份验证机制

要使用特定的 SASL 机制,您需要在Context.SECURITY_AUTHENTICATION环境属性中指定其 IANA 注册的机制名称。您还可以指定 LDAP 提供程序尝试的机制列表。通过指定一个有序的以空格分隔的机制名称列表来实现。LDAP 提供程序将使用它找到实现的第一个机制。

这是一个示例,要求 LDAP 提供程序尝试获取 DIGEST-MD5 机制的实现,如果不可用,则使用 GSSAPI 的实现。

env.put(Context.SECURITY_AUTHENTICATION, "DIGEST-MD5 GSSAPI");

您可以从应用程序的用户获取此身份验证机制列表。或者您可以通过类似于之前显示的调用询问 LDAP 服务器获取它。LDAP 提供程序本身不会向服务器查询此信息。它只是尝试定位和使用指定机制的实现。

平台中的 LDAP 提供程序内置支持外部、摘要-MD5 和 GSSAPI(Kerberos v5)SASL 机制。您可以添加对其他机制的支持。

指定身份验证机制的输入

一些机制,如 External,不需要额外的输入,仅凭借机制名称就足以进行认证。External 示例展示了如何使用 External SASL 机制。

大多数其他机制需要一些额外的输入。根据机制的不同,输入的类型可能会有所不同。以下是一些机制常见的输入要求。

  • 认证 ID。执行认证的实体的身份。

  • 授权 ID。如果认证成功,应该进行访问控制检查的实体的身份。

  • 认证凭据。例如,密码或密钥。

认证和授权 ID 可能会有所不同,如果程序(如代理服务器)代表另一个实体进行认证。认证 ID 是通过使用Context.SECURITY_PRINCIPAL环境属性指定的。它的类型是java.lang.String

认证 ID 的密码/密钥是通过使用Context.SECURITY_CREDENTIALS环境属性指定的。它的类型是java.lang.Stringchar 数组(char[])或byte 数组(byte[])。如果密码是byte数组,则会使用 UTF-8 编码将其转换为char数组。

如果已设置"java.naming.security.sasl.authorizationId"属性,则其值将用作授权 ID。其值必须是java.lang.String类型。默认情况下,空字符串将用作授权 ID,这将指示服务器从客户端的认证凭据中派生授权 ID。

Digest-MD5 示例展示了如何使用Context.SECURITY_PRINCIPALContext.SECURITY_CREDENTIALS属性进行 Digest-MD5 认证。

如果某个机制需要除了已经描述的之外的输入,那么你需要为该机制定义一个回调对象供其使用,你可以在JNDI 教程中查看回调示例。本课程的下一部分将讨论如何使用 SASL Digest-MD5 认证机制。SASL 策略GSS API(Kerberos v5)CRAM-MD5 机制在 JNDI 教程中有介绍。

Digest-MD5

原文:docs.oracle.com/javase/tutorial/jndi/ldap/digest.html

Digest-MD5 认证是 LDAP v3 服务器所需的认证机制(RFC 2829)。因为 SASL 的使用是 LDAP v3 的一部分(RFC 2251),仅支持 LDAP v2 的服务器不支持 Digest-MD5。

Digest-MD5 机制在 RFC 2831 中有描述。它基于 HTTP 摘要认证(RFC 2251)。在 Digest-MD5 中,LDAP 服务器发送包含各种认证选项以及一个特殊令牌给 LDAP 客户端的数据。客户端通过发送加密响应来回应,该响应指示其选择的认证选项。响应被加密以证明客户端知道其密码。LDAP 服务器然后解密并验证客户端的响应。

要使用 Digest-MD5 认证机制,必须设置认证环境属性如下。

Context.SECURITY_AUTHENTICATION

设置为字符串"DIGEST-MD5"

Context.SECURITY_PRINCIPAL

设置为主体名称。这是一个特定于服务器的格式。一些服务器支持登录用户 id 格式,例如 UNIX 或 Windows 登录屏幕中定义的格式。其他服务器接受专有名称。还有一些使用 RFC 2829 中定义的授权 id 格式。在该 RFC 中,名称应为字符串"dn:",后跟被认证实体的完全限定 DN,或者字符串"u:",后跟用户 id。一些服务器接受多种格式。一些格式的示例是"cuser""dn: cn=C. User, ou=NewHires, o=JNDITutorial",和"u: cuser"。此属性的数据类型必须是java.lang.String

Context.SECURITY_CREDENTIALS

设置为主体的密码(例如,"mysecret")。它的类型可以是java.lang.Stringchar 数组(char[]),或byte 数组(byte[])。如果密码是java.lang.Stringchar[],则使用 UTF-8 进行编码以传输到服务器。如果密码是byte[],则原样传输到服务器。

以下示例展示了客户端如何使用 Digest-MD5 对 LDAP 服务器进行认证。

// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");

// Authenticate as C. User and password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "DIGEST-MD5");
env.put(Context.SECURITY_PRINCIPAL, 
        "dn:cn=C. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

// ... do something useful with ctx


注意: Oracle Directory Server, v5.2 支持具有明文密码的用户的 Digest-MD5 认证机制。您必须在创建用户之前设置密码加密模式。如果您已经创建了用户,请删除并重新创建。要使用管理控制台设置密码加密模式,请选择“配置”选项卡和“数据”节点。在“密码”窗格中,选择“无加密(CLEAR)”选项以进行“密码加密”。服务器接受简单的用户名(即具有一个条目的"uid"属性的值)和用户名称的“dn:”格式。有关详细信息,请参阅服务器的文档。


指定领域

领域 定义了选择身份验证实体(Context.SECURITY_PRINCIPAL 属性的值)的命名空间。服务器可能有多个领域。例如,大学的服务器可能配置为具有两个领域,一个用于学生用户,另一个用于教师用户。领域配置由目录管理员完成。一些目录具有默认的单个领域。例如,Oracle Directory Server, v5.2,使用机器的完全限定主机名作为默认领域。

在 Digest-MD5 认证中,您必须对特定领域进行身份验证。您可以使用以下身份验证环境属性来指定领域。如果您不指定领域,则服务器提供的任何一个领域都将被使用。

java.naming.security.sasl.realm

设置为主体的领域。这是一个部署特定和/或服务器特定的区分大小写的字符串。它标识应选择主体名称(Context.SECURITY_PRINCIPAL)的领域或域。如果此领域与服务器提供的领域之一不匹配,则身份验证失败。

以下示例 显示了如何设置环境属性以使用 Digest-MD5 进行身份验证并指定领域。要使此示例在您的环境中起作用,您必须更改源代码,以便领域值反映在您的目录服务器上的配置。

// Authenticate as C. User and password "mysecret" in realm "JNDITutorial"
env.put(Context.SECURITY_AUTHENTICATION, "DIGEST-MD5");
env.put(Context.SECURITY_PRINCIPAL, 
        "dn:cn=C. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");
env.put("java.naming.security.sasl.realm", "JNDITutorial");

如果您需要使用隐私保护和其他 SASL 属性,请参阅 JNDI 教程。

SSL 和自定义套接字

原文:docs.oracle.com/javase/tutorial/jndi/ldap/ssl.html

除了 SASL 身份验证外,大多数 LDAP 服务器允许通过 SSL 访问其服务。SSL 对于 LDAP v2 服务器特别有用,因为 v2 协议不支持 SASL 身份验证。

启用 SSL 的服务器通常以两种方式支持 SSL。在最基本的方式中,服务器支持 SSL 端口以及普通(未受保护)端口。服务器支持 SSL 的另一种方式是通过使用“启动 TLS 扩展”(RFC 2830)。此选项仅适用于 LDAP v3 服务器,并在该部分中有详细描述。

使用 SSL 套接字属性

默认情况下,JDK 中的 LDAP 服务提供程序在与 LDAP 服务器通信时使用普通套接字。要求使用 SSL 套接字,请将Context.SECURITY_PROTOCOL属性设置为"ssl"

以下示例中,LDAP 服务器在端口 636 上提供 SSL。要运行此程序,您必须在 LDAP 服务器上的端口 636 上启用 SSL。此过程通常由目录管理员执行。


服务器要求: LDAP 服务器必须设置为具有 X.509 SSL 服务器证书并启用 SSL。通常,您必须首先从证书颁发机构(CA)为服务器获取签名证书。然后,按照目录供应商的说明启用 SSL。不同的供应商有不同的工具来执行此操作。

对于Oracle Directory Server, v5.2,请在管理控制台中使用“管理证书”工具生成证书签名请求(CSR)。将 CSR 提交给 CA 以获取 X.509 SSL 服务器证书。使用管理控制台,将证书添加到服务器的证书列表中。如果 CA 的证书尚未在服务器的受信任 CA 列表中,则也需安装 CA 的证书。通过在管理控制台中使用“配置”选项卡启用 SSL。在左窗格中选择服务器。在右窗格中选择“加密”选项卡。选中“为此服务器启用 SSL”和“使用此密码族:RSA”的复选框,确保您添加的服务器证书在证书列表中。

客户端要求: 您需要确保客户端信任您将要使用的 LDAP 服务器。您必须在 JRE 的受信任证书数据库中安装服务器的证书(或其 CA 的证书)。这里是一个例子。

# cd *JAVA_HOME*/lib/security
# keytool -import -file server_cert.cer -keystore jssecacerts

有关如何使用安全工具的信息,请参阅安全指南。有关 JSSE 的信息,请参阅Java 安全套接字扩展(JSSE)参考指南


// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:636/o=JNDITutorial");

// Specify SSL
env.put(Context.SECURITY_PROTOCOL, "ssl");

// Authenticate as S. User and password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, 
        "cn=S. User, ou=NewHires,o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

// ... do something useful with ctx


注意: 如果您使用 SSL 连接到未使用 SSL 的端口的服务器,则您的程序将挂起。同样,如果您使用普通套接字连接到服务器的 SSL 套接字,则您的应用程序将挂起。这是 SSL 协议的特性。


使用 LDAPS URL

通过使用Context.SECURITY_PROTOCOL属性请求使用 SSL,您还可以通过使用 LDAPS URL 请求使用 SSL。LDAPS URL 类似于 LDAP URL,只是 URL 方案为 "ldaps" 而不是 "ldap"。它指定与 LDAP 服务器通信时使用 SSL。

以下示例中,LDAP 服务器在端口 636 上提供 SSL。要运行此程序,您必须在 LDAP 服务器上的端口 636 上启用 SSL。

// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");

// Specify LDAPS URL
env.put(Context.PROVIDER_URL, "ldaps://localhost:636/o=JNDITutorial");

// Authenticate as S. User and password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, 
        "cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

// ... do something useful with ctx

LDAPS URL 可以在任何接受 LDAP URL 的地方使用。查看JNDI 教程了解有关 LDAP 和 LDAPS URL 的详细信息。

客户端身份验证:使用外部 SASL 机制的 SSL

SSL 在比 LDAP 更低的层次提供身份验证和其他安全服务。如果在 SSL 上已经完成了身份验证,则 LDAP 层可以通过使用External SASL 机制从 SSL 中使用该身份验证信息。

以下示例类似于先前的 SSL 示例,只是它不使用简单身份验证,而是使用外部 SASL 身份验证。通过使用 External,您无需提供任何主体或密码信息,因为它们会从 SSL 中获取。


服务器要求: 此示例要求 LDAP 服务器允许基于证书的客户端身份验证。此外,LDAP 服务器必须信任(CA 的)接收到的客户端证书,并且必须能够将客户端证书中的所有者可分辨名称映射到其了解的主体。请按照您的目录供应商的说明执行这些任务。

客户端要求: 此示例要求客户端具有 X.509 SSL 客户端证书。此外,证书必须存储为密钥库文件中的第一个密钥条目。如果此条目受密码保护,则必须与密钥库具有相同的密码。有关 JSSE 密钥库的更多信息,请参阅Java 安全套接字扩展(JSSE)参考指南


// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>(11);
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:636/o=JNDITutorial");

// Principal and credentials will be obtained from the connection
env.put(Context.SECURITY_AUTHENTICATION, "EXTERNAL");

// Specify SSL
env.put(Context.SECURITY_PROTOCOL, "ssl");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

...

要运行此程序以便使用客户端证书进行身份验证,您必须提供(作为系统属性)包含客户端证书的密钥库的位置和密码。以下是运行程序的示例。

java -Djavax.net.ssl.keyStore=MyKeystoreFile \
    -Djavax.net.ssl.keyStorePassword=mysecret \
    External

如果您没有提供密钥库,程序将使用匿名身份验证运行,因为在 SSL 上不存在客户端凭据。

此示例展示了实现基于证书的客户端身份验证的最基本方法。 通过编写和使用访问客户端证书的自定义套接字工厂,可以以更灵活的方式实现更高级的方法,也许通过使用 LDAP 目录。 下一节将展示如何在 JNDI 应用程序中使用自定义套接字工厂。

使用自定义套接字

当使用 SSL 时,默认情况下,LDAP 提供程序将使用套接字工厂,javax.net.ssl.SSLSocketFactory,用于创建与服务器通信的 SSL 套接字,使用默认的 JSSE 配置。 JSSE 可以以多种方式进行自定义,详细信息请参阅Java 安全套接字扩展(JSSE)参考指南。 但是,有时这些自定义不足以满足需求,您需要对 LDAP 服务提供程序使用的 SSL 套接字或一般套接字进行更多控制。 例如,您可能需要能够绕过防火墙的套接字,或者 JSSE 套接字使用非默认的缓存/检索策略来管理其信任和密钥存储。 要设置 LDAP 服务提供程序使用的套接字工厂实现,请将"java.naming.ldap.factory.socket"属性设置为套接字工厂的完全限定类名。 此类必须实现javax.net.SocketFactory抽象类,并提供实现getDefault()方法的实例,该方法返回套接字工厂的实例。 请参阅Java 安全套接字扩展(JSSE)参考指南

这里是一个产生普通套接字的自定义套接字工厂的示例。

public class CustomSocketFactory extends SocketFactory {
    public static SocketFactory getDefault() {

        System.out.println("[acquiring the default socket factory]");
        return new CustomSocketFactory();
    }
        ...
}

请注意,此示例每次创建新的 LDAP 连接时都会创建CustomSocketFactory的新实例。 这对某些应用程序和套接字工厂可能是合适的。 如果要重用相同的套接字工厂,getDefault()应返回一个单例。

要在 JNDI 程序中使用此自定义套接字工厂,请设置"java.naming.ldap.factory.socket"属性,如以下示例所示。

// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");

// Specify the socket factory
env.put("java.naming.ldap.factory.socket", "CustomSocketFactory");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

// ... do something useful with ctx

"java.naming.ldap.factory.socket" 属性可用于在每个上下文基础上设置套接字工厂。另一种控制 LDAP 服务提供程序使用的套接字的方法是通过使用java.net.Socket.setSocketImplFactory()为整个程序中使用的所有套接字设置套接字工厂。使用这种方法不够灵活,因为它影响所有套接字连接,而不仅仅是 LDAP 连接,因此应谨慎使用。

更多 LDAP 操作

原文:docs.oracle.com/javase/tutorial/jndi/ldap/rename.html

LDAP 课程的其余部分介绍了 JNDI 提供的执行某些有趣的 LDAP 操作的能力。

重命名对象

您可以使用Context.rename()来重命名目录中的对象。在LDAP v2中,这对应于“修改 RDN”操作,该操作将在同一上下文中重命名条目(即重命名同级)。在LDAP v3中,这对应于“修改 DN”操作,类似于“修改 RDN”,只是旧条目和新条目不需要在同一上下文中。您可以使用Context.rename()来重命名叶条目或内部节点。在 Naming and Directory Operations 课程中展示的示例重命名了一个叶条目。以下代码将内部节点从"ou=NewHires"重命名为"ou=OldHires"

ctx.rename("ou=NewHires", "ou=OldHires");


注意: Oracle Directory Server v5.2 不支持重命名内部节点。如果您运行此示例,则会收到ContextNotEmptyException


将条目重命名为 DIT 的不同部分

使用 LDAP v3,您可以将条目重命名为 DIT 的不同部分。要通过Context.rename()实现此目的,必须使用一个既是新条目又是旧条目的共同祖先的上下文。例如,要将"cn=C. User, ou=NewHires, o=JNDITutorial"重命名为"cn=C. User, ou=People, o=JNDITutorial",您必须使用由"o=JNDITutorial"命名的上下文。以下是演示此操作的示例。如果您尝试在 LDAP v2 服务器上运行此示例,则会收到InvalidNameException,因为版本 2 不支持此功能。

ctx.rename("cn=C. User, ou=NewHires", "cn=C. User, ou=People");


注意: Oracle Directory Server v5.2 不支持使用不同父节点进行重命名。如果您使用该服务器运行此示例,则会收到OperationNotSupportedException(表示“协议错误”)。


保留旧名称属性

在 LDAP 中,当您重命名条目时,您可以选择将条目的旧 RDN 保留为更新后条目的属性。例如,如果您将条目"cn=C. User"重命名为"cn=Claude User",您可以指定是否要保留旧 RDN"cn=C. User"作为属性。

要指定在使用Context.rename()时是否要保留旧名称属性,请使用"java.naming.ldap.deleteRDN"环境属性。如果此属性的值为"true"(默认值),则旧的 RDN 将被移除。如果其值为"false",则旧的 RDN 将保留为更新条目的属性。完整示例在这里

// Set the property to keep RDN
env.put("java.naming.ldap.deleteRDN", "false");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

// Perform the rename
ctx.rename("cn=C. User, ou=NewHires", "cn=Claude User,ou=NewHires");

LDAP 比较

原文:docs.oracle.com/javase/tutorial/jndi/ldap/compare.html

LDAP 的“比较”操作允许客户端询问服务器是否具有指定条目的属性/值对。这使得服务器可以保留某些属性/值对的机密性(即,不对一般“搜索”访问公开),同时仍允许客户端有限使用它们。例如,一些服务器可能会将此功能用于密码,尽管客户端在“比较”操作本身中传递明文密码是不安全的。

要在 JNDI 中实现这一点,请对以下方法使用适当限制的参数:

  1. 过滤器必须是“(name=value)”的形式。不能使用通配符。

  2. 搜索范围必须是SearchControls.OBJECT_SCOPE

  3. 你必须要求不返回任何属性。如果不符合这些条件,那么这些方法将使用 LDAP 的“搜索”操作而不是 LDAP 的“比较”操作。

这里有一个示例,会导致使用 LDAP 的“比较”操作。

// Value of the attribute
byte[] key = {(byte)0x61, (byte)0x62, (byte)0x63, (byte)0x64, 
              (byte)0x65, (byte)0x66, (byte)0x67};

// Set up the search controls
SearchControls ctls = new SearchControls();
ctls.setReturningAttributes(new String[0]);       // Return no attrs
ctls.setSearchScope(SearchControls.OBJECT_SCOPE); // Search object only

// Invoke search method that will use the LDAP "compare" operation
NamingEnumeration answer = ctx.search("cn=S. User, ou=NewHires", 
                                      "(mySpecialKey={0})", 
                                       new Object[]{key}, ctls);

如果比较成功,结果枚举将包含一个名称为空且不包含任何属性的单个项目。

搜索结果

原文:docs.oracle.com/javase/tutorial/jndi/ldap/result.html

当您在DirContext接口中使用搜索方法时,您将获得一个NamingEnumerationNamingEnumeration中的每个项目都是一个SearchResult,其中包含以下信息:

  • 名称

  • 对象

  • 类名

  • 属性

名称

每个SearchResult包含满足搜索过滤器的 LDAP 条目的名称。您可以通过使用getName()来获取条目的名称。该方法返回 LDAP 条目相对于目标上下文复合名称。目标上下文是name参数解析到的上下文。在 LDAP 术语中,目标上下文是搜索的基本对象。以下是一个示例。

NamingEnumeration answer = ctx.search("ou=NewHires", 
    "(&(mySpecialKey={0}) (cn=*{1}))",  // Filter expression
    new Object[]{key, name},                // Filter arguments
    null);                                  // Default search controls

在本示例中,目标上下文由"ou=NewHires"命名。answer中的SearchResult中的名称相对于"ou=NewHires"。例如,如果getName()返回"cn=J. Duke",那么相对于ctx的名称将是"cn=J. Duke, ou=NewHires"

如果您使用SearchControls.SUBTREE_SCOPESearchControls.OBJECT_SCOPE执行搜索,并且目标上下文本身满足搜索过滤器,则返回的名称将是""(空名称),因为这是相对于目标上下文的名称。

这并不是全部。如果搜索涉及引荐(请参阅JNDI 教程)或解引用别名(请参阅JNDI 教程),那么相应的SearchResult将具有不相对于目标上下文的名称。相反,它们将是直接引用条目的 URL。要确定getName()返回的名称是相对还是绝对,请使用isRelative()。如果此方法返回true,则名称相对于目标上下文;如果返回false,则名称是一个 URL。

如果名称是一个 URL,您需要使用该 URL,则可以将其传递给了解 URL 的初始上下文(请参阅JNDI 教程)。

如果您需要获取条目的完整 DN,可以使用NameClassPair.getNameInNamespace()

对象

如果搜索是请求返回条目对象的(使用SearchControls.setReturningObjFlag()调用为true),那么SearchResult将包含表示条目的对象。要检索此对象,您需要调用getObject()。如果之前将java.io.SerializableReferenceableReference对象绑定到 LDAP 名称,则使用来自条目的属性来重建该对象(请参阅JNDI 教程中的示例)。否则,使用来自条目的属性创建代表 LDAP 条目的DirContext实例。在任一情况下,LDAP 提供程序会在对象上调用DirectoryManager.getObjectInstance()并返回结果。

类名

如果搜索是请求返回条目对象,则类名是从返回的对象派生的。如果搜索请求包括检索 LDAP 条目的"javaClassName"属性,则类名是该属性的值。否则,类名为"javax.naming.directory.DirContext"。类名是从getClassName()获取的。

属性

当执行搜索时,您可以通过向其中一个search()方法提供参数或使用SearchControls.setReturningAttributes()设置搜索控件来选择返回属性。如果没有明确指定属性,则将返回所有 LDAP 条目的属性。要指定不返回任何属性,必须传递一个空数组(new String[0])。

要检索 LDAP 条目的属性,您需要在SearchResult上调用getAttributes()

响应控件

查看JNDI 教程中的“控件和扩展”课程,了解如何检索搜索结果的响应控件的详细信息。

LDAP 非请求通知

原文:docs.oracle.com/javase/tutorial/jndi/ldap/unsol.html

LDAP v3(RFC 2251)定义了非请求通知,即 LDAP 服务器向客户端发送的无需客户端引发的消息。在 JNDI 中,非请求通知由UnsolicitedNotification接口表示。

由于服务器异步发送非请求通知,您可以使用与接收有关命名空间更改和对象内容更改通知相同的事件模型。通过在EventContextEventDirContext上注册UnsolicitedNotificationListener来注册接收非请求通知的兴趣。

这里是一个示例展示了UnsolicitedNotificationListener的实现。

public class UnsolListener implements UnsolicitedNotificationListener {
    public void notificationReceived(UnsolicitedNotificationEvent evt) {
        System.out.println("received: " + evt);
    }

    public void namingExceptionThrown(NamingExceptionEvent evt) {
        System.out.println(">>> UnsolListener got an exception");
            evt.getException().printStackTrace();
    }
}

以下是一个示例,展示了如何向事件源注册UnsolicitedNotificationListener的实现。请注意,只有EventContext.addNamingListener()中的监听器参数与非请求通知相关。名称和范围参数与非请求通知无关。

// Get the event context for registering the listener
EventContext ctx = (EventContext)
    (new InitialContext(env).lookup("ou=People"));

// Create the listener
NamingListener listener = new UnsolListener();

// Register the listener with the context (all targets equivalent)
ctx.addNamingListener("", EventContext.ONELEVEL_SCOPE, listener);

运行此程序时,您需要将其指向一个可以生成非请求通知并促使服务器发出通知的 LDAP 服务器。否则,程序将在一分钟后悄无声息地退出。

实现UnsolicitedNotificationListener的监听器也可以实现其他NamingListener接口,比如NamespaceChangeListenerObjectChangeListener

连接管理

原文:docs.oracle.com/javase/tutorial/jndi/ldap/connect.html

JNDI 为访问命名和目录服务提供了高级接口。JNDI Context实例与底层网络连接之间的映射可能不是一对一的。只要保留接口语义,服务提供者就可以自由共享和重用连接。应用程序开发人员通常不需要了解Context实例如何创建和使用连接的细节。当开发人员需要调整程序时,这些细节是有用的。

本课程描述了 LDAP 服务提供者如何使用连接。它描述了何时创建连接以及如何指定特殊的连接参数,例如多个服务器和连接超时。本课程还展示了如何在支持的网络环境中动态发现和使用 LDAP 服务器。

创建的连接最终必须关闭。本课程包含一个描述客户端和服务器如何进行连接关闭的部分。

最后,本课程向您展示如何使用连接池使使用许多短暂连接的应用程序更有效率。


注意: 本课程中提供的信息仅适用于 JDK 中的 LDAP 服务提供者。其他供应商的 LDAP 服务提供者可能不使用相同的连接管理策略。


创建

原文:docs.oracle.com/javase/tutorial/jndi/ldap/create.html

创建连接的几种方式。最常见的方式是从创建初始上下文开始。当您使用 LDAP 服务提供程序创建InitialContextInitialDirContextInitialLdapContext时,会立即与Context.PROVIDER_URL属性中命名的目标 LDAP 服务器建立连接。每次创建初始上下文时,都会创建一个新的 LDAP 连接。有关如何更改此行为的信息,请参见 Pooling 部分。

如果属性值包含多个 URL,则依次尝试每个 URL,直到成功创建连接为止。然后将属性值更新为成功的 URL。有关如何使用 URL 列表创建初始上下文的示例,请参见JNDI 教程

还有三种直接创建连接的方式。

  1. 通过将 URL 作为初始上下文的名称参数传递。当将 LDAP 或 LDAPS URL 作为名称参数传递给初始上下文时,URL 中的信息将用于创建到 LDAP 服务器的新连接,而不管初始上下文实例本身是否连接到 LDAP 服务器。实际上,初始上下文可能未连接到任何服务器。有关如何将 URL 用作名称的更多信息,请参见JNDI 教程

  2. 另一种创建连接的方式是使用Reference。当传递包含 LDAP 或 LDAPS URL 的ReferenceNamingManager.getObjectInstance()DirectoryManager.getObjectInstance()时,将使用 URL 中指定的信息创建一个新连接。

  3. 最后,当手动或自动跟随引荐时,引荐中的信息将用于创建新连接。有关引荐的信息,请参见JNDI 教程

共享连接

从一个Context实例派生的Context实例和NamingEnumerations将共享相同的连接,直到对其中一个Context实例进行更改使共享不再可能。 例如,如果您从初始上下文调用Context.lookup()Context.listBindings()DirContext.search()并获得其他Context实例,则所有这些Context实例将共享相同的连接。

这里是一个示例

// Create initial context
DirContext ctx = new InitialDirContext(env);

// Get a copy of the same context
Context ctx2 = (Context)ctx.lookup("");

// Get a child context
Context ctx3 = (Context) ctx.lookup("ou=NewHires");

在这个示例中,ctxctx2ctx3将共享相同的连接。

共享是无论Context实例如何产生都会进行的。 例如,通过遵循引荐获得的Context实例将与引荐共享相同的连接。

当您更改与连接相关的Context实例的环境属性,例如用户的主体名称或凭据时,您进行这些更改的Context实例将获得自己的连接(如果连接是共享的)。 未来从此Context实例派生的Context实例将共享这个新连接。 先前共享旧连接的Context实例不受影响(即它们继续使用旧连接)。

这里是一个使用两个连接的示例

// Create initial context (first connection)
DirContext ctx = new InitialDirContext(env);

// Get a copy of the same context
DirContext ctx2 = (DirContext)ctx.lookup("");

// Change authentication properties in ctx2
ctx2.addToEnvironment(Context.SECURITY_PRINCIPAL, 
    "cn=C. User, ou=NewHires, o=JNDITutorial");
ctx2.addToEnvironment(Context.SECURITY_CREDENTIALS, "mysecret");

// Method on ctx2 will use new connection
System.out.println(ctx2.getAttributes("ou=NewHires"));

ctx2最初与ctx共享相同的连接。 但是当其主体和密码属性更改时,它将无法再使用ctx的连接。 LDAP 提供程序将自动为ctx2创建一个新连接。

同样,如果您使用LdapContext.reconnect()来更改Context实例的连接控制,如果连接正在共享,则Context实例将获得自己的连接。

如果Context实例的连接未被共享(即没有Context派生自它),则对其环境或连接控制的更改不会导致创建新连接。 相反,与连接相关的任何更改将应用于现有连接。

创建超时

并非所有连接创建都成功。如果 LDAP 提供程序在一定的超时期限内无法建立连接,则会中止连接尝试。默认情况下,此超时期限是网络(TCP)超时值,大约几分钟。要更改超时期限,您可以使用"com.sun.jndi.ldap.connect.timeout"环境属性。此属性的值是表示连接超时的整数的字符串表示。

这里是一个示例

// Set up environment for creating initial context
Hashtable env = new Hashtable(11);
env.put(Context.INITIAL_CONTEXT_FACTORY, 
    "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");

// Specify timeout to be 5 seconds
env.put("com.sun.jndi.ldap.connect.timeout", "5000");

// Create initial context
DirContext ctx = new InitialDirContext(env);

// do something useful with ctx

在这个示例中,如果在 5 秒内无法创建连接,将抛出异常。

如果Context.PROVIDER_URL属性包含多个 URL,则提供程序将对每个 URL 使用超时。例如,如果有 3 个 URL 并且超时已指定为 5 秒,则提供程序将总共等待最多 15 秒。

请查看连接池部分,了解此属性如何影响连接池。

posted @ 2024-04-12 15:15  绝不原创的飞龙  阅读(11)  评论(0编辑  收藏  举报