回答:
.NET 3.5以降で作業している場合は、System.DirectoryServices.AccountManagement
名前空間を使用して、資格情報を簡単に確認できます。
// 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");
}
それはシンプルで、信頼性が高く、100%C#で管理されたコードです-これ以上何を要求できますか?:-)
それについてここですべて読んでください:
更新:
この他のSOの質問(およびその回答)で概説されているように、この呼び出しにTrue
は、ユーザーの古いパスワードを返す可能性があるという問題があります。この動作に注意してください。これが発生しても驚かないでください:-)(これを指摘してくれた@MikeGledhillに感謝します!)
UserPrinciple.FindByIdentity
て、渡されたユーザーIDが最初に存在するかどうかを確認することができます。
ContextOptions.Negotiate
。
これはイントラネットで行います
System.DirectoryServicesを使用する必要があります。
ここにコードの要点があります
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
、プレーンテキストでLDAPに保存されることを意味しますか?
Close()
にusing
変数。
ここで紹介するいくつかのソリューションには、間違ったユーザー/パスワードと、変更が必要なパスワードを区別する機能がありません。これは次の方法で実行できます。
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);
}
}
}
}
ユーザーのパスワードが間違っている場合、またはユーザーが存在しない場合は、エラーに
"8009030C:LdapErr:DSID-0C0904DC、コメント:AcceptSecurityContextエラー、データ52e、v1db1"、
ユーザーのパスワードを変更する必要がある場合は、パスワードが含まれます
"8009030C:LdapErr:DSID-0C0904DC、コメント:AcceptSecurityContextエラー、データ773、v1db1"
lexc.ServerErrorMessage
データ値は、Win32エラーコードの16進数表現です。これらは、Win32 LogonUser API呼び出しを呼び出さないと返されるエラーコードと同じです。以下のリストは、16進数と10進数の値を持つ一般的な値の範囲をまとめたものです。
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
およびSystem.DirectoryServices.Protocols
DirectoryServicesを使用した非常にシンプルなソリューション:
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;
}
不正なユーザー/パスワードを検出するにはNativeObjectアクセスが必要です
PrincipleContext
です。ただし、.NET 3.5以降を使用している場合は、使用する必要がありますPrincipleContext
残念ながら、ADでユーザーの資格情報を確認する「簡単な」方法はありません。
これまでに紹介したすべての方法では、偽陰性になる可能性があります。ユーザーの資格情報は有効ですが、特定の状況ではADは偽を返します。
ActiveDirectoryでは、LDAPを使用して、ユーザーがパスワードを変更する必要があるため、またはパスワードの有効期限が切れているためにパスワードが無効であるかどうかを判断できません。
パスワードの変更または期限切れのパスワードを確認するには、Win32:LogonUser()を呼び出し、Windowsエラーコードで次の2つの定数を確認します。
おそらく最も簡単な方法は、LogInUser Win32 API.egをPInvokeすることです。
MSDNリファレンスはこちら...
間違いなくログオンタイプを使いたい
LOGON32_LOGON_NETWORK (3)
これにより、軽量トークンのみが作成されます-AuthNチェックに最適です。(他のタイプを使用してインタラクティブセッションなどを構築できます)
LogonUser
APIは持っているユーザーを必要とオペレーティングシステムの一部として機能を privelage。これはユーザーが取得するものではなく、組織内のすべてのユーザーに付与するものではありません。(msdn.microsoft.com/en-us/library/aa378184(v=vs.85).aspx)
完全な.Netソリューションは、System.DirectoryServices名前空間のクラスを使用することです。ADサーバーに直接クエリを実行できます。これを行う小さなサンプルを次に示します。
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
このコードは、提供された資格情報を使用して、ADサーバーに直接接続します。資格情報が無効な場合、searcher.FindOne()は例外をスローします。ErrorCodeは、 "無効なユーザー名/パスワード" COMエラーに対応するものです。
ADユーザーとしてコードを実行する必要はありません。実際、私はドメイン外のクライアントからADサーバーの情報を照会するためにそれをうまく使用しています!
LDAP資格情報をすばやく認証するためのさらに別の.NET呼び出し:
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
}
}
このコードを試してください(注:Windows Server 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
ただし、「LogonException」に対して独自のカスタム例外を作成する必要があります。
.NET 2.0とマネージコードに悩まされている場合は、ローカルアカウントとドメインアカウントで機能する別の方法を次に示します。
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;
}
Windows認証は、さまざまな理由で失敗する可能性があります:正しくないユーザー名またはパスワード、ロックされたアカウント、期限切れのパスワードなど。これらのエラーを区別するには、P / Invokeを介してLogonUser API関数を呼び出し、関数が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()
}
}
使用例:
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;
}
}
注:LogonUserには、検証対象のドメインとの信頼関係が必要です。
私のシンプルな機能
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;
}
}
ここに、参照用の完全な認証ソリューションを示します。
まず、次の4つの参照を追加します
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 = "";
}
}