在 .NET 中获取 AD 上帐号密码过期时间 【转】

转载 2005年04月29日 20:00:00

原文:http://www.blogcn.com/User8/flier_lu/blog/4371854.html

    .NET Framework 提供了 System.DirectoryServices 名字空间用于操作 AD 等支持 LDAP 接口的服务器,通过这组类我们能够很容易实现通过 AD 验证用户帐号,以及向 AD 查询域用户及其所在组的信息,是在 Web 应用中集成 AD 以实现企业单点登陆的重要手段之一。
    纯朴的狗熊 在其 blog 上有一系列非常出色的文章介绍了这方面的基本知识

    活动目录.NET编程Tips
    使用System.DirectoryServices.Protocols实现对AD的简单操作
    ADHelper 活动目录用户操作类

    虽然他给出的那个例子代码并不完整,但为后来者提供了很好的基础。

    为了让笔者所在公司的基于 SharePoint 的内网门户能够提供一些方便的小功能,如查询自己帐号的密码过期时间等等,笔者对其封装代码做了一些修改,定义了 AdServer/AdGroup/AdUser 分别用于对 AD 服务器/组/用户的封装,让关系更加清晰。回头等封装代码稳定了,再写篇文章详细介绍。

    其中碰到一个讨厌的问题是如何从 AD 获取当前帐号的密码过期时间。对基于域的用户来说,通过 ADSI 接口的 WINNT:// 协议,可以简单的从 IADsUser::PasswordExpirationDate 获得这一信息;但对于 AD 的 LDAP:// 协议接口,这个字段并不存在,需要我们手工从帐号最后登陆时间 (pwdLastSet) 和用户所在域的帐号过期时间 (maxPwdAge) 自行计算。

    ADSI 接口的 User 对象 schema 中定义了这些常用的属性

    User Object Properties

    微软 MSDN 中也专门有一篇文章详细介绍了如何进行这种计算

    How Long Until My Password Expires?

    其核心算法步骤如下:

    1.帐号是否被禁用
    2.帐号密码是否被设置过
    3.帐号所在域是否有密码期限设置
    4.计算密码期限设置的天数
    5.计算密码过期的时间

    算法流程图如下:

    

    对 VBScript 来说只需要一段简单的代码就可以完成任务

On   Error   Resume   Next

Const  ADS_UF_DONT_EXPIRE_PASSWD  =   & h10000
Const  E_ADS_PROPERTY_NOT_FOUND   =   & h8000500D
Const  ONE_HUNDRED_NANOSECOND     =  . 000000100
Const  SECONDS_IN_DAY             =   86400

Set  objADSystemInfo  =   CreateObject ( " ADSystemInfo " )               '  LINE 8
Set  objUser  =   GetObject ( " LDAP:// "   &  objADSystemInfo.UserName)    '  LINE 9

intUserAccountControl 
=  objUser. Get ( " userAccountControl " )
If  intUserAccountControl  And  ADS_UF_DONT_EXPIRE_PASSWD  Then
    WScript.Echo 
" The password does not expire."
    WScript.Quit
Else
    dtmValue 
=  objUser.PasswordLastChanged
    
If  Err.Number  =  E_ADS_PROPERTY_NOT_FOUND  Then
        WScript.Echo 
" The password has never been set."
        WScript.Quit
    
Else
        intTimeInterval 
=   Int ( Now   -  dtmValue)
        WScript.Echo 
" The password was last set on  "   &  _
          
DateValue (dtmValue)  &   "  at  "   &   TimeValue (dtmValue)   &  vbCrLf  &  _
          
" The difference between when the password was last "   &  vbCrLf  &  _
          
" set and today is  "   &  intTimeInterval  &   "  days"
     End   If

    
Set  objDomain  =   GetObject ( " LDAP:// "   &  objADSystemInfo.DomainDNSName)
    
Set  objMaxPwdAge  =  objDomain. Get ( " maxPwdAge " )

    
If  objMaxPwdAge.LowPart  =   0   Then
        WScript.Echo 
" The Maximum Password Age is set to 0 in the  "   &  _
                     
" domain. Therefore, the password does not expire."
        WScript.Quit
    
Else
        dblMaxPwdNano 
=  _
            
