Isabella
---- I fell upon the thorn of life, I bleed.
 

System.DirectoryServices使用户能够通过ASP.NET访问一些基本的用户管理功能。这篇文章首先回顾了活动目录(AD)的有关概念,然后简要地讨论了实际的System.DirectoryServices名字空间本身,最后给出了可供我们在实际应用程序中使用的代码。

活动目录是什么?



在目前的网络环境下,能够方便地控制对各个网络设备的访问是非常关健的。在控制谁在何时有访问何种设备的权限时,需要有一整套方法,这些设备包括打印机、文件以及其他的局域网或分布式网络资源。AD能够提供这些功能,由于与操作系统的整合非常紧密,这意味着AD能够在非常低的级别上提供支持。

AD的工作原理



单地说,AD就是一个内容为所有网络资源、分层次、面向对象的数据库。最顶层的对象一般是Organization (O),在该组织单元(OU)之下是容器,最后是包含实际资源的对象。这种分层次的格式为系统管理人员创建了一种熟悉和易于管理的。例如,如果指定一个OU访问一个实际的资源,这一权限也将被赋予包含在其中的对象。

如何创建AD



.NET Framework中,微软为我们提供了System.DirectoryServices名字空间,它又使用了活动目录服务接口(ADSI)。

DSI
是通过编程与许多不同的目录服务提供者交互的方式,也就是一种编程接口。

System.DirectoryServices
空间中的类能够与如下所示的活动目录服务提供者配合使用:

1.1 AD服务提供者

 

目录服务提供者

路径

Windows NT 5.0Windows 2000Windows XP

WinNT://path

Lightweight Directory Access Protocol (LDAP)

LDAP://path

Novell NetWare Directory Service

NDS://path

Novell Netware 3.x

NWCOMPAT://path

Internet Information Services (IIS)

IIS://



System.DirectoryServices名字空间内,有二个主要的类:System.DirectoryServices.DirectoryEntrySystem.DirectoryServices.DirectorySearcher类。本篇文章不涉及这二个类的细节,需要注意的是,DirectorySearcher只能与LDAP提供者一起使用。

在使用DirectoryEntry对象时,每个对象都有一个模式。模型是对象条目的类型。例如,如果有一个User模式的DirectoryEntry对象,它就代表是一个用户。

