目录服务技术介绍——ADSI - [编程知识]

    2007-08-10

    版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
    http://wfpb.blogbus.com/logs/7593744.html

    目录服务技术介绍——ADSI

    【摘要】本文介绍了Microsoft提出的活动目录服务接口(ADSI)技术。文章首先讲述了ADSI的结构,然后介绍了ADSI的程序设计方法,最后通过例子简单说明了ADSI的用法。

    一、ADSI简介

    ADSI (Active Directory Services Interface)是Microsoft新推出的一项技术,它统一了许多底层服务的编程接口,程序员可以使用一致的对象技术来访问这些底层服务。ADSI把这些服务的公共部分提取出来,同时隔离出相异的部分,程序员可以用统一的接口访问底层服务的公共部分,并延伸到底层服务的专有部分。

    为了说明ADSI的接口标准和用法,首先我们解释几个重要的概念:

    目录(Directory):目录类似于一个数据库,它通常包含一些描述性的、基于属性的信息。由于目录中的信息被读访问的频率比写访问的频率要高得多,所以,目录并不象数据库那样必须实现复杂的事务或者回滚机制。在许可的条件下,目录的更新操作通常是简单的更改操作。对于大量的信息查找或者信息检索要求,目录应该快速给出应答。为了提高性能和可靠性,并降低应答时间,分布式的目录信息可能需要在广阔的网络范围内进行复制操作。因为引入了复制机制,所以在复制点之间,暂时的不一致是难以避免的,只要最终能够同步即可。

    目录服务(Directory Service):目录服务所表达的含义与目录紧密相关,它是指目录信息源与针对这些信息源的服务结合起来使得这些信息可被用户使用。

    一个典型的目录服务具有以下几个特性:

    □    安全特性。管理员可以对目录强制使用安全特性,以便保护目录信息不被非法获取。

    □    分布特性。一个目录可以跨越机器边界,也即目录信息可以在网络上的不同机器上。

    □    复制特性。目录信息应尽量让更广阔范围的用户所利用。

    目录服务既是一个信息管理工具,同时也是一个面向最终用户的工具。在网络上各种各样的对象数量迅速增长的今天,目录服务非常有意义。类似于网络硬件设施的集线器(hub)的概念,目录服务相当于软件集线器。

    活动目录(Active Directory):活动目录是Microsoft在Windows 2000中实现的目录服务,它不仅实现了作为一般目录服务的特性,包括安全特性、分布特性以及复制特性等。它同时也扩充了一些新的特性,以便使得目录信息更加易于管理和搜索。活动目录在实现目录服务的同时,充分考虑了目录信息的可伸缩性,从包含上百个对象的单服务器结构到成千上百服务器的百万以上的对象信息,它都可以正常工作。

    目录服务是一个抽象的概念,从用户使用的角度来看,通常最为关心的是目录的名字空间以及相关的访问协议。名字空间限定了目录服务的描述能力,而访问协议必须要标准化,以便支持分布式特性。访问协议的标准为LDAP(Light-weight Directory Access Protocol)协议,它是建立在TCP/IP基础上的目录服务协议,它的信息模型包括了数据和名字空间。与其它Internet协议不同的是,LDAP还提供了一组API以便简化LDAP应用的编写工作。

    LDAP也遵守客户-服务器模型,包含目录数据的一个或多个LDAP服务器建立起一个LDAP目录树,LDAP客户通过网络连接到服务器,向服务器发出请求或者执行一些操作。LDAP服务器响应客户的请求,或者把客户的请求指引到其它包含客户指定信息的另外的LDAP服务器上。不管客户连接到哪个LDAP服务器上,他所看到的目录树视图应该是一样的。

    Microsoft提供了ADSI用于开发客户方的目录服务应用系统。ADSI是一组COM接口标准,它通过LDAP协议访问目录服务。ADSI实现了目录服务的客户模型,利用ADSI,我们可以在Windows平台上开发目录服务客户应用。如果不使用ADSI,那么用到目录服务的应用必须编写代码处理每个它所用到的名字空间结构,当有新的名字空间加入时,必须修改代码以适应变化,而且,在访问目录服务时,可能要调用到底层与网络有关的API函数

    从技术角度来看,ADSI用到了与ADO(Active Data Object,Microsoft推出的一致数据访问接口)非常类似的技术,它通过一组双接口(dual interface,既可以通过vtable也可以通过自动化接口IDispatch访问属性和方法的自动化对象)提供了目录服务功能。客户程序可以根据性能要求或者开发语言的特点选择不同的编程模型。

    由于ADSI使用了COM和自动化对象技术,所以ADSI的编程用法比较简单,但要真正掌握ADSI,首先必须理解ADSI接口标准中用到的一些基本概念,下面列出如下:

    (1) 名字空间(Namespace)。名字空间是LDAP的基本概念,也是ADSI的基本概念。名字空间是一个有界区域,每一个给定的名字都必须在特定的名字空间中被解析,解析的过程是把名字翻译成某个对象或者名字所代表的信息。比如,电话簿形成了一个名字空间,每一个电话订户的名字被解析成电话号码;NTFS文件系统也构成了一个名字空间,每个文件名可被解析成文件对象。活动目录也形成了一个名字空间,目录中的每个对象可被解析到对象本身。

    (2) 对象(Object)或目录对象(Directory Object)。对象是指一组属性的集合,它往往代表了有形的实体,比如用户、文件等。对象通过属性描述它的基本特征,比如,用户的属性可能包括姓名、电话号码、电子邮件地址等。

    (3) 包容器(Container)。包容器是名字空间的一部分,与目录对象一样,它也有属性,但与目录对象不同的是,它不代表有形的实体,而是其它目录对象或者包容器的容器。

    (4) 目录树(Directory Tree)。在一个名字空间中,由包容器和对象构成了一个完整的树结构。在ADSI中,树是基本的结构,从每一个包容器对象作为起点,层层深入,都可以构成一棵子树。一个简单的目录可以构成一棵树,一个计算机网络或者一个NT域也可以构成一棵树。

    (5) 标识名(Distinguished Name,简称为DN名)。活动目录中的每一个对象都有一个标识名,标识名包含对象在名字空间中的完整路径名,从基本的名字空间包容器开始,通过层层包容器对象,一直到达对象节点。不同的名字空间有不同的标识名命名规则,ADSI提供了一个命名框架,通过名字即可识别相应的目录服务和名字空间。

    (6) 对象标识符(Object Identity)。对象除了其DN名之外,它还有一个128位的全局标识符(GUID)。在ADSI内部,对象是通过标识符来识别的,而不是名字。当对象被创建时,目录服务代理程序首先为对象分配一个标识符,客户可以通过对象的“objectGUID”属性获得标识符,这是一个只读属性,不管对象被移动或者改名,它的标识符都不会被改变。

    (7) 命名环境(Naming Context)。命名环境是指任何一个目录子树,在ADSI中,一个服务器至少包含三个命名环境:表结构(schema)、配置(configuration)和用户命名环境。

    (8) 域(Domain)。在ADSI中,域是Windows NT网络的安全性边界。ADSI由一个或多个域组成。在单独的计算机上,域即指计算机本身。当多个域通过信任关系连接起来之后,所有的域共享公共的表结构、配置、全局目录(global catalog),从而形成域树(domain tree),多个域树连接在一起形成域林(domain forest)。域林中的所有域共享公共的表结构、配置、全局目录(global catalog)。

    (9) 站点(site)。站点是指一个或多个通过TCP/IP连接起来的子网。通常,站点内部的子网通过可靠的网络连接起来。

    从编程技术来看,ADSI只是一些COM接口和COM组件的标准,但ADSI表达信息的方式是革命性的。名字空间的概念体现了ADSI表达信息的广泛性;域和站点的概念体现了ADSI的空间广阔性,从单机到局域网,再到广域网,都可以纳入ADSI的表达范围;命名环境的概念体现了ADSI对信息的自组织、自描述特性。对象、包容器、目录树的概念体现了ADSI表达信息的基本结构方式。对象标识名和标识符两种机制结合起来,使得目录对象既有直接面对最终用户的名字特性,又有内部唯一标识的可编程特性。

    在ADSI出现之前,实际上,我们已经有了很多可按目录方式管理的应用系统,比如文件系统、用户管理、电子邮件应用等等。ADSI抽取了这些应用对象的共性,通过一组标准化的接口实现目录对象的管理。虽然,我们还不能看到Windows 2000中ADSI的最终形式,但是在Windows NT 4.0版本基础上的一些应用系统,已经实现了ADSI标准,最为典型的是Microsoft Exchange Server,它充分体现了ADSI的基本概念。


    收藏到:Del.icio.us




    Tag:
    引用地址:

    评论

  • 客户程序也可以利用IADsClass、IADsProperty和IADsSyntax接口在运行时刻确定目录对象的详细属性信息。操作步骤如下:



    a. 先绑定到目录对象的表结构对象,可以直接访问目录对象的“schema”属性。



    b. 用枚举的办法从表结构对象中找到强制属性或者可选属性,如果客户知道要访问的属性名则可以跳过这一步。



    c. 绑定到表结构对象的包容器对象。



    d. 从表结构对象的包容器对象获得属性的定义对象。



    e. 从属性定义对象获取属性信息,包括属性的语法信息等。



    下面的代码演示了如何从获得表结构对象到访问“Owner”属性的细节信息的过程:



    Dim obj As IADs



    Dim cl As IADsClass



    Dim pr As IADsProperty



    Dim sy As IADsSyntax



    Dim sc As IADsContainer



    Set obj = GetObject("WinNT://MyDomain/myMachine")



    Set cl = GetObject(obj.Schema)



    Set sc = GetObject(cl.Parent)



    Set pr = sc.GetObject("Property","Owner")



    MsgBox "Attribute: " & pr.Name



    MsgBox "Syntax: " & pr.Syntax



    Set sy = GetObject(sc.AdsPath & "/" & pr.Syntax)



    MsgBox "Syntax object: " & sy.Name & " of OleAutoDataType: " & sy.OleAutoDataType



    (6) 增加或者删除目录对象。



    利用包容器对象的IADsContainer接口的方法(见表4),我们可以很方便地增加或者删除目录对象,举例如下:



    Dim Container as IADsContainer



    Dim NewUser as IADsUser



    Set Container = GetObject("WinNT://MyDomain")



    @# Create the new wrapper.



    Set NewUser = Container.Create("user", "PanAimin")



    @# Write it back to the DS



    NewUser.SetInfo



    @# Set Jane@#s password.



    NewUser.SetPassword("123")



    @# Delete the user.



    Call Container.Delete("user","Mary")



    ADSI的各种操作比较简单,只要我们能够理解ADSI的基本结构,并且对核心的几个接口以及常用提供者的一些接口有所了解,就可以用一致的方法访问目录服务,这也说明了ADSI作为通用目录服务标准为程序员带来的最大益处。



    实现ADSI提供者要比使用ADSI复杂得多,通常情况下,我们只需使用系统已经定义的ADSI提供者。但如果确实应用需要,我们可以实现自定义的ADSI提供者。为了实现一个ADSI提供者,有一些特性是必须要支持的,而其它的特性则是可选的。下面是一些必须要实现的特性:



    (1) 路径解析使用COM的命名规范(moniker),其中名字空间对象必须要实现IParseDisplayName::ParseDisplayName函数以及IADsOpenDSObject接口。



    (2) IADs接口必须实现属性的缓存技术,只有IADs::GetInfo函数和IADs::SetInfo函数才刷新缓存或提交到底层目录服务。



    (3) 每一个包容器对象必须实现IADsContainer接口。



    (4) 所有的ADSI对象都支持IDispatch接口,以便自动化客户访问属性和方法。



    (5) 对于非自动化客户通过IDirectoryObject访问ADSI目录对象,而不是IADs接口。



    (6) ADSI至少实现一个表结构包容器对象,以及相应的语法、属性和类别对象,分别支持接口IADsSyntax、IADsProperty或IADsClass,每个根节点必须包含它自己的表结构包容器对象。



    为了让ADSI能够找到自定义的提供者,我们必须在实现了ADSI提供者之后,把它注册到系统注册表中。在HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ADs\Providers键下添加新的提供者项(指向提供者组件的ProgID),如果读者感兴趣,可以检查该键项,以便确定当前系统已经安装了哪些提供者。
  • 由于目录对象的标识名可能在某些操作的影响下被改变,但目录对象的objectGUID属性永远不会改变,所以我们可以直接在LDAP路径中指定此GUID,客户程序直接绑定到此对象,下面的代码显示了这种用法:



    IADs *pADs;



    LPWSTR pszFilter = L"LDAP://MyServer/<GUID=63560110f7e1d111a6bfaaaf842b9cfa>";



    hr = ADsGetObject( pszFilter, IID_IADs, (void**)&pADs);



    等价的VB代码如下:



    Dim myObject as IADs



    Set myObject = GetObject("LDAP://<GUID=63560110f7e1d111a6bfaaaf842b9cfa>")



    (2) 包容器对象的枚举操作。



    客户程序通过上面的方法可以直接到达指定的目录对象或者包容器对象,我们也可以从某个节点开始访问目录子树,利用IADsContainer接口所提供的方法,我们可以实现枚举操作,举例如下:



    Dim MyObject as IADs



    Dim Child as IADs



    Dim Container as IADsContainer







    On Error Resume Next



    Set MyObject = GetObject("LDAP://MyServer")



    Set Container = MyObject



    If Err = 0 Then



    For Each Child in Container



    Debug.Print Child.Name



    Next Child



    EndIf



    通过这种枚举操作,我们可以访问目录子树中的所有包容器对象和目录对象。



    (3) 读取目录对象的属性。



    读取对象属性最简单的方法莫过于直接通过对象的IADs::Get或者IADs::GetEx接口成员函数,比如:



    Dim MyUser as IADs



    Dim MyCommonName as String



    set MyUser = GetObject("WinNT://MyServer/MyName")



    MyCommonName = MyUser.Get("Fullname")



    当然如果我们已经知道了目录对象的其它接口,也可以用更直接的方法访问属性,比如:



    Dim MyUser as IADsUser



    Dim MyName as String



    Set MyUser = GetObject("WinNT://MyServer/MyName")



    MyName = MyUser.Fullname



    客户程序在访问目录对象时,通常ADSI已经为对象的属性作了缓存。当对象被创建时,其属性缓存为空,如果客户调用IADs::GetInfo从底层目录服务装载对象属性,那么它就会填满缓存,因为Get或者其它的Get_propertymethod函数都隐式调用了GetInfo函数,所以客户只要调用Get函数取一次属性值,以后的Get函数就直接从缓存中读取属性值。如果客户程序要刷新缓存中的属性值,可以显式调用IADs::GetInfo函数。使用缓存可以避免频繁地从服务器读取属性数据,从而降低网络负担。



    如果客户程序不希望在缓存状态下读取属性值,可以使用目录对象的另一个接口IDirectoryObject,它可以直接访问底层目录服务的对象属性数据,为了优化性能,每次它可以取多个属性值。



    (4) 设置目录对象的属性。



    与读取属性相对应,设置对象属性可通过对象的IADs::Put或者IADs::PutEx接口成员函数实现,但是这两个函数只是把属性值写到缓存中,客户程序必须显式调用IADs::SetInfo成员函数,以便使更新后的属性值反映到底层目录服务中。举例如下:



    Dim MyUser As IADsUser



    Dim NewName As Variant



    Set MyUser = GetObject("WinNT://MyServer/MyName")



    NewName = "Pan Aimin"



    User.Put "FullName", NewName



    User.SetInfo



    当客户程序创建了一个新的目录对象之后,只有在调用了IADs::SetInfo函数后,目录对象才真正被永久创建,否则只是缓存中的对象而已。



    (5) 表结构的用法。



    ADSI提供了用表结构描述目录对象属性的机制,客户程序可以在运行时刻访问目录对象表结构信息,下面的代码演示了表结构的基本用法:



    Dim Computer As IADsComputer



    Dim Class As IADsClass



    Dim V As Variant



    Set Computer = GetObject("WinNT://MyDomain/MyMachine")



    Set Class = GetObject(c.Schema)



    Debug.Print "Properties in this Class: "



    For Each V In Class.MandatoryProperties



    Debug.Print " "; V



    Next V



    For Each V In Class.OptionalProperties



    Debug.Print " "; V



    Next V

  • 表1 IADs接口的属性



    属性名

    类型

    说明



    Name

    BSTR

    对象名字



    AdsPath

    BSTR

    对象的全路径



    Class

    BSTR

    对象的类别(表结构对象的路径)



    GUID

    BSTR

    对象的全局唯一标识(GUID)



    Parent

    BSTR

    父包容器的路径



    Schema

    BSTR

    表结构对象的路径







    表2 IADs接口的方法



    方法名

    参数

    说明



    GetInfo



    用属性值重新装入对象



    SetInfo



    提交属性值的变化



    Get

    BSTR bstrName,



    VARIANT *pvProp

    获取指定名字的属性值



    Put

    BSTR bstrName,



    VARIANT vProp

    设置指定名字的属性值



    GetEx

    BSTR bstrName,



    VARIANT *pvProp

    获取指定名字的单值或多值属性值



    PutEx

    LONG lnControlCode,



    BSTR bstrName,



    VARIANT vProp

    设置指定名字的单值或多值属性值;若lnControlCode为ADS_PROPERTY_CLEAR,可以删除属性



    GetInfoEx

    VARIANT vProps,



    LONG lReserved

    与GetInfo类似,优化处理







    另一个重要的接口是IADsContainer,它是所有包容器对象必须要实现的接口。IADsContainer接口的属性和方法如表3和表4所示。



    表3 IADsContainer接口的属性



    属性名

    类型

    说明



    Count

    long

    包容器中对象的个数



    _NewEnum

    LPUNKNOWN

    返回包容器中对象的枚举器



    Filter

    VARIANT

    BSTR数组,每个BSTR指定了过滤器的定义



    Hints

    VARIANT

    属性数组,其用法与GetInfoEx类似







    表4 IADsContainer接口的方法



    方法名

    参数

    说明



    GetObject

    BSTR bstrClass,



    BSTR bstrRelativeName,



    LPDISPATCH *pNewObject

    得到包容器对象中指定对象的IDispatch接口指针



    Create

    BSTR bstrClass,



    BSTR bstrRelativeName,



    LPDISPATCH *pNewObject

    在包容器对象中创建指定名字的目录对象,并返回对象的IDispatch接口指针



    Delete

    BSTR bstrClass,



    BSTR bstrRelativeName

    删除包容器对象中指定的目录对象



    CopyHere

    BSTR bstrSource,



    BSTR bstrNewName,



    LPDISPATCH* pNewObject

    拷贝目录对象到当前包容器对象中



    MoveHere

    BSTR bstrSource,



    BSTR bstrNewName,



    LPDISPATCH* pNewObject

    移动目录对象到当前包容器对象中







    IADs和IADsContainer接口分别代表了目录服务中的目录对象和包容器对象,为了对一个名字空间进行各种目录操作,我们必须从最基本的根出发,ADSI为我们提供了接口IADsNamespaces用以表达名字空间对象。IADsNamespaces接口比较简单,它只有一个属性“DefaultContainer”,此属性指定了客户程序访问的基本包容器对象的路径。



    仅仅一个IADs接口还难以表达一个目录对象的全部特征,前面已经提到,ADSI用类别来定义目录对象的特征,并且类别对象本身也是目录对象,它除了实现IADs接口外,还实现了IADsClass接口。IADsClass接口从IADs派生,其属性如表5所示。



    表5 IADsClass接口的属性



    属性名

    类型

    说明



    PrimaryInterface

    BSTR

    可直接访问到此类别属性的接口ID



    CLSID

    BSTR

    与之相连的COM对象的CLSID



    OID

    BSTR

    定义此类别的对象标识符



    Abstract

    VARIANT_BOOL

    指示此类别是否为抽象类,若是的话,则它不能被实例化,但其它类可以继承该类别



    Auxiliary

    VARIANT_BOOL

    指示此类别是否为辅助类



    MandatoryProperties

    VARIANT

    与之相连的目录对象必须支持的属性名数组



    OptionalProperties

    VARIANT

    与之相连的目录对象支持的可选属性名数组



    NamingProperties

    VARIANT

    与之相连的目录对象支持的命名属性名数组(用于指示相对标识名RDN)



    DerivedFrom

    VARIANT

    此类所继承的类的路径数组



    AuxDerivedFrom

    VARIANT

    此类所继承的辅助类的路径数组



    PossibleSuperiors

    VARIANT

    可能包含此类实例的类的路径数组



    Containment

    VARIANT

    可能被包含此类中的类的路径数组



    Container

    VARIANT_BOOL

    指示此类是否为包容器对象类别



    HelpFileName

    BSTR

    帮助文件名



    HelpFileContext

    long

    帮助环境标识符







    IADsClass接口只有一个方法Qualifiers,用来返回描述附加限制对象的集合对象。在IADsClass接口的属性中,PrimaryInterface属性可直接指示客户程序去请求对应的接口ID,以便访问该类别所指示的属性,比如,在目录服务中,“User”类指示用户对象实现了一个ADSI接口IADsUser,它包含属性“姓”、“名”、“职称”、“电话”等等。
  • 下面列出目前已经实现的ADSI目录服务:



    (1) Windows NT域用户管理。



    (2) LDAP (Exchange Server)目录服务。



    (3) Internet Information Server



    (4) NDS(Novell NetWare Directory Services)



    目前,在一个企业内部存在多个目录服务也会带来一些问题。对于管理员来说,它必须管理多个目录服务,这使得管理更为复杂;对于企业工作人员来说,为了访问不同目录服务中的信息,他必须登录到多个目录服务中;对于开发人员来说,为了使用目录服务,他必须选择使用哪个目录服务或者使用多个目录服务。虽然ADSI统一了目录服务的编程接口,但是为了在企业内部更好地使用目录服务功能,必须对目录服务的应用有所规划。由于Windows 2000全面采用了ADSI作为其应用编程接口,因此,建立在Windows 2000上的应用将可以更方便地访问系统提供的各种目录服务,应用程序与操作系统可以更好地结合起来。



    二、ADSI结构



    ADSI编程接口包括两个方面,实现ADSI目录服务的提供者(provider)和使用ADSI的客户。每一个当前被支持的目录服务必须有一个提供者,ADSI提供者实现了ADSI对象以及与名字空间相关的对象;ADSI客户与普通的COM客户程序类似,它调用ADSI接口访问目录服务所提供的各种功能,包括查找目录、读取目录对象的属性,如果允许的话,还可以修改对象的属性。



    ADSI提供目录访问的基本结构如下图所示:







    图1 ADSI目录服务层次结构图



    ADSI提供了两种形式的编程接口,对于支持自动化的客户,它可以通过ADSI自动化接口调用目录服务提供者的属性和方法;对于性能要求比较高的客户,它可以通过vtable形式的COM接口访问目录服务提供者。ADSI这种双接口结构几乎总能满足客户程序的需要,关于双接口的原理以及用法请读者参阅有关自动化(Automation)方面的书籍,本文为简便起见,仅从自动化接口的角度讨论ADSI接口。



    在ADSI所定义的接口规范中,对应于目录服务的目录对象,我们也称为活动目录对象(Active Directory Object)。另一个重要的对象为包容器(Container) 对象,包容器对象与目录对象的关系跟我们在文件系统中使用的目录与文件的关系类似,一个包容器对象可以包含许多其它的目录对象或者包容器对象,而目录对象代表了一定的实体。对于每个ADSI提供者,它从基本的名字空间的根节点开始,通过包容器对象和目录对象,形成了一棵树,如图2所示。







    图2 ADSI包容器对象与目录对象的树状结构示意图



    ADSI的每个包容器对象或者目录对象都有一个与之相联系的类别对象,类别对象说明了对象的一些特征信息,比如,对象的属性、对象属于包容器还是目录对象,等等。类别对象也描述一些属性,这些属性是所有属于此类别的对象所共有的属性,在这些属性中,有的属性是强制必须要有的,而有的属性则是可选的。属性最重要的特征是它的语法,语法定义本身也是一个目录对象。类别、属性和语法都是某个包容器对象下面的目录对象,被称为表结构(schema)。



    在ADSI定义的接口中,最基本的接口为IADs接口,它是一个从IDispatch接口派生出来的双接口,并且所有的目录对象都必须实现IADs接口。IADs接口的属性和方法如表1和表2所示。