_principalContext.ValidateCredentials(userName,pass,ContextOptions.SimpleBind);
但是,此方法仅返回bool,我们希望返回更好的消息,甚至将用户重定向到密码重置屏幕,例如:
>用户被锁定在帐户外.
>用户密码已过期.
>用户需要在下次登录时更改密码.
因此,如果用户登录失败,我们调用NetValidatePasswordPolicy来查看用户无法登录的原因.这似乎运行良好但我们意识到这个方法只返回NET_API_STATUS.NERR_PasswordMustChange,无论Active Directory用户的状态是什么.
我发现这个问题的唯一例子来自Sublime Speech插件here.我使用的代码如下:
var outputPointer = IntPtr.Zero; var inputArgs = new NET_VALIDATE_PASSWORD_CHANGE_INPUT_ARG { PasswordMatched = false,UserAccountName = username }; inputArgs.ClearPassword = Marshal.StringToBSTR(password); var inputPointer = IntPtr.Zero; inputPointer = Marshal.AllocHGlobal(Marshal.SizeOf(inputArgs)); Marshal.StructureToPtr(inputArgs,inputPointer,false); using (new ComImpersonator(adImpersonatingUserName,adImpersonatingDomainName,adImpersonatingPassword)) { var status = NetValidatePasswordPolicy(serverName,IntPtr.Zero,NET_VALIDATE_PASSWORD_TYPE.NetValidateAuthentication,ref outputPointer); if (status == NET_API_STATUS.NERR_Success) { var outputArgs = (NET_VALIDATE_OUTPUT_ARG)Marshal.PtrToStructure(outputPointer,typeof(NET_VALIDATE_OUTPUT_ARG)); return outputArgs.ValidationStatus; } else { //fail } }
代码总是成功,那么为什么outputArgs.ValidationStatus的值每次都是相同的结果,无论Active Directory用户的状态如何?
解决方法
>当前的方法论问题
>在线和本主题中推荐解决方案的问题
>解决方案
您方法的当前问题.
NetValidatePasswordPolicy要求其InputArgs参数接受指向结构的指针,并且传入的结构取决于您传入的ValidationType.在这种情况下,您要传递NET_VALIDATE_PASSWORD_TYPE.NetValidateAuthentication,这需要输入NET_VALIDATE_AUTHENTICATION_INPUT_ARG的输入,但是您需要重新传入指向NET_VALIDATE_PASSWORD_CHANGE_INPUT_ARG的指针.
此外,您正在尝试将“currentPassword”类型的值分配给NET_VALIDATE_PASSWORD_CHANGE_INPUT_ARG结构.
但是,使用NetValidatePasswordPolicy存在更大的根本问题,那就是您正在尝试使用此功能来验证Active Directory中的密码,但这不是它的用途. NetValidatePasswordPolicy用于允许应用程序根据应用程序提供的身份验证数据库进行验证.
有关NetValidatePasswordPolicy here的更多信息.
在线和此线程中推荐的解决方案的问题
在线各种文章推荐使用AdvApi32.dll中的logonUser函数,但是这个实现带有一系列问题:
第一个是logonUser对本地缓存进行验证,这意味着除非您使用“网络”模式,否则您无法立即获得有关该帐户的准确信息.
第二个是在Web应用程序上使用logonUser,在我看来有点hacky,因为它是专为在客户端计算机上运行的桌面应用程序而设计的.但是,考虑到Microsoft提供的限制,如果logonUser给出了所需的结果,我不明白为什么不应该使用它 – 除非缓存问题.
logonUser的另一个问题是它对您的用例的效果取决于您的服务器的配置方式,例如:您需要在要验证的域上启用某些特定权限,而这些权限需要在“网络”登录类型工作.
此外,不应使用GetLastError(),而应使用GetLastWin32Error(),因为使用GetLastError()是不安全的.
有关GetLastWin32Error()here的更多信息.
解决方案.
为了从Active Directory获得准确的错误代码,没有任何缓存问题,直接来自目录服务,这是需要做的事情:当帐户出现问题时依赖从AD返回的COMException,因为最终错误是你在寻找什么.
首先,以下是在验证当前用户名和密码时从Active Directory触发错误的方法:
public LdapBindAuthenticationErrors AuthenticateUser(string domain,string username,string password,string ouString) { // The path (ouString) should not include the user in the directory,otherwise this will always return true DirectoryEntry entry = new DirectoryEntry(ouString,username,password); try { // Bind to the native object,this forces authentication. var obj = entry.NativeObject; var search = new DirectorySearcher(entry) { Filter = string.Format("({0}={1})",ActiveDirectoryStringConstants.SamAccountName,username) }; search.PropertiesToLoad.Add("cn"); SearchResult result = search.FindOne(); if (result != null) { return LdapBindAuthenticationErrors.OK; } } catch (DirectoryServicesCOMException c) { LdapBindAuthenticationErrors ldapBindAuthenticationError = -1; // These LDAP bind error codes are found in the "data" piece (string) of the extended error message we are evaluating,so we use regex to pull that string if (Regex.Match(c.ExtendedErrorMessage,@" data (?<ldapBindAuthenticationError>[a-f0-9]+),").Success) { string errorHexadecimal = match.Groups["ldapBindAuthenticationError"].Value; ldapBindAuthenticationError = (LdapBindAuthenticationErrors)Convert.ToInt32(errorHexadecimal,16); return ldapBindAuthenticationError; } catch (Exception e) { throw; } } return LdapBindAuthenticationErrors.ERROR_logoN_FAILURE; }
这些是您的“LdapBindAuthenticationErrors”,您可以在MSDN,here中找到更多信息.
internal enum LdapBindAuthenticationErrors { OK = 0 ERROR_INVALID_PASSWORD = 0x56,ERROR_PASSWORD_RESTRICTION = 0x52D,ERROR_logoN_FAILURE = 0x52e,ERROR_ACCOUNT_RESTRICTION = 0x52f,ERROR_INVALID_logoN_HOURS = 0x530,ERROR_PASSWORD_EXPIRED = 0x532,ERROR_ACCOUNT_DISABLED = 0x533,ERROR_ACCOUNT_EXPIRED = 0x701,ERROR_PASSWORD_MUST_CHANGE = 0x773,ERROR_ACCOUNT_LOCKED_OUT = 0x775 }
然后,您可以使用此枚举的返回类型,并在控制器中执行所需的操作.需要注意的重要一点是,您正在寻找COMException的“扩展错误消息”中字符串的“数据”部分,因为它包含您正在寻找的全能错误代码.
祝你好运,我希望这会有所帮助.我测试了它,它对我很有用.