Abs (objMaxPwdAge.HighPart  *   2 ^ 32   +  objMaxPwdAge.LowPart)
        dblMaxPwdSecs 
=  dblMaxPwdNano  *  ONE_HUNDRED_NANOSECOND
        dblMaxPwdDays 
=   Int (dblMaxPwdSecs  /  SECONDS_IN_DAY)
        WScript.Echo 
" Maximum password age is  "   &  dblMaxPwdDays  &   "  days"

         If  intTimeInterval  >=  dblMaxPwdDays  Then
            WScript.Echo 
" The password has expired."
         Else
            WScript.Echo 
" The password will expire on  "   &  _
              
DateValue (dtmValue  +  dblMaxPwdDays)  &   "  ( "   &  _
              
Int ((dtmValue  +  dblMaxPwdDays)  -   Now &   "  days from today)."
         End   If
    
End   If
End   If

    但因为 .NET v1.x 中活动目录的简陋封装,使得在 .NET 中要实现上述功能相对较为繁琐。

    首先需要通过 AdUser 对象封装的 DirectoryEntry 的属性获得 userAccountControl 字段的值,并判断是否设置了密码永不过期的标志:

   public   class  AdUser : AdItem
  
{
    
public   enum  ADS_USER_FLAG_ENUM
    
{
      
      ADS_UF_DONT_EXPIRE_PASSWD 
=   0X10000 ,
      
    }


    
public   int  UserAccountControl
    
{
      
get
      
{
        
return  Convert.ToInt32(Properties[ " userAccountControl " ][ 0 ]);
      }

    }


    
public   bool  IsPasswordNotExpire
    
{
      
get
      
{
        
return  (UserAccountControl  &  ( int )ADS_USER_FLAG_ENUM.ADS_UF_DONT_EXPIRE_PASSWD)  !=   0 ;
      }

    }

  }


    然后需要访问密码最后被重置的时间,判断此帐号是否被使用过。

    这里需要注意的是,密码最后重置时间 (pwdLastSet) 和域密码过期时间 (maxPwdAge) 等字段在 AD 中是 INTEGER8 类型。虽然理论上对应于 C# 中的 long,但通过 System.DirectoryServices 并不能直接访问之。也就是说对于这些 INTEGER8 类型的字段,通过 Convert.ToInt64(Properties["pwdLastSet"][0]) 这样的强制转换调用会直接抛出异常。
    要访问这种字段,必须显式通过 ADSI 规范中的 IADsLargeInteger 接口,手工进行转换。.NET 247  上的一篇文章里面介绍了这个问题的解决方法

    DirectoryEntry __ComObject use.

    而这个例子中的转换代码还可能出现溢出问题,需要小心处理

    Problem with the HighPart and LowPart Property Methods

    完整的转换代码如下:

public   abstract   class  AdEntry : IDisposable
{
  
//  在 .NET 中访问 INTEGER8 类型必须通过 IADsLargeInteger 接口
  
//   http://www.dotnet247.com/247reference/msgs/31/159934.aspx
  [ComImport]
  [Guid(
" 9068270B-0939-11D1-8BE1-00C04FD8D503 " )]
  [InterfaceType(ComInterfaceType.InterfaceIsDual)]
  
internal   interface  IADsLargeInteger
  
{
    [DispId(
0x00000002 )]  int  HighPart { get set ;}
    [DispId(
0x00000003 )]  int  LowPart { get set ;}
  }


  
internal   long  GetLongValue(IADsLargeInteger value)
  
{
    
//  将 IADsLargeInteger 内容转换为 long 之前必须小心溢出
    
//   http://www.rlmueller.net/Integer8Discussion.htm
     return  ( long )((( ulong )value.HighPart  <<   32 +  ( ulong )value.LowPart);
  }

}


    只有解决了这诸多问题,才能将上面那一小段 VBScript 代码真正移植到 .NET 下:

public   class  AdUser : AdItem
  
{
    
//   http://msdn.microsoft.com/library/en-us/dnclinic/html/scripting09102002.asp
     public  DateTime PasswordExpirationDate
    
{
      
get
      
{
        
if (IsPasswordNotExpire)
        
{
          
return  DateTime.MaxValue;  //  帐号被设置为密码永不过期
        }

        
else
        
{
          
long  lastChanged;

          
try
          
{
            lastChanged 
=  GetLongValue((IADsLargeInteger)Properties[ " pwdLastSet " ][ 0 ]);
          }

          
catch (Exception)
          
{
            
return  DateTime.MinValue;  //  密码没有被设置过
          }


          IADsLargeInteger maxAge 
=  (IADsLargeInteger)Server.Properties[ " maxPwdAge " ][ 0 ];

          
if (maxAge.LowPart  ==   0 )
            
return  DateTime.MaxValue;  //  域中密码没有设置最大有效期限
           else
            
return  PasswordLastChanged.AddDays(Server.MaxPasswordDays);
        }

      }

    }

  }


    虽然是个小问题,可里面的阻力一点都不小。  希望如 纯朴的狗熊  所说微软会在下个版本里面真正认真对待目录服务这块企业级应用必备的领域。

在 .NET 中获取 AD 上帐号密码过期时间

原文:http://www.blogcn.com/User8/flier_lu/blog/4371854.html    .NET Framework 提供了 System.DirectoryServ...
  • flier_lu
  • flier_lu
  • 2005年01月31日 13:03
  • 2067

AD账号密码过期邮件提醒-powershell实现--密码加密(新增)

#leader的需求是,AD密码过期15天之前通过邮件通知用户,告知还有多少天密码就要过期了 #由于不是特别熟悉powershell,所以会写的比较笨,不过功能是实现了 #我们设置的是用户密码90天就...
  • zxm425
  • zxm425
  • 2017年12月06日 17:49
  • 292

NET 修改域账户过期密码

采用常规的AD域账号修改密码,去修改过期密码是行不通的 测试半天,都把域控里面的账号过期误解为密码过期了,所以一直 在网上找不到把域账户设置为永不过期的资料 其实只需要处理密码过期问题就好 域账户密...
  • qq873113580
  • qq873113580
  • 2017年08月01日 16:26
  • 176

Change expired password in AD with C# (使用c# 更改AD 中过期密码)

Question: Hello,Im looking for a solution to completely replace iisadmpwd with a pure ASP.NET 2.0 so...
  • xhinker
  • xhinker
  • 2009年01月12日 17:30
  • 1464

Asp.net读取AD域信息的方法

using System; using System.Collections.Generic; using System.Linq; using System.Text; using Syst...
  • jsjpanxiaoyu
  • jsjpanxiaoyu
  • 2016年10月24日 10:00
  • 1726

linux设置密码过期时间

密码过期了,帐户还能用吗?可不可以把密码过期时间变更为帐户过期时间呢? chage -E 2010-10-10 username 刚开始的时候,我认为密码过期相当于帐户停用,但试了一下才明白,密码...
  • ppby2002
  • ppby2002
  • 2011年09月16日 13:06
  • 3883

AD10 导出生产文件【过孔盖油】【gerber文件和位号图】【修改标号相对于元件的位置】【输出gerber的时候提示the film is too small for this pc】【坐标文件】

输出gerber文件 本节部分内容摘录于:http://blog.sina.com.cn/s/blog_9b9a51990100zyyv.html...
  • chengdong1314
  • chengdong1314
  • 2016年09月20日 10:30
  • 5339

SQL Server 密码过期

原文地址 ALTER LOGIN sa WITH CHECK_POLICY = OFF – 把密码策略关掉就行 ALTER LOGIN sa WITH PASSWORD = ‘password’ u...
  • qq_26981913
  • qq_26981913
  • 2016年12月15日 14:14
  • 744

如何查看AD域账号的删除记录

**如何查看AD域账号删除记录及恢复** 在日常AD域管理中,有时候我们不小心删除了域账号,或者我们想查看这个域账号是什么时候创建并删除的,那怎么办?是否可以恢复?其实微软本身已经为我们的账号信息做了...
  • Chingfai
  • Chingfai
  • 2017年11月15日 13:52
  • 643

.net下时间与时间戳之间的转换

.net下时间与时间戳之间的转换
  • rznice
  • rznice
  • 2017年07月20日 12:03
  • 1783
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:在 .NET 中获取 AD 上帐号密码过期时间 【转】
举报原因:
原因补充:

(最多只允许输入30个字)