微软活动目录是管理网络资源的一种有效技术,它使用户、系统管理员和应用程序开发者能以前所未有的方法使用操作系统,进一步模糊各网络之间的界限,提供了一种针对网络资源安全可靠、方便快捷的全新概念的综合管理工具;与此同时,信息化的今天,网络资源的浏览和操作离不开Web技术,它具有信息共享高、易用性、易维护和安全性好的优点,是互联网的主流技术;假如把活动目录和 Web 技术相互结合,必能使网络应用上新台阶。
一、活动目录
活动目录(Active Directory)是面向Windows Standard Server、Windows Enterprise Server以及 Windows Datacenter Server的目录服务。
Active Directory存储了有关网络对象的信息,并且让管理员和用户能够轻松地查找和使用这些信息,使用了一种结构化的数据存储方式,并以此作为基础对目录信息进行合乎逻辑的分层组织。
Active Directory支持标准的LDAP(轻型目录访问协议),它是一种工业标准服务协议,在大多数平台和大多数开发语言中都可以进行访问。
活动目录是存储用户信息、打印机、网络服务和定制数据的目录服务,它是一个树型结构,树枝为组织单元(OU),它可以是国家、公司和组织等,而树叶是对象(CN),它可以是组、用户和网络资源等,组织单元可以包括组织单元和树叶;一个对象可有多个属性,属性包括:姓名、电话号码和单位等。组可包括不同组织单元的多个用户,这些都可实现文件和URL授权的判断条件。
二、TreeView控件
树形图用于显示按照树形结构进行组织的数据,其用途比较广泛,如计算机中的文件系统(Windows中的资源管理器)、企业或公司的组成结构等。在Windows下VB、PB和Delphi等工具提供了一个功能很强的树型控件TreeView,利用Treeview控件可以方便地开发树形图。然而在网页上实现树形图就不那么容易了,现在在ASP.NET中利用微软提供的Internet Explorer WebControls使得网页上的树形图开发与在Windows下一样方便,甚至更灵活。
三、访问活动目录
在.NET中,微软为我们提供了System.DirectoryServices命名空间,它使用了活动目录服务接口(ADSI)。 ADSI是通过编程与许多不同的目录服务提供者交互的方式,也就是一种编程接口。
DirectoryEntry 是 System.DirectoryServices 命名空间中最有用的一个类。它的实例代表活动目录中的对象,DirectorySearcher 主要用于属性的搜索。
通过LDAP协议的方式连接到活动目录中:
DirectoryEntry entry1 = new DirectoryEntry("LDAP://ptr.petrochina/ OU=第四采油厂,OU=大庆油田有限责任公司,OU=大庆油田,DC=ptr,DC=petrochin ", mailuser, passwd, AuthenticationTypes.ServerBind);
其中DC是Domain Component(域组件)的缩写,它只用于表示域的根。OU是Organizational Unit(组织单元)的缩写。OU是容器对象,它主要从逻辑的角度来管理和组织活动目录域。AuthenticationTypes是连接到活动目录的认证方式,这里使用的是AuthenticationTypes.ServerBind的方式。
下面是一个获取某个OU节点下所有的组织单元和用户的例子:
public static void GetAll()
{
DirectoryEntry entry = new DirectoryEntry("domainADsPath");
DirectorySearcher mySearcher = new DirectorySearcher(entry);
// mySearcher.Filter = ("(objectClass=organizationalUnit)");
// 查询条件是所有的组织单元
mySearcher.Filter = "(|(objectClass=user)(objectClass=organizationalUnit))";
mySearcher.PageSize = 20000;
foreach(SearchResult resEnt in mySearcher.FindAll())
{
Response.Write (" <b>Name:</b> "+resEnt.GetDirectoryEntry().Name.ToString());
Response.Write (" <b>Path: </b> +resEnt.GetDirectoryEntry().Path.ToString()+"<br>");
}
}
参数domainADsPath是活动目录的域名,使用类似“LDAP://域名”的形式, mySearcher.Filter是过滤条件,基本上有以下三个选择:
1. objectClass=organizationalUnit 查询条件是所有的组织单元(OU)
2. objectClass=group 查询条件是所有的组(GROUP)
3. objectClass=user 查询条件是所有的用户(USER)
而且mySearcher.Filter支持多种条件的组合,如本文中的例子"(|(objectClass=user)(objectClass=organizationalUnit))"是查询所有的用户和组织单元,注意这个表达式的写法,逻辑运算符是前置的。
mySearcher.PageSize = 20000此参数可以任意设置,但不能不设置,如不设置读取AD数据最多为999条数据,设置后可以读取大于1000条数据。
这样读取出活动目录上某一节点的一些属性,如name(姓名)、 mail(邮箱)、samaccountname(帐号)等信息。把这些信息读出后,可以通过遍历的方式捆绑到Treeview的节点上。
四、Treeview的生成
为了生成Treeview的树形结构,必须再从上面节点读取到的属性中,构造一个可以构造Treeview中父子节点关系的字段,经过测试找到了distinguishedname这个属性。Distinguishedname中存放的是从活动目录根节点到当前节点的组织单元,使用逗号进行分割。可以看出下面例子的节点是一个叫信息中心的组织单元,它的上级节点是第四采油厂。
distinguishedname =
OU=信息中心,OU=第四采油厂,OU=大庆油田有限责任公司,OU=大庆油田,DC=ptr,DC=petrochina
通过构造截取字符串的函数,可以把一个节点的父节点的信息取道,通过一个递归的函数,可以找到这个父节点,这样在遍历活动目录的同时,把当前的节点不断地插入到Treeview中,最后就生成了Treeview的树形结构了。实现的核心代码如下:
// 设置Treeview根节点,就是遍历活动目录开始的起点
// path 是根节点的distinguishedname值
path = "OU=第四采油厂,OU=大庆油田有限责任公司,OU=大庆油田,DC=ptr,DC=petrochina";
string root=path.Split(',')[0].ToString().Substring(3);
TreeNode node1 = new TreeNode();
node1.Text = root;
node1.NodeData=path;
node1.ImageUrl="images/folder.gif";
node1.ExpandedImageUrl="images/folderopen.gif";
ADTree.Nodes.Add(node1); // ADTree 是我们定义的 Treeview
ADTree.ExpandLevel = 1; // 展开树
// 把组织单元和用户节点捆绑到Treeview中
public void GetAll()
{
System.DirectoryServices.DirectoryEntry entry = new System.DirectoryServices.DirectoryEntry("LDAP://ptr.petrochina/" + path, mailuser, passwd, AuthenticationTypes.ServerBind);
System.DirectoryServices.DirectorySearcher mySearcher = new System.DirectoryServices.DirectorySearcher(entry);
mySearcher.Filter = "(|(objectClass=user)(objectClass=organizationalUnit))";
mySearcher.PageSize = 20000;
foreach(System.DirectoryServices.SearchResult resEnt in mySearcher.FindAll())
{
System.DirectoryServices.DirectoryEntry de=resEnt.GetDirectoryEntry();
string test=de.Properties["distinguishedname"].Value.ToString();
string fjdstr=test.Substring(test.Split(',')[0].ToString().Length+1);
TreeNode tmpNd = new TreeNode();
tmpNd.Text=de.Properties["name"].Value.ToString();
// 在NodeData 属性中存放 当前节点的distinguishedname的值
tmpNd.NodeData=test;
// 通过objectcategory属性判断节点是组织单元还是用户,并设置不同的Treeview节点显示图片
if ( de.Properties["objectcategory"].Value.ToString().Split(',')[0].ToString()=="CN=Organizational-Unit" )
{
tmpNd.ImageUrl="images/folder.gif";
tmpNd.ExpandedImageUrl="images/folderopen.gif";
}
else
{
tmpNd.ImageUrl="images/user.gif";
tmpNd.ExpandedImageUrl="images/user.gif";
}
// 递归遍历Treeview找当前节点的父节点,把当前节点插入到Treeview中
try
{
FindNode(ADTree.Nodes[0],fjdstr).Nodes.Add(tmpNd);
}
catch(Exception e)
{
}
}
}
截取当前节点的父节点的方法是,先用Split()函数把distinguishedname属性按逗号分割成几段,test.Split(',')[0].ToString().Length是当前节点第一逗号前的长度,加上后面的逗号,所以开始截取字符串的长度是test.Split(‘,')[0].ToString().Length+1,使用截取字符串函数Substring(),很容易就找到当前节点的父节点了。
在上面的代码中使用了一个寻找父节点的递归函数FindNode(ADTree.Nodes[0],fjdstr),这个函数使用了深度优先算法,其原理是截取 distinguishedname属性的字符串,获取到上级节点的distinguishedname值,通过比较存放在TreeNode节点上NodeData中的distinguishedname值,找到当前节点的父节点,递归函数返回当前节点的父节点,通过Nodes.Add()的方法把当前节点插入到父节点下。
// 查找到父节点的递归函数
private TreeNode FindNode( TreeNode tnParent, string strValue)
{
if( tnParent == null ) return null;
if( tnParent.NodeData.ToString() == strValue ) return tnParent;
TreeNode tnRet = null;
foreach( TreeNode tn in tnParent.Nodes )
{
tnRet = FindNode( tn, strValue);
if( tnRet != null ) break;
}
return tnRet;
}
应用上面的算法,可以得到一个包含组织单元和用户的树型结构,下图是程序运行结果的演示。
五、结语
本文给出了一个利用活动目录节点的属性生成Treeview结构的算法,这个算法利用截取活动目录节点中的distinguishedname属性,获得上层的父节点的distinguishedname属性,然后通过递归的方法找到Treeview中父节点,然后把当前节点都插入到Treeview中。所给出程序在遍历活动目录节点数不多的时候,速度还可以,但遍历一个大型的活动目录,如果有上千个节点的时候,效率就会很低,因为在Treeview中每查找一个节点就要遍历一次整个Treeview。所以在生成Treeview的算法上还应有更高效的方法,比如可以把活动目录节点的属性读出后,把满足生成Treeview的字段放到数据库中,最后由数据库生成Treeview。
|