在.net中针对域控制器验证用户凭据

时间:2012-01-11 07:29:03

标签: c# .net windows winapi authentication

在.NET应用程序中,我正在尝试通过用户名和密码对Windows用户,本地用户以及域用户进行身份验证。 I already tried this solution 。获取PrincipalContext的代码如下所示:

protected static PrincipalContext TryCreatePrincipalContext(String domain)
{
    var computerDomain = TryGetComputerDomain();

    if (String.IsNullOrEmpty(domain) && String.IsNullOrEmpty(computerDomain))
        return new PrincipalContext(ContextType.Machine);
    else if (String.IsNullOrEmpty(domain))
        return new PrincipalContext(ContextType.Domain, computerDomain);
    else
        return new PrincipalContext(ContextType.Domain, domain);
}

protected static String TryGetComputerDomain()
{
    try
    {
        var domain = Domain.GetComputerDomain();
        return domain.Name;
    } catch
    {
       return null;
    }
}

适用于本地Windows用户用户和ActiveDirectory中的远程用户。但是,如果我尝试在计算机上运行身份验证,则会加入非ActiveDirectory域主服务器,例如。 Samba服务器我得到以下例外:

System.DirectoryServices.AccountManagement.PrincipalServerDownException: Mit dem Server konnte keine Verbindung hergestellt werden. ---> 
System.DirectoryServices.Protocols.LdapException: Der LDAP-Server ist nicht verfügbar.
bei System.DirectoryServices.Protocols.LdapConnection.Connect()
bei System.DirectoryServices.Protocols.LdapConnection.SendRequestHelper(DirectoryRequest request, Int32& messageID)
bei System.DirectoryServices.Protocols.LdapConnection.SendRequest(DirectoryRequest request, TimeSpan requestTimeout)
bei System.DirectoryServices.Protocols.LdapConnection.SendRequest(DirectoryRequest request)
bei System.DirectoryServices.AccountManagement.PrincipalContext.ReadServerConfig(String serverName, ServerProperties& properties)
--- Ende der internen Ausnahmestapelüberwachung ---
bei System.DirectoryServices.AccountManagement.PrincipalContext.ReadServerConfig(String serverName, ServerProperties& properties)
bei System.DirectoryServices.AccountManagement.PrincipalContext.DoServerVerifyAndPropRetrieval()
bei System.DirectoryServices.AccountManagement.PrincipalContext..ctor(ContextType contextType, String name, String container, ContextOptions options, String userName, String password)
bei System.DirectoryServices.AccountManagement.PrincipalContext..ctor(ContextType contextType, String name)
bei DomainAuthTest.DomainAuthenticator.TryCreatePrincipalContext(String domain)
bei DomainAuthTest.DomainAuthenticator.Authenticate(String domainUser, String  password)
bei DomainAuthTest.Program.Main(String[] args)

因此,似乎PrincipalContext尝试在ContextType.Domain的情况下使用LDAP。如果我尝试使用ContextType.Machine,我无法使用workgroup / domain-name,因为PrincipalContext尝试直接连接到计算机。如果已经有来自同一台机器的那台机器连接到那台机器,那就失败了。

所以我的问题是:

  • 如何使用域主服务器的凭据域,用户名和密码对用户进行身份验证,而域主服务器不一定基于ActiveDirectory?
  • 是否有托管API来完成上述任务?
  • 如果没有托管的基础课程,那么正确的方向是什么?

感谢您的回复。

2 个答案:

答案 0 :(得分:4)

为了完整起见,我的解决方案似乎完全符合我的要求:

public class WinApiDomainAuthenticator
{
    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool LogonUser(string lpszUsername,
                                        string lpszDomain,
                                        string lpszPassword,
                                        int dwLogonType,
                                        int dwLogonProvider,
                                        out IntPtr phToken);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    public extern static bool CloseHandle(IntPtr handle);

    public static IPrincipal Authenticate(String domainUser, String password)
    {
        var userToken = IntPtr.Zero;
        var creds = new DomainAuthCredentials(domainUser, password); 

        if (! LogonUser(creds.Username, 
                        creds.Domain,
                        creds.Password,
                       (int)LogonType.LOGON32_LOGON_BATCH, 
                       (int)LogonProvider.LOGON32_PROVIDER_DEFAULT, out userToken))
        {
            var error = new Win32Exception(Marshal.GetLastWin32Error());
            throw new SecurityException("Error while authenticating user", error);
        }

        var identity = new WindowsIdentity(userToken);

        if (userToken != IntPtr.Zero) 
            CloseHandle(userToken);

        return ConvertWindowsIdentityToGenericPrincipal(identity);
    }

    protected static IPrincipal ConvertWindowsIdentityToGenericPrincipal(WindowsIdentity windowsIdentity)
    {
        if (windowsIdentity == null)
            return null;

        // Identity in format DOMAIN\Username
        var identity = new GenericIdentity(windowsIdentity.Name);

        var groupNames = new string[0];
        if (windowsIdentity.Groups != null)
        {
            // Array of Group-Names in format DOMAIN\Group
            groupNames = windowsIdentity.Groups
                                        .Select(gId => gId.Translate(typeof(NTAccount)))
                                        .Select(gNt => gNt.ToString())
                                        .ToArray();
        }

        var genericPrincipal = new GenericPrincipal(identity, groupNames);
        return genericPrincipal;
    }

    protected class DomainAuthCredentials
    {
        public DomainAuthCredentials(String domainUser, String password)
        {
            Username = domainUser;
            Password = password;
            Domain = ".";

            if (!domainUser.Contains(@"\"))
                return;

            var tokens = domainUser.Split(new char[] { '\\' }, 2);
            Domain = tokens[0];
            Username = tokens[1];
        }

        public DomainAuthCredentials()
        {
            Domain = String.Empty;
        }

        #region Properties

        public String Domain { get; set; }
        public String Username { get; set; }
        public String Password { get; set; }

        #endregion
    }
}

LogonType和LogonProvider枚举反映了“Winbase.h”中的定义。我决定使用LogonType.LOGON32_LOGON_BATCH而不是LogonType.LOGON32_LOGON_NETWORK,因为samba 3.4.X似乎遇到了这种类型的问题。

答案 1 :(得分:2)

以下是我刚为自己正在开发的应用所做的一项 - 需要Framework v3.5或更高版本....

public static bool Authenticate(string user, string password)
{
    // Split the user name in case a domain name was specified as DOMAIN\USER
    string[] NamesArray = user.Split(new char[] { '\\' }, 2);

    // Default vars for names & principal context type
    string DomainName = string.Empty;
    string UserName = string.Empty;
    ContextType TypeValue = ContextType.Domain;

    // Domain name was supplied
    if (NamesArray.Length > 1)
    {
        DomainName = NamesArray[0];
        UserName = NamesArray[1];
    }
    else
    {
        // Pull domain name from environment
        DomainName = Environment.UserDomainName;
        UserName = user;

        // Check this against the machine name to pick up on a workgroup
        if (string.Compare(DomainName, System.Environment.MachineName, StringComparison.InvariantCultureIgnoreCase) == 0)
        {
            // Use the domain name as machine name (local user)
            TypeValue = ContextType.Machine;
        }
    }

    // Create the temp context
    using (PrincipalContext ContextObject = new PrincipalContext(TypeValue, DomainName))
    {
        // Validate the credentials
        return ContextObject.ValidateCredentials(UserName, password);
    }
}