简体   繁体   中英

Validate user credentials against domain controller in .net

in an .NET application, I'm trying to authenticate users by username and password a against windows users, local ones as well as domain users. I already tried this solution . My code to get the PrincipalContext looks the following:

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;
    }
}

That works fine for local windows users users and for remote users in an ActiveDirectory. But if I try to run the authentication on a machine, that is joined to a non-ActiveDirectory Domain Master, eg. a Samba Server I get the following Exception:

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)

So it seems that the PrincipalContext tries to use LDAP in case of ContextType.Domain. If I try to use ContextType.Machine I have cannot use the workgroup/domain-name as PrincipalContext tries to connect directly to the machine. That fails if there is already a connection to that machine with that windows from the same machine.

So my question is:

  • How to authenticate a user with the credentials domain, username and password against a domain master, which is not necessarily based on an ActiveDirectory?
  • Are there managed APIs to accomplish the above described task?
  • If there are no managed foundation-classes, what is the right direction to do that with?

Thank you for your replies.

For the sake of completeness, here my solution which seems to do exactly what I want:

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
    }
}

The LogonType and LogonProvider enums reflect the definitions in "Winbase.h". I settled with LogonType.LOGON32_LOGON_BATCH instead of LogonType.LOGON32_LOGON_NETWORK because samba 3.4.X seems to have trouble with this type.

Here is one that I just did for an app I'm working on myself - requires Framework v3.5 or greater....

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);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM