Câu trả lời:
Nếu bạn làm việc trên .NET 3.5 trở lên, bạn có thể sử dụng System.DirectoryServices.AccountManagement
không gian tên và dễ dàng xác minh thông tin đăng nhập của bạn:
// create a "principal context" - e.g. your domain (could be machine, too)
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "YOURDOMAIN"))
{
// validate the credentials
bool isValid = pc.ValidateCredentials("myuser", "mypassword");
}
Thật đơn giản, đáng tin cậy, đó là mã được quản lý 100% C # của bạn - bạn còn đòi hỏi gì hơn nữa? :-)
Đọc tất cả về nó ở đây:
Cập nhật:
Như đã nêu trong câu hỏi SO khác này (và câu trả lời của nó) , có một vấn đề với cuộc gọi này có thể trả về True
mật khẩu cũ của người dùng. Chỉ cần lưu ý về hành vi này và đừng quá ngạc nhiên nếu điều này xảy ra :-) (cảm ơn @MikeGledhill đã chỉ ra điều này!)
UserPrinciple.FindByIdentity
để xem liệu ID người dùng đã qua có tồn tại trước hay không.
ContextOptions.Negotiate
.
Chúng tôi làm điều này trên mạng nội bộ của chúng tôi
Bạn phải sử dụng System.DirectoryService;
Đây là can đảm của mã
using (DirectoryEntry adsEntry = new DirectoryEntry(path, strAccountId, strPassword))
{
using (DirectorySearcher adsSearcher = new DirectorySearcher(adsEntry))
{
//adsSearcher.Filter = "(&(objectClass=user)(objectCategory=person))";
adsSearcher.Filter = "(sAMAccountName=" + strAccountId + ")";
try
{
SearchResult adsSearchResult = adsSearcher.FindOne();
bSucceeded = true;
strAuthenticatedBy = "Active Directory";
strError = "User has been authenticated by Active Directory.";
}
catch (Exception ex)
{
// Failed to authenticate. Most likely it is caused by unknown user
// id or bad strPassword.
strError = ex.Message;
}
finally
{
adsEntry.Close();
}
}
}
strPassword
là được lưu trữ trong LDAP trong văn bản thuần túy?
Close()
về một using
biến.
Một số giải pháp được trình bày ở đây thiếu khả năng phân biệt giữa người dùng / mật khẩu sai và mật khẩu cần được thay đổi. Điều đó có thể được thực hiện theo cách sau:
using System;
using System.DirectoryServices.Protocols;
using System.Net;
namespace ProtocolTest
{
class Program
{
static void Main(string[] args)
{
try
{
LdapConnection connection = new LdapConnection("ldap.fabrikam.com");
NetworkCredential credential = new NetworkCredential("user", "password");
connection.Credential = credential;
connection.Bind();
Console.WriteLine("logged in");
}
catch (LdapException lexc)
{
String error = lexc.ServerErrorMessage;
Console.WriteLine(lexc);
}
catch (Exception exc)
{
Console.WriteLine(exc);
}
}
}
}
Nếu mật khẩu người dùng sai hoặc người dùng không tồn tại, lỗi sẽ chứa
"8009030C: LdapErr: DSID-0C0904DC, nhận xét: AcceptSecurityContext lỗi, dữ liệu 52e, v1db1",
nếu mật khẩu người dùng cần thay đổi, nó sẽ chứa
"8009030C: LdapErr: DSID-0C0904DC, nhận xét: AcceptSecurityContext lỗi, dữ liệu 773, v1db1"
Các lexc.ServerErrorMessage
giá trị dữ liệu là một đại diện hex của Error Code Win32. Đây là các mã lỗi tương tự sẽ được trả về bằng cách gọi lệnh gọi Win32 LogonUser API. Danh sách dưới đây tóm tắt một loạt các giá trị phổ biến với các giá trị thập phân và thập phân:
525 user not found (1317)
52e invalid credentials (1326)
530 not permitted to logon at this time (1328)
531 not permitted to logon at this workstation (1329)
532 password expired (1330)
533 account disabled (1331)
701 account expired (1793)
773 user must reset password (1907)
775 user account locked (1909)
System.DirectoryServices
vàSystem.DirectoryServices.Protocols
giải pháp rất đơn giản bằng cách sử dụng DirectoryService:
using System.DirectoryServices;
//srvr = ldap server, e.g. LDAP://domain.com
//usr = user name
//pwd = user password
public bool IsAuthenticated(string srvr, string usr, string pwd)
{
bool authenticated = false;
try
{
DirectoryEntry entry = new DirectoryEntry(srvr, usr, pwd);
object nativeObject = entry.NativeObject;
authenticated = true;
}
catch (DirectoryServicesCOMException cex)
{
//not authenticated; reason why is in cex
}
catch (Exception ex)
{
//not authenticated due to some other exception [this is optional]
}
return authenticated;
}
cần có quyền truy cập NativeObject để phát hiện người dùng / mật khẩu xấu
PrincipleContext
- chỉ tồn tại trong .NET 3.5. Nhưng nếu bạn đang sử dụng .NET 3.5 hoặc mới hơn, bạn nên sử dụngPrincipleContext
Thật không may, không có cách "đơn giản" nào để kiểm tra thông tin đăng nhập của người dùng trên AD.
Với mọi phương thức được trình bày cho đến nay, bạn có thể nhận được âm tính giả: Tín dụng của người dùng sẽ hợp lệ, tuy nhiên AD sẽ trả về sai trong một số trường hợp:
ActiveDirectory sẽ không cho phép bạn sử dụng LDAP để xác định xem mật khẩu không hợp lệ do người dùng phải thay đổi mật khẩu hoặc nếu mật khẩu của họ đã hết hạn.
Để xác định thay đổi mật khẩu hoặc mật khẩu đã hết hạn, bạn có thể gọi Win32: LogonUser () và kiểm tra mã lỗi của windows cho 2 hằng số sau:
Có lẽ cách dễ nhất là PInvoke LogonUser Win32 API.eg
Tham khảo MSDN tại đây ...
Chắc chắn muốn sử dụng loại đăng nhập
LOGON32_LOGON_NETWORK (3)
Điều này chỉ tạo ra một mã thông báo nhẹ - hoàn hảo cho kiểm tra AuthN. (các loại khác có thể được sử dụng để xây dựng các phiên tương tác, v.v.)
LogonUser
API yêu cầu người dùng phải có luật như là một phần của hệ điều hành privelage; đó không phải là thứ mà người dùng nhận được - và không phải thứ bạn muốn cấp cho mọi người dùng trong tổ chức. ( msdn.microsoft.com/en-us/l Library / aa378184 ( v = vs85 ) .aspx )
Một giải pháp .Net đầy đủ là sử dụng các lớp từ không gian tên System.DirectoryService. Họ cho phép truy vấn trực tiếp một máy chủ AD. Đây là một mẫu nhỏ sẽ làm điều này:
using (DirectoryEntry entry = new DirectoryEntry())
{
entry.Username = "here goes the username you want to validate";
entry.Password = "here goes the password";
DirectorySearcher searcher = new DirectorySearcher(entry);
searcher.Filter = "(objectclass=user)";
try
{
searcher.FindOne();
}
catch (COMException ex)
{
if (ex.ErrorCode == -2147023570)
{
// Login or password is incorrect
}
}
}
// FindOne() didn't throw, the credentials are correct
Mã này kết nối trực tiếp với máy chủ AD, sử dụng thông tin đăng nhập được cung cấp. Nếu thông tin đăng nhập không hợp lệ, searcher.FindOne () sẽ đưa ra một ngoại lệ. ErrorCode là một lỗi tương ứng với lỗi COM "tên người dùng / mật khẩu không hợp lệ".
Bạn không cần phải chạy mã với tư cách là người dùng AD. Trên thực tế, tôi thành công sử dụng nó để truy vấn thông tin trên máy chủ AD, từ một khách hàng bên ngoài miền!
Một cuộc gọi .NET khác để nhanh chóng xác thực thông tin đăng nhập LDAP:
using System.DirectoryServices;
using(var DE = new DirectoryEntry(path, username, password)
{
try
{
DE.RefreshCache(); // This will force credentials validation
}
catch (COMException ex)
{
// Validation failed - handle how you want
}
}
Hãy thử mã này (LƯU Ý: Được báo cáo là không hoạt động trên máy chủ windows 2000)
#region NTLogonUser
#region Direct OS LogonUser Code
[DllImport( "advapi32.dll")]
private static extern bool LogonUser(String lpszUsername,
String lpszDomain, String lpszPassword, int dwLogonType,
int dwLogonProvider, out int phToken);
[DllImport("Kernel32.dll")]
private static extern int GetLastError();
public static bool LogOnXP(String sDomain, String sUser, String sPassword)
{
int token1, ret;
int attmpts = 0;
bool LoggedOn = false;
while (!LoggedOn && attmpts < 2)
{
LoggedOn= LogonUser(sUser, sDomain, sPassword, 3, 0, out token1);
if (LoggedOn) return (true);
else
{
switch (ret = GetLastError())
{
case (126): ;
if (attmpts++ > 2)
throw new LogonException(
"Specified module could not be found. error code: " +
ret.ToString());
break;
case (1314):
throw new LogonException(
"Specified module could not be found. error code: " +
ret.ToString());
case (1326):
// edited out based on comment
// throw new LogonException(
// "Unknown user name or bad password.");
return false;
default:
throw new LogonException(
"Unexpected Logon Failure. Contact Administrator");
}
}
}
return(false);
}
#endregion Direct Logon Code
#endregion NTLogonUser
ngoại trừ bạn sẽ cần tạo ngoại lệ tùy chỉnh của riêng mình cho "LogonException"
Nếu bạn bị mắc kẹt với .NET 2.0 và mã được quản lý, đây là một cách khác hoạt động với tài khoản miền và tài khoản miền:
using System;
using System.Collections.Generic;
using System.Text;
using System.Security;
using System.Diagnostics;
static public bool Validate(string domain, string username, string password)
{
try
{
Process proc = new Process();
proc.StartInfo = new ProcessStartInfo()
{
FileName = "no_matter.xyz",
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
RedirectStandardInput = true,
LoadUserProfile = true,
Domain = String.IsNullOrEmpty(domain) ? "" : domain,
UserName = username,
Password = Credentials.ToSecureString(password)
};
proc.Start();
proc.WaitForExit();
}
catch (System.ComponentModel.Win32Exception ex)
{
switch (ex.NativeErrorCode)
{
case 1326: return false;
case 2: return true;
default: throw ex;
}
}
catch (Exception ex)
{
throw ex;
}
return false;
}
Xác thực Windows có thể không thành công vì nhiều lý do: tên người dùng hoặc mật khẩu không chính xác, tài khoản bị khóa, mật khẩu đã hết hạn và hơn thế nữa. Để phân biệt giữa các lỗi này, hãy gọi hàm API LogonUser qua P / Gọi và kiểm tra mã lỗi nếu hàm trả về false
:
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
public static class Win32Authentication
{
private class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private SafeTokenHandle() // called by P/Invoke
: base(true)
{
}
protected override bool ReleaseHandle()
{
return CloseHandle(this.handle);
}
}
private enum LogonType : uint
{
Network = 3, // LOGON32_LOGON_NETWORK
}
private enum LogonProvider : uint
{
WinNT50 = 3, // LOGON32_PROVIDER_WINNT50
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr handle);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool LogonUser(
string userName, string domain, string password,
LogonType logonType, LogonProvider logonProvider,
out SafeTokenHandle token);
public static void AuthenticateUser(string userName, string password)
{
string domain = null;
string[] parts = userName.Split('\\');
if (parts.Length == 2)
{
domain = parts[0];
userName = parts[1];
}
SafeTokenHandle token;
if (LogonUser(userName, domain, password, LogonType.Network, LogonProvider.WinNT50, out token))
token.Dispose();
else
throw new Win32Exception(); // calls Marshal.GetLastWin32Error()
}
}
Sử dụng mẫu:
try
{
Win32Authentication.AuthenticateUser("EXAMPLE\\user", "P@ssw0rd");
// Or: Win32Authentication.AuthenticateUser("user@example.com", "P@ssw0rd");
}
catch (Win32Exception ex)
{
switch (ex.NativeErrorCode)
{
case 1326: // ERROR_LOGON_FAILURE (incorrect user name or password)
// ...
case 1327: // ERROR_ACCOUNT_RESTRICTION
// ...
case 1330: // ERROR_PASSWORD_EXPIRED
// ...
case 1331: // ERROR_ACCOUNT_DISABLED
// ...
case 1907: // ERROR_PASSWORD_MUST_CHANGE
// ...
case 1909: // ERROR_ACCOUNT_LOCKED_OUT
// ...
default: // Other
break;
}
}
Lưu ý: LogonUser yêu cầu mối quan hệ tin cậy với tên miền bạn đang xác thực.
Chức năng đơn giản của tôi
private bool IsValidActiveDirectoryUser(string activeDirectoryServerDomain, string username, string password)
{
try
{
DirectoryEntry de = new DirectoryEntry("LDAP://" + activeDirectoryServerDomain, username + "@" + activeDirectoryServerDomain, password, AuthenticationTypes.Secure);
DirectorySearcher ds = new DirectorySearcher(de);
ds.FindOne();
return true;
}
catch //(Exception ex)
{
return false;
}
}
Đây là giải pháp xác thực hoàn chỉnh của tôi để bạn tham khảo.
Đầu tiên, thêm bốn tài liệu tham khảo sau
using System.DirectoryServices;
using System.DirectoryServices.Protocols;
using System.DirectoryServices.AccountManagement;
using System.Net;
private void AuthUser() {
try{
string Uid = "USER_NAME";
string Pass = "PASSWORD";
if (Uid == "")
{
MessageBox.Show("Username cannot be null");
}
else if (Pass == "")
{
MessageBox.Show("Password cannot be null");
}
else
{
LdapConnection connection = new LdapConnection("YOUR DOMAIN");
NetworkCredential credential = new NetworkCredential(Uid, Pass);
connection.Credential = credential;
connection.Bind();
// after authenticate Loading user details to data table
PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
UserPrincipal user = UserPrincipal.FindByIdentity(ctx, Uid);
DirectoryEntry up_User = (DirectoryEntry)user.GetUnderlyingObject();
DirectorySearcher deSearch = new DirectorySearcher(up_User);
SearchResultCollection results = deSearch.FindAll();
ResultPropertyCollection rpc = results[0].Properties;
DataTable dt = new DataTable();
DataRow toInsert = dt.NewRow();
dt.Rows.InsertAt(toInsert, 0);
foreach (string rp in rpc.PropertyNames)
{
if (rpc[rp][0].ToString() != "System.Byte[]")
{
dt.Columns.Add(rp.ToString(), typeof(System.String));
foreach (DataRow row in dt.Rows)
{
row[rp.ToString()] = rpc[rp][0].ToString();
}
}
}
//You can load data to grid view and see for reference only
dataGridView1.DataSource = dt;
}
} //Error Handling part
catch (LdapException lexc)
{
String error = lexc.ServerErrorMessage;
string pp = error.Substring(76, 4);
string ppp = pp.Trim();
if ("52e" == ppp)
{
MessageBox.Show("Invalid Username or password, contact ADA Team");
}
if ("775" == ppp)
{
MessageBox.Show("User account locked, contact ADA Team");
}
if ("525" == ppp)
{
MessageBox.Show("User not found, contact ADA Team");
}
if ("530" == ppp)
{
MessageBox.Show("Not permitted to logon at this time, contact ADA Team");
}
if ("531" == ppp)
{
MessageBox.Show("Not permitted to logon at this workstation, contact ADA Team");
}
if ("532" == ppp)
{
MessageBox.Show("Password expired, contact ADA Team");
}
if ("533" == ppp)
{
MessageBox.Show("Account disabled, contact ADA Team");
}
if ("533" == ppp)
{
MessageBox.Show("Account disabled, contact ADA Team");
}
} //common error handling
catch (Exception exc)
{
MessageBox.Show("Invalid Username or password, contact ADA Team");
}
finally {
tbUID.Text = "";
tbPass.Text = "";
}
}