ADSI を使用する System.DirectoryServices 名前空間のDirectoryEntry クラスの Invoke メソッドを使用してパスワードを変更するサンプルを掲載します。また、System.DirectoryServices.Protocols 名前空間のクラスを使用して、ADSI経由ではない方法でLDAP に接続詞パスワードを設定または変更するサンプルを掲載します。
動作環境は次の通り
- 動作環境 Windows 2003 Server (ActiveDirectory構成)
- 開発環境 Visual Studio 2008 Professional
- .NET 3.5 (.NET 2.0以上で動作するはずです)
動作環境のドメイン機能レベルはWindows 2003 となっています。
1. System.DirectoryServices名前空間のクラスを使用してパスワードを管理する
ADSI 経由でADのユーザのパスワードを管理するサンプルを掲載します。サンプルでは、SetPassword と ChangePassword を使用します。
SetPassword はアクティブディレクトリの操作でパスワードをクリアする処理に該当します。引数に、設定するパスワードを指定します。 ChangePasswordはパスワードを変更する処理に該当します。引数に旧新パスワードの順で指定します。それぞれのメソッドはDiretoryEntry.Invoke メソッドを使用して呼び出します。
/// <summary>
/// 動作テストでは、パスワードのポリシーを以下のようにセット
/// パスワードの長さ 0文字以上
/// パスワードの変更禁止期間 0日
/// パスワードの有効期限 0日
/// パスワードの履歴を記録する 0回
/// パスワードは複雑さの要件を満たす必要がある 無効
/// 暗号化を元に戻せる状態でパスワードを保存
///
/// 関係あるかわかりませんが、ドメインの機能レベルはWindows 2003
/// </summary>
public static void SetPasswordChangePassword()
{
using (DirectoryEntry crmuser = GetDirectoryEntry("CN=crmuser03,OU=CrmUsers,DC=crm1,DC=local"))
{
try
{
// SetPassword
crmuser.Invoke("SetPassword", "p@ssw0rd001");
// ChangePassword
crmuser.Invoke("ChangePassword", "p@ssw0rd001", "p@ssw0rd002");
}
catch (TargetInvocationException tiex)
{
// COMExceptionがスローされる場合は、パスワード要件が満たされるか
// を確認すること。
throw tiex.InnerException;
}
}
}
private static DirectoryEntry GetDirectoryEntry(string path)
{
path = "LDAP://" + path;
return new DirectoryEntry(path, null, null, AuthenticationTypes.Secure);
}
プログラムのコメントにも記載していますが、動作しいない場合は、グループポリシーにパスワードの強度が違反していないかを確認して下さい。サンプルでは、ドメインcrm1.localの組織CrmUsersに存在する相対名がcrmuser03というユーザのパスワードをクリアする処理と、変更する処理を実行しています。
2. System.DirectoryServices.Protocols 名前空間のクラスを使用してパスワードを管理する
LDAPに接続してパスワードを管理する方法を掲載します。この方法では、ケルベロスを使用するかSSLを使用して通信を暗号化する必要があることに注意してください。ADAMの場合は、セキュリティーレベルを下げれば、プレーンテキストを使用してパスワードを変更できるようです。詳細は、DirectoryEntryConfiguration クラスを調べてください。
本サンプルでは、Kerberos認証を使用して通信を暗号化し、ADのユーザのパスワードを変更します。SSLを使用した場合の変更点はプログラムにコメントとして記載してあります。
/// <summary>
/// System.DirectoryServices.Protocols名前空間のクラスを
/// 使用した場合のSetPassword, ChangePassword
///
/// ドメイン crm1.local にLDAP接続し、パスワードを
/// 変更する。SSLを使用した場合の変更点もコメントで設定
/// </summary>
public static void SetPasswordChangePasswordBySDSP()
{
try
{
// LDAPに接続
// 接続するドメイン(サーバ名は省略可), LDAP/SSLの場合はcrm1.local:636
string host = "crm1.local";
using (LdapConnection connection = new LdapConnection(host))
{
// ケルベロスを使用する
connection.SessionOptions.Sealing = true;
// SSLを使用する場合はSecureSocketLayerをtrueにする
// connection.SessionOptions.SecureSocketLayer = true;
connection.Bind(CredentialCache.DefaultNetworkCredentials);
// パスワードのリセット
SetPassword(connection, "P@ssw0rd003");
// パスワードの変更
ChangePassword(connection, "P@ssw0rd003", "P@ssw0rd001");
}
}
catch (Exception)
{
throw;
}
}
/// <summary>
/// パスワードを変更する
/// </summary>
/// <param name="connection"></param>
/// <param name="oldPassword"></param>
/// <param name="newPassword"></param>
private static void ChangePassword(DirectoryConnection connection, string oldPassword, string newPassword)
{
// 変更するディレクトリオブジェクトのパス
string path = "CN=crmuser02,OU=CrmUsers,DC=crm1,DC=local";
DirectoryAttributeModification passwordDeleteModification = new DirectoryAttributeModification();
passwordDeleteModification.Name = "unicodePwd";
passwordDeleteModification.Add(ConvertUnicodeBytes(oldPassword));
passwordDeleteModification.Operation = DirectoryAttributeOperation.Delete;
DirectoryAttributeModification passwordAddModification = new DirectoryAttributeModification();
passwordAddModification.Name = "unicodePwd";
passwordAddModification.Add(ConvertUnicodeBytes(newPassword));
passwordAddModification.Operation = DirectoryAttributeOperation.Add;
ModifyRequest request = new ModifyRequest(path, passwordDeleteModification, passwordAddModification);
DirectoryResponse response = connection.SendRequest(request);
}
/// <summary>
/// パスワードをリセットする
/// </summary>
/// <param name="connection"></param>
/// <param name="password"></param>
private static void SetPassword(DirectoryConnection connection, string password)
{
// 変更するディレクトリオブジェクトのパス
string path = "CN=crmuser02,OU=CrmUsers,DC=crm1,DC=local";
DirectoryAttributeModification passwordReplaceModification = new DirectoryAttributeModification();
passwordReplaceModification.Name = "unicodePwd";
passwordReplaceModification.Add(ConvertUnicodeBytes(password));
passwordReplaceModification.Operation = DirectoryAttributeOperation.Replace;
ModifyRequest request = new ModifyRequest(path, passwordReplaceModification);
DirectoryResponse response = connection.SendRequest(request);
}
/// <summary>
/// パスワードをダブルクォーテーションで囲んだ文字を
/// Unicodeでエンコードする
/// </summary>
/// <param name="password">パスワードの文字列</param>
/// <returns>ユニコードエンコードされたバイト配列</returns>
private static byte[] ConvertUnicodeBytes(string password)
{
string dquotedPassword = string.Format("\"{0}\"", password);
return Encoding.Unicode.GetBytes(dquotedPassword);
}
少し長いですが、SetPasswordChangePasswordBySDSP メソッドの先頭でLDAP接続コネクションオブジェクトを作成しています。資格情報には現在の実行ユーザの資格情報を使用しています。SetPassword と ChangePassword メソッドがそれぞれ、パスワードのリセット、パスワードの変更を行うメソッドで、作成したDirectoryConnectionオブジェクトを使用します。
パスワードの設定は、属性値unicodePwdを置換することで行います。ConvertUnicodeBytesはパスワードを unicodePwd 用にバイト配列に変換するメソッドです。パスワードの変更処理は属性値unicodePwd を削除、追加することで行います。
3. おわり
説明は以上です。指摘点、誤り等がありましたらご指摘ください。プログラムがうまく動作しない場合は、権限とパスワードのポリシーに違反していないかを確認してみるとよいと思います。
ネットワークセグメントを跨ぐとユーザー登録ができず、ずっとハマっていました。
最初はMSDNを参考にPrincipalを使用するコードを書いていましたが、ネットワークセグメントを越えると全く動作せず、DirectoryEntryを使用するコードに全て書き直しました。
それでもパスワード変更ができず途方に暮れていた処にこのページを発見しました。
このページに書かれているポリシーに加え、ADSI EditでRolesの確認をして、無事パスワードを変更することができました。
ありがとうございました。
あほ丸出しコメントすいませんでした・・・よく見ればよかったです
失礼しました。