Sun JNDI教程翻译 第二部分 Directory Operations

来源:互联网 发布:淘宝号刷了单会危险嘛 编辑:程序博客网 时间:2024/04/30 07:44
本文是JNDI Tutorial系列文章的第二部分:The Basics,介绍了JNDI的一些基础知识,诸如Naming操作和Directory操作。介绍了如何通过编程的方式访问命名和目录服务,如何使用JNDI和目录进行交互。从准备环境到查找对象以及在目录中进行搜索等操作。
本部分主要讲述了JNDI对目录的操作。
可以通过JNDi来实现以下目录操作:
  • Reading an object's attributes 读取对象属性
  • Modifying an object's attributes 修改对象属性
  • Searching the directory 检索
  • Performing hybrid naming and directory operations 命名和目录的联合操作
接下来将介绍这些操作的具体内容,在使用它们一下,首先应该了解一下有关属性名称的知识。
配置
本部分教程的内容使用LDAP服务provider。本部分的例子都是假设已经根据前面的预备知识创建了样例名称空间。如果使用其他的服务provider或者使用其他的LDAP名称空间,那么这部分的例子的执行结果可能会和实际有所不同或者不能执行。
通过以下环境属性来初始化这部分例子中的initial context
//Set up the environment for creating the initial context
env.put(Context.INITIAL_CONTEXT_FACTORY,
                                                                           "com.sun.jndi.ldap.LdapCtxFactory");
                   env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");
                   env.put(Context.SECURITY_AUTHENTICATION, "simple");
                   env.put(Context.SECURITY_PRINCIPAL, "uid=admin,ou=system");
env.put(Context.SECURITY_CREDENTIALS, "secret");
DirContext ctx = new InitialDirContext(env);
(此处教程中使用匿名用户进行的绑定,而ApacheDS默认情况下不允许匿名绑定,所以笔者使用管理员进行绑定,实际上,根据不同的服务器,本教程中所有的例子的返回结果可能和原JNDI教程有些不一致)
属性名(Attribute Names)
一个属性包含属性标识符合一系列属性值,属性标识符也叫做属性名,是一个字符串用来标识一个属性。属性值是属性的内容,并且属性的值得类型不局限于字符串。当在执行检索、获取或者修改的时候需要指定特定的属性,这个时候需要使用属性名。返回属性的操作也会返回名称,例如读操作或者检索操作。
当使用属性名时,需要注意特定目录服务器的特性,所以有时可能会返回不同的结果。这些特性在后面将会介绍。
属性类型
在诸如LDAP的目录中,属性名标识了属性的类型,并且叫做属性类型名。例如,属性名cn叫做属性类型名。属性的类型定义指定了属性的值(是否可以有多个值),等价性,以及排序规则(当根据属性值进行执行比较和排序操作时)
属性子类
一些目录实现支持属性子类,这些服务器允许属性类型根据其他的属性类型进行定义。例如,name属性可能是所有名称相关属性的超类,例如commonName可能是name的一个子类。对于支持属性子类的目录实现,当请求name属性的时候,可能返回commonName属性。
当访问支持属性子类的目录时,要注意服务器可能返回与要求的属性名不同的属性,为了最小程度的减少这种情况,使用最底层的子类。
同义的属性名
一些目录实现支持属性名同义的概念,例如cn属性是commonName的一个同义属性。因此请求cn属性时,可能返回commonName属性。
当访问支持这个特性的目录时,必须注意服务器可能返回和请求属性名不同的属性。为了防止这种情况的发生,使用规范的属性名来代替和它同义的属性名。规范属性名用于属性定义,而同义的属性名是参考了规范属性名的定义。
语言参数选择
LDAP v3的一个扩展允许在属性名中指定语言代码。这和属性子类相似,一个属性名可以代表多个不同的属性。一个例子就是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 = attrs.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()));
}
执行的结果如下:
attribute: telephonenumber
value: +1 408 555 5252
attribute: mail
value: Ted.Geisel@JNDITutorial.com
attribute: facsimiletelephonenumber
value: +1 408 555 2329
Eclipse中查看的视图如下:
(从图中可以看出,该对象的属性不仅仅上面列出的这些,但是ApacheDS在返回结果的时候只返回了一部分。笔者认为ApacheDS的理由是:既然你提供了用户的DN来进行读取属性,那么诸如cnsncouo等属性就应该是已知的了,没有必要再返回一次。)
 
返回指定的属性:
为了运行所有属性的一个子集,可以指定一个属性名数组来指定需要获取的属性:
// 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);
返回结果如下:
attribute: telephonenumber
value: +1 408 555 5252
attribute: mail
value: Ted.Geisel@JNDITutorial.com
attribute: sn
value: Geisel
由于这个对象没有golfhanicap属性,所以只返回三个属性。
修改属性
DirContext接口包含了修改对象的属性及属性值得方法。
使用修改列表
修改一个对象的属性的方法之一就是提供一个修改请求列表(ModificationItem)。每个ModificationItem由一定数量的指定修改类型的常量以及修改的属性Attribute组成。下面是三种类型的修改:
¨         ADD_ATTRIBUTE
¨         REPLACE_ATTRIBUTE
¨         REMOVE_ATTRIBUTE
ModificationItem(int mod_op, Attribute attr)
          Creates a new instance of ModificationItem.
修改的顺序和它们出现在列表中的顺序一致,要么所有的修改都完成,要么一个都不完成,即一个修改操作是一个事务。
下面的代码创建了一个修改列表,将属性mail的值修改为geisel@wizards.com,添加了一个telephonenumber属性值,并删除了jpegphoto属性。
                   // Create the initial context
                   DirContext ctx = new InitialDirContext(env);
                   String name = "cn=Ted Geisel, ou=People";
                   // Save original attributes
              Attributes orig = ctx.getAttributes(name, new String[] { "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 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"));
              // Perform the requested modifications on the named object