在本篇文章中,我们使用了Windows 2000提供者(WinNT://)System.DirectoryServices.DirectoryEntry类。

用户管理



在本篇文章的例子中,我们使用System.DirectoryServices名字空间创建一个数据访问层(DAL),执行一些非常基础的用户管理任务。DAL是对执行数据访问的真实的复杂性进行抽象的一种方法。例如,如果我们要编写一个访问Access数据库的DAL,就可以在对象中隐藏所有与ADO.NET有关的任务。当开发人员需要与数据库打交道时,只需要简单地使用这些对象,而无需关心特定的实现细节。开发者根本无需为ADO.NET操心,即使是以后决定升级到SQL Server,也只是需要改变DAL,使之适合新的数据库即可,其他的东西无需改变。

用户对象



我们建立的第一个对象用来表示任何给定用户的当前状态,该对象对User模式的DirectoryEntry类进行抽象。为了清楚起见,它看起来更象一个实际的User对象。


namespace DSHelper {

 public class DSUser {

    public DSUser(System.DirectoryServices.DirectoryEntry user) {

      this.domainName=user.Path;

      this.Username=user.Name;

      this.Password=user.Password;

      try {

        this.FullName=Convert.ToString(user.Invoke("Get", new object[]

{"FullName"}));

        this.Description=Convert.ToString(user.Invoke("Get", new object[]

{"Description"}));

        this.PasswordExpired=Convert.ToInt32(user.Invoke("Get", new object[]

{"PasswordExpired"}));

        this.RasPermissions=Convert.ToInt32(user.Invoke("Get", new object[]

{"RasPermissions"}));

        this.MaxStorage=Convert.ToInt32(user.Invoke("Get", new object[]

{"MaxStorage"}));

        this.PasswordAge=Convert.ToInt32(user.Invoke("Get", new object[]

{"PasswordAge"}));

        this.HomeDirectory=Convert.ToString(user.Invoke("Get", new object[]

{"HomeDirectory"}));

        this.LoginScript=Convert.ToString(user.Invoke("Get", new object[]

{"LoginScript"}));

        this.HomeDirDrive=Convert.ToString(user.Invoke("Get", new object[]

{"HomeDirDrive"}));

        this.userDirEntry=user;

      }catch(Exception e) {

        throw(new Exception("Could not load user from given DirectoryEntry"));

      }

    }

    public DSUser(string Username, string Password, string DomainName) {

      domainName=DomainName;

      if(domainName=="" || domainName==null) domainName=Environment.MachineName;

      username=Username;

      password=Password;

    }

    private object groups=null;

    public object Groups{get{return groups;} set{groups=value;}}

 }

}

 

1.2 User对象



我们的用户对象有二个缺省的构造器。第一个用来用一个给定的DirectoryEntry对象对我们的用户进行初始化,它将使用Invoke方法从对象中获取用户的属性。

第二个构造器用来创建一个新的用户。我们只需要向它传递三个参数,在用来创建新用户时,它就会创建一个新的DSUser对象。需要注意的是,由于没有完成任何的AD操作,因此我们并没有在AD中创建真正的用户。

数据访问层(DAL



下一步就是创建ADDAL封装了,下面的一些代码是分步骤完成的完整的UserAdmin DAL代码。

我们首先创建并初始化在UserAdmin类中所需要的代码:


#缺省属性的初始化

    //我们的错误日志设备,应当尽量保持简单,避免代码膨胀过大

    System.Text.StringBuilder errorLog = new System.Text.StringBuilder();

    private System.Boolean error=false;

    public System.Boolean Error{get{return error;}}

    public string ErrorLog{get{return errorLog.ToString();}}

    //设置缺省的属性

    private string loginPath="WinNT://"+Environment.MachineName+",computer";

    private string domainName=null;

    private string loginUsername=null;

    private string loginPassword=null;

    private System.DirectoryServices.AuthenticationTypes authenticationType =

System.DirectoryServices.AuthenticationTypes.None;

    private System.DirectoryServices.DirectoryEntry AD=null;

    private System.Boolean connected=false;

    #endregion

 

1.3 缺省属性的初始化



注意我们是如何将LoginPath硬拷贝为WinNT提供商的。为了使DAL能够与任何其他AD服务提供商配合,这一点必须进行改变。另外需要注意的是,我使用了System.Text.StringBuilder对象来保存错误日志,从而简化了错误处理过程。在有错误发生的情况下,系统会简单地添加一条日志,Boolean型变量error将会被设置为值。下面的代码是我们设计的类的构造器:(图1.4


#region .ctor's

    public UserAdmin() {

      Connect();

    }

    /// <摘要>

    /// 在需要的时候,使我们能够创建UserAdmin

    /// </摘要>

    /// <param name="LoginUsername"></param>

    /// <param name="LoginPassword"></param>

    /// <param name="AuthenticationType"></param>

    /// <param name="DomainName"></param>

    public UserAdmin(string LoginUsername, string LoginPassword,

               System.DirectoryServices.AuthenticationTypes AuthenticationType,

string DomainName) {

      loginUsername=LoginUsername;

      loginPassword=LoginPassword;

      authenticationType=AuthenticationType;

      if(DomainName=="" || DomainName==null)

DomainName=System.Environment.UserDomainName;

      domainName=DomainName;

      Connect();

    }

    /// <摘要>

    /// 获得UserAdmin类的另一种方式,可以指定另外一个LoginPath,例如LDAPIIS

    /// </摘要>

    /// <param name="LoginPath"></param>

    /// <param name="LoginUsername"></param>

    /// <param name="LoginPassword"></param>

    /// <param name="AuthenticationType"></param>

    /// <param name="DomainName"></param>

    public UserAdmin(string LoginPath, string LoginUsername, string LoginPassword,

               System.DirectoryServices.AuthenticationTypes AuthenticationType,

string DomainName) {

      loginPath=LoginPath;

      loginUsername=LoginUsername;

      loginPassword=LoginPassword;

      authenticationType=AuthenticationType;

     if(DomainName=="" || DomainName==null)

DomainName=System.Environment.UserDomainName;

      domainName=DomainName;

      Connect();

    }

    #endregion

 

1.4 构造器

这里的第一个构造器是一个基本的缺省构造器,能够在无需任何参数的情况下创建对象。这意味着该对象将在没有指定的安全权限的情况下连接到本机的WinNT提供者。

第二个构造器使我们能够指定连接到给定域的AD上所需要的证书,在需要使用给定的证书连接到任意域名时,这一构造器非常方便。

第二个构造器中增添了LoginPath参数,这将使我们能够覆盖LoginPath,能够选择缺省提供商之外的其他服务提供者。

下面的代码被我称为活动方法,代码如下所示:


public DSHelper.DSUser LoadUser(string username)



这一方法用来接收用户名,并在当前的提供者中查找DirectoryEntry。如果没有找到,就简单地返回NULL


public System.Boolean AddUserToGroup(string groupname, DSHelper.DSUser dsUser)



这一方法接收现有组的名字和定制的DSUser类的实例,并试图将用户添加到提供的组中。这也是第一个我们能看到impersonation的方法()。这一方法的核心是下面这行代码:


public System.Collections.ArrayList GetGroups()



我们将使用该方法获得域中所有组的名单。


public System.Boolean DeleteUser(DSHelper.DSUser dsUser)



这一方法获得给定的用户名,并从活动目录中完全删除它。


public System.DirectoryServices.DirectoryEntry SaveUser(DSHelper.DSUser dsUser)



没有实际的在数据库中保存和插入记录的能力,就不能被称为一个完整的DAL。上面的方法可以完成这二项任务,它首先检查AD,看是否有指定的用户存在。如果存在,,则会用我们提供的数据更新该用户。如果用户不存在,就会创建一个用户。


public System.Boolean Connect()



最后,我们需要一个方法,与数据库进行连接,Connect()就是用来完成这一任务的方法。需要指出的是,此时,它不执行任何假冒的操作或指定使用AD的证书,我们只在需要的时候才提供证书,这就使得在没有提供安全证书的情况下,DAL只能在只读的状态下使用。

下面我们将上面的内容串起来,来看看我们如何使用DAL创建ASP.NET用户管理页。

ASP.NET用户管理页



在这一例子工程中,我们能够为系统管理员用户输入用户名和口令,它还提供了一个文本框,我们可以输入域中任何用户的名字,就会列出其所有属性。我们还可以对用户进行编辑、保存操作,也可以完全删除该用户。

现在我们来详细地回顾整个工程,其中重点是与DAL进行交互的部份。请读者考虑使用新的方法改进界面,并实际地创建一个可用性更高以及更友好的设计。另外,读者也可以考虑如何使用控制台应用程序创建界面。

扮演

扮演这个词的意思是,我们以其他人或用户的身份执行操作。在我们的ASP.NET应用程序中,这意味着我们可以暂时地客串其他用户,而不是IIS用来处理匿名访问的缺省帐户的ASP.NET用户。我们希望能够使自己的代码扮演对AD资源有更大访问权限的其他用户,以便对资源进行适当的修改。要完成这一过程非常简单。


#region setup impersonation via interop

    //需要保存详细资料时,为完成扮演需要通过InteropServicesCOM中导入

    public const int LOGON32_LOGON_INTERACTIVE = 2;

    public const int LOGON32_PROVIDER_DEFAULT = 0;

    System.Security.Principal.WindowsImpersonationContext impersonationContext;

[DllImport("advapi32.dll", CharSet=CharSet.Auto)]public static extern int

LogonUser(String lpszUserName,

String lpszDomain,String lpszPassword,int dwLogonType,int dwLogonProvider,

ref IntPtr phToken);

[DllImport("advapi32.dll", CharSet=System.Runtime.InteropServices.CharSet.Auto,

SetLastError=true)]public

extern static int DuplicateToken(IntPtr hToken, int impersonationLevel,

ref IntPtr hNewToken);

    #endregion

 

1.5 建立扮演



首先,我们需要从advapi32.dll中导入新的方法,其中包括一些有用的常量。名字为impersonationContext的变量将用来保持扮演操作之前的Windows用户。

这样,我们就建立了扮演,下面是一个如何使用它的例子:


if(impersonateValidUser(this.LoginUsername, this.DomainName,

this.loginPassword)) {

 //在这里插入在指定用户的安全环境下运行的代码

 //不要忘记取消扮演

   undoImpersonation();

} else {

 //扮演操作失败了,插入保证失败后系统安全的机制

}

 

1.6 扮演的使用



我们只需要简单地以有效的用户名和口令调用impersonateValidUser方法,来扮演该用户。从现在开始,只到调用undoImpersonation()之后,我们将以给定的用户名执行所有的操作。

为了使ASP.NET下的扮演操作能够正常地工作,我们应当改变machine.configc:\winnt\microsoft.net\framework\v%VERSION%\config\machine.config)中的processModel节点,其中的username属性必须被设置成System

结束语



本篇文章所论述的是使用System.DirectoryServices名字空间完成一个能够完全地运行的、面向任何提供商ADDAL所必需的基本内容。在实际应用中,可以对该例子进行改变,使这更符合我们的要求。需要记住的是,对于任何能够实际使用的DAL,它不应当只局限于包括正常的数据库提供者在内的一种服务提供者。我们应当能够交换DAL

看过本篇文章后,读者可能感到有一种意犹未尽的感觉。读者可能希望在其中添加组管理的功能,其中包括组的创建、编辑和删除,并显示每个用户所属的组以及每个组中的用户。不过这些都需要通过COM Interop才能完成,请读者自行参阅有关资料。


posted on 2007-02-05 10:39  李昀璟  阅读(492)  评论(0)    收藏  举报