Validate user credentials against domain controller in .net

匿名 (未验证) 提交于 2019-12-03 08:30:34

问题:

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.

回答1:

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.



回答2:

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


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!