可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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); } }