当创建完修改列表后,执行modifyAttributes()方法,该方法接受两个参数,一个为String类型的对象名,另一个为ModificationItem[]类型的修改列表。执行完该方法后,修改被应用。
ctx.modifyAttributes(name, mods);
修改完成后打印新旧属性的比较:
**** new attributes *****
attribute: telephonenumber
value: +1 408 555 5252
value: +1 555 555 5555
attribute: mail
value: geisel@wizards.com
**** reverted to original attributes *****
attribute: telephonenumber
value: +1 408 555 5252
attribute: mail
value: Ted.Geisel@JNDITutorial.com
attribute: jpegphoto
value: [B@119cca4
windows active directory中,telephonenumber属性是一个单值属性,但是RFC 2256中的定义是多值的。所以当LDAP服务器为win ad时,需要将添加操作修改为替换操作。
使用属性
也可以通过制定修改的类型以及要修改的属性来执行修改操作。
例如,下面的方法将名称为name的对象的属性(orig中定义的同名的属性)替换为orig中的属性,该对象的orig以外的属性不变。
ctx.modifyAttributes(name,DirContext.REPLACE_ATTRIBUTE,orig);
检索操作
最常见的目录服务就是黄页,或者说检索服务。
[yellow page: 黄页(电话)查号簿(美国按行业、职业分类的部分常用黄色纸)]
可以组合一个检索语句,语句中包含实体的属性需要符合的条件,然后提交给目录服务器,目录服务器返回符合条件的检索列表。例如,可以查找所有surname”Sch”开头的人的实体,或者保龄球平均成绩高于200的实体。
DirContext接口提供了很多检索的方法,功能和复杂性各不相同。目录检索的相关内容包括以下部分:
¨         基本检索
¨         检索过滤器
¨         检索控制
基本检索
这是目录检索的最简单的形式,只需要指定检索结果必须包含的属性以及检索的目的上下文。下面的代码创建了一个属性集matchAttrs,其中包含两个属性,telephonenumbermail,检索指定了实体必须具有的surname(sn)属性和mail属性,并且surname属性的值为”Geisel”,而mail的属性值任意。然后调用DirContext.search()方法在ou=people的上下文中检索符合matchAttrs的实体。
Attributes matchAttrs = new BasicAttributes(true); // ignore 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);
可以使用下面的语句打印结果:
while (answer.hasMore()) {
SearchResult sr = (SearchResult) answer.next();
System.out.println(">>>" + sr.getName());
GetattrsAll.printAttrs(sr.getAttributes());
}
返回结果为:
>>>cn=Ted Geisel
attribute: telephonenumber
value: +1 408 555 5252
attribute: mail
value: Ted.Geisel@JNDITutorial.com
attribute: facsimiletelephonenumber
value: +1 408 555 2329
attribute: objectClass
value: person
value: inetOrgPerson
value: organizationalPerson
value: top
attribute: jpegphoto
value: [B@179c285
attribute: sn
value: Geisel
attribute: cn
value: Ted Geisel
可以选择返回指定的属性,代码如下:
// Specify the ids of the attributes to return
String[] attrIDs = { "sn", "telephonenumber", "golfhandicap","mail" };
answer = ctx.search("ou=People", matchAttrs, attrIDs);
此时返回的结果为:
>>>cn=Ted Geisel
attribute: telephonenumber
value: +1 408 555 5252
attribute: mail
value: Ted.Geisel@JNDITutorial.com
attribute: sn
value: Geisel
 
检索过滤器
和基本检索不同的是,可以使用一个检索过滤器进行检索操作。检索过滤器的检索语句是通过逻辑表达式的形式表示的。检索过滤器的语法在RFC 2254中进行了定义,DirContext.search()方法接受符合这个语法的过滤器。
下面的检索过滤器指定了合法的实体必须具有sn属性,并且其值为Geisel以及一个值为任意的mail属性:(&(sn=Geisel)(mail=*))
下面的代码创建了一个过滤器和一个默认的检索控制,SearchControls,并使用它们来进行检索操作。这个检索和基本检索中的功能是一样的。
// Create default search controls
SearchControls ctls = new SearchControls();
// Specify the search filter to match
// Ask for objects with attribute sn == Geisel and which have
// the "mail" attribute.
String filter = "(&(sn=Geisel)(mail=*))";
// Search for objects using filter
NamingEnumeration answer = ctx.search("ou=People", filter, ctls);
// Print the answer
while (answer.hasMore()) {
         SearchResult sr = (SearchResult) answer.next();
         System.out.println(">>>" + sr.getName());
         GetattrsAll.printAttrs(sr.getAttributes());
}
检索返回的结果和基本检索的结果是一样的。
检索过滤器语法一览
检索过滤器语法基本上就是逻辑运算符作为前缀的逻辑表达式,下图是一些逻辑运算符号及其说明:
过滤器中的每一项都由属性标识符和属性值或者上述的运算符号组成。例如,”sn=Geisel”表示必须具有sn属性,并且sn属性的值必须为Geisel”mail=*”则表示mail属性只要存在就可以了。
每一项都必须使用括号括起来使用。逻辑运算符可以组合使用,通过使用括号进行分割。例如
(| (& (sn=Geisel) (mail=*)) (sn=L*))
关于更完整的语法描述,参见RFC 2254
返回指定属性
可以通过设置SearchControlssetReturningAttributes()方法来设置需要返回的特定属性:
// Specify the ids of the attributes to return
String[] attrIDs = { "sn", "telephonenumber", "golfhandicap","mail" };
ctls.setReturningAttributes(attrIDs);
其返回的结果和基本认证返回的一致。
 
检索控制
在上面的检索过滤器中如何使用SearchControls参数来选择需要返回的属性。还可以是SearchControls参数来控制检索的其他方面,下面是一些可用的控制:
¨         返回的属性
¨         检索的范围
¨         返回的最大结果数
¨         等待最大时间(毫秒),即超时时间
¨         是否返回和实体相关联的Java对象
¨         在检索时是否废除JNDI链接
其中后两部分在之后的教程中介绍,本部分教程介绍前四种控制:
检索范围
通过使用SearchControls.setSearchScope(int scope)方法来设置检索范围,检索范围由三种,分别如下:
static int OBJECT_SCOPE
                           
检索指定的对象
static int ONELEVEL_SCOPE
                           
检索指定对象及其直接子实体
static int SUBTREE_SCOPE
                           
在以指定对象为根的子树中进行检索
下图摘自ApacheDS网站对于检索范围的说明,比较易懂:
Note:Base scope is useful for reading attribute values of a particular entry. One example for using One_Level scope is expanding the tree in graphical LDAP clients. For extensive investigations within your data, Subtree is the most powerful option.
这里的示例就省略了,通过上面的图片应该很容易理解检索范围的概念。
 
数量限制
有的时候,需要限制查询返回结果的数量,这个时候需要使用检索控制的数量限制。默认情况下,检索操作返回所有符合条件的检索结果,可以使用SearchControls.setCountLimit()方法设置检索结果的数量。下面的代码将检索结果数量设置为1
// Set search controls to limit count to 'expected'
SearchControls ctls = new SearchControls();
ctls.setCountLimit(expected);
如果程序要获取大于最大数量的结果,那么将抛出一个SizeLimitExceededException。所以当设置数量限制的时候,应该将这个异常和NamingExceptions异常相区分,或者根据数量限制请求结果(不要超过最大数量)
指定检索结果的数量是一种控制程序消耗资源的方法,比如内存或者网络带宽。其他控制资源消耗的办法有实用查询过滤器、在适当的上下文中查询以及使用适当的检索范围。
(笔者运行这部分Sun的教程,和实际不一样,原因有待调查,情况如下:即使我设置了数量限制,但是还是可以返回两个结果。)
但是当笔者将检索过滤器修改为objectClass=*的时候就可以使用了。
程序运行结果为:
>>>cn=Ted Geisel
number of answers: 1
源代码如下:
package com.sun.jndi.examples.basics;
 
import java.util.Hashtable;
 
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.SizeLimitExceededException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
 
/**
 * Demonstrates how to perform a search and limit the number of results
 * returned.
 *
 * usage: java SearchCountLimit
 */
class SearchCountLimit {
         static int expected = 1;
 
         public static void printSearchEnumeration(NamingEnumeration enu) {
                            int count = 0;
                            try {
                                               while (enu.hasMore()) {
                                                                 SearchResult sr = (SearchResult) enu.next();
                                                                 System.out.println(">>>" + sr.getName());
                                                                 ++count;
                                               }
                                               System.out.println("number of answers: " + count);
                            } catch (SizeLimitExceededException e) {
                                               if (count == expected)
                                                                 System.out.println("number of answers: " + count);
                                               else
                                                                 e.printStackTrace();
                            } catch (NamingException e) {
                                               e.printStackTrace();
                            }
         }
 
         public static void main(String[] args) {
 
                            // Set up the environment for creating the initial context
                            Hashtable env = new Hashtable();
                            env.put(Context.INITIAL_CONTEXT_FACTORY,
                                                                 "com.sun.jndi.ldap.LdapCtxFactory");
                            env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");
                            env.put(Context.SECURITY_AUTHENTICATION, "simple");
                            env.put(Context.SECURITY_PRINCIPAL, "uid=admin,ou=system");
                            env.put(Context.SECURITY_CREDENTIALS, "sunjavaduke");
 
                            try {
                                               // Create initial context
                                               DirContext ctx = new InitialDirContext(env);
                                               // Set search controls to limit count to 'expected'
                                               SearchControls ctls = new SearchControls();
                                               ctls.setCountLimit(expected);
                                               // Search for objects with those matching attributes
                                               NamingEnumeration answer = ctx.search("ou=People", "(sn=M*)", ctls);
                                               // Print the answer
                                               printSearchEnumeration(answer);
                                               // Close the context when we're done
                                               ctx.close();
                            } catch (Exception e) {
                                               System.err.println(e);
                            }
         }
}
(运行上面的程序时,打印结果为:
>>>cn=Spuds Mackenzie
>>>cn=Londo Mollari
number of answers: 2
并没有抛出异常)
设置超时
                   设置超时是为了不至于等待太长的时间,如果超过时间限制,操作还没有执行完的话,将抛出TimeLimitExceededException,下面的代码设置了时间限制:
ctls.setTimeLimit(1000); //set time limit to 1 second
为了使这个程序运行,需要做一些特殊的设置,例如使用较慢的服务器或者检索范围使用SUB_TREE检索,并且服务器最好包含大量的实体。
当设置时间限制为0的时候,则表示没有时间限制。
集成命名和目录操作
命名操作部分介绍了如何使用Context接口的bind(),rebind(),createSubcontext()方法来创建绑定和子上下文。DirContext接口包含了这些方法的重载方法,接受属性参数。可以使用这些DirContext方法关联属性和对象。例如,可以创建一个Person对象,并将其绑定到一个名称空间,同时添加一些Person对象的属性。
创建一个包含属性的上下文
通过使用DirContext.createSubcontext()方法创建一个包含属性的上下文,提供要创建的上下文的名字和属性作为参数:
// Create the initial context
DirContext ctx = new InitialDirContext(env);
// 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("ou=Fruits", attrs);
上面的代码创建了一个叫做ou=Fruits的上下文,并包含具有两个值得属性objectClass,值分别为toporganizationalUnit,这个新的上下文被追加到ctx上下文中,然后列出ctx下面的内容,结果如下:
ou=Groups: javax.naming.directory.DirContext
ou=People: javax.naming.directory.DirContext
ou=Fruits: javax.naming.directory.DirContext
可以看到新建的Context已经添加成功。
添加包含属性的绑定
DirContext.bind()方法用来在上下文中添加一个包含属性的绑定。该方法的参数是对象的名字被绑定的对象以及一个属性集合
// Create the initial context
DirContext ctx = new InitialDirContext(env);
// Create object to be bound
Fruit fruit = new Fruit("orange");
// Create attributes to be associated with 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类的对象fruit,并使用ou=favorite名字将其绑定到ou=Fruits的上下文。这个上下文相对于ctx,及o=JNDITutorial。这个绑定具有objectclass属性。绑定完成后查找ou=favorite,ou=Fruits,将会获取fruit对象。如果获取ou=favorite,ou=Fruits的属性,那么将会获取新建的对象的属性:
orange
attribute: ou
value: favorite
attribute: objectClass
value: javaNamingReference
value: javaObject
value: organizationalUnit
value: top
attribute: javafactory
value: com.sun.jndi.examples.basics.FruitFactory
attribute: javareferenceaddress
value: #0#fruit#orange
attribute: javaclassname
value: com.sun.jndi.examples.basics.Fruit
(当运行这个程序两次的话,会提示错误信息NameAlreadyBoundException,这是由于ou=favorite已经被绑定到ou=Fruits上下文。)
可以使用rebind进行重新绑定。
有关JNDI的另外一篇不错的文章,请参考:
http://www.javaworld.com/javaworld/jw-03-2000/jw-0324-ldap.html?page=1
  
原创粉丝点击