IISADMPWD が今後新しいバージョンで動作保証しないそうなので作りました。
Active Directory のユーザーが自身のパスワードを変更できるようにします。
実際に運用する際は、別のページから iframe タグで呼び出すのが良いと思います。
なお、http でも動作しますがパスワードが平文で通過することから、https による運用を強くお勧めします。
作成 2013.11.09
更新 2017.05.12
更新 2017.05.12
IIS 8.5 で Active Directory ユーザーアカウントのパスワード変更
動作環境
- Windows Server 2012 R2 + IIS 8.5
役割サービスの IIS の項目の「ASP.NET 4.5」を有効にするだけで使用可能になります。 - Windows Server 2008 R2 + IIS 7.5
事前に .Net Framework 4 をインストールした状態で、IIS の「ASP.NET」を有効にすると使用可能になります。
もし、IIS インストール後に .Net Framework 4 をインストールした場合は、aspnet_regiis -i を実行することで使用可能になります。実行ファイルは C:\Windows\Microsoft.NET\Framework64\v4.0.30319 フォルダにあります。
コード
- 10行目
bool debugMode = false; の部分を true へ変更すると途中の値の状態が確認できます。 - 77行目
ユーザーアカウントの検索は sAMAccountName と cn のOR条件となっています。そのため Active Directory に日本語ユーザー名で登録している場合は、名前 + パスワードでも通過します。ログオンIDのみとしたい場合やメールアドレスで認証したい場合はこの行を書き換えてください。 - 107行目
ユーザーのパスワードが有効期限切れになっている場合、もしくは「ユーザーは次回ログオン時にパスワード変更が必要」がオンになっている場合は、認証失敗しパスワードを変更できません。パスワードを変更できるようにするには、この行を管理者アカウントとパスワードに差し替えます。 - その他、web.config の作成や書き換えは不要です。
<%@ PAGE LANGUAGE="C#" %>
<%@ Assembly Name="System.DirectoryServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" %>
<%@ Assembly Name="System.DirectoryServices.AccountManagement, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" %>
<%@ Import Namespace="System.DirectoryServices" %>
<%@ Import Namespace="System.DirectoryServices.AccountManagement" %>
<html>
<head>
<title>AD User Change Password</title>
<script runat="server">
bool debugMode = false;
void Page_Load(object sender, EventArgs e)
{
if(debugMode){ lbl_debug.Text = "Debug Mode: ON"; }
old_password.TextMode = TextBoxMode.Password;
new_password.TextMode = TextBoxMode.Password;
new_password2.TextMode = TextBoxMode.Password;
}
void password_change(object sender, EventArgs e)
{
String ldapBaseDN = "";
String dNSDomainName = "";
String nTDomainName = "";
String samName = "";
String userName = user_name.Text;
String searchText = "";
String ldapUserPath = "";
lbl_change_result.Text = "";
lbl_system_result.Text = "";
lbl_base_dn.Text = "";
lbl_dns_domain.Text = "";
lbl_nt_domain.Text = "";
lbl_user_path.Text = "";
lbl_sam.Text = "";
if(userName.Length == 0){
lbl_system_result.Text = "ユーザー名が指定されていません。";
return;
}
if(String.Compare(new_password.Text, new_password2.Text) != 0){
lbl_system_result.Text = "新しいパスワードが一致していません。";
return;
}
try
{
DirectoryEntry domain = new DirectoryEntry("LDAP://RootDSE");
ldapBaseDN = domain.Properties["defaultNamingContext"][0].ToString();
if(debugMode){ lbl_base_dn.Text = " LDAP Root DN: " + ldapBaseDN; }
}
catch(Exception ex1)
{
if(debugMode){
lbl_system_result.Text = ex1.Message;
}else{
lbl_system_result.Text = "このサーバーはドメインに参加していないようです。";
}
return;
}
System.Text.RegularExpressions.Regex r = new System.Text.RegularExpressions.Regex("=([^,]+),?");
System.Text.RegularExpressions.Match m = r.Match(ldapBaseDN);
if (m.Success)
{
dNSDomainName = m.Groups[1].ToString();
m = m.NextMatch();
while (m.Success)
{
dNSDomainName = dNSDomainName + "." + m.Groups[1].ToString();
m = m.NextMatch();
}
}
if(debugMode){ lbl_dns_domain.Text = " DNS Domain: " + dNSDomainName; }
nTDomainName = Environment.GetEnvironmentVariable("USERDOMAIN");
if(debugMode){ lbl_nt_domain.Text = " NT Domain: " + nTDomainName; }
searchText = "(&(objectClass=user)(!objectClass=computer)(|(sAMAccountName=" + userName + ")(cn=" + userName + ")))";
try
{
using (DirectorySearcher ds = new DirectorySearcher(searchText))
{
SearchResult sr = ds.FindOne();
if(sr == null)
{
if(debugMode){ lbl_system_result.Text = "ユーザー名が見つかりません。"; }
else { lbl_system_result.Text = "ユーザー名またはパスワードが違います。"; }
return;
}
else
{
ldapUserPath = sr.Path;
if(debugMode){ lbl_user_path.Text = " LDAP User Path: " + ldapUserPath; }
samName = sr.Properties["sAMAccountName"][0].ToString();
if(debugMode){ lbl_sam.Text = " sAMAccountName: " + samName; }
}
}
}
catch(ArgumentException ae)
{
lbl_system_result.Text = ae.Message;
return;
}
try
{
using(PrincipalContext ctx = new PrincipalContext(ContextType.Domain,
dNSDomainName, nTDomainName + @"\" + samName, old_password.Text))
using(UserPrincipal usr = UserPrincipal.FindByIdentity(ctx, samName))
{
usr.ChangePassword(old_password.Text, new_password.Text);
if(debugMode){ lbl_change_result.Text = "パスワードを変更しました。"; }
else{
Response.ClearContent();
Response.Write("<html><head></head><body>パスワードを変更しました。</body></html>");
Response.Flush();
Response.Close();
}
}
}
catch(PasswordException pe)
{
if(debugMode){ lbl_system_result.Text = pe.Message; }
else { lbl_system_result.Text = "ユーザー名またはパスワードが違います。"; }
return;
}
catch(Exception ex2)
{
if(debugMode){ lbl_system_result.Text = ex2.Message; }
else { lbl_system_result.Text = "ユーザー名またはパスワードが違います。"; }
return;
}
}
</script>
</head>
<body>
<form runat="server">
<table>
<tr><td>ユーザー名</td><td><asp:TextBox id="user_name" Columns="50" Rows="1" Text="" runat="server" /></td></tr>
<tr><td>現在のパスワード</td><td><asp:TextBox id="old_password" Columns="50" Rows="1" Text="" runat="server" /></td></tr>
<tr><td>新しいパスワード</td><td><asp:TextBox id="new_password" Columns="50" Rows="1" Text="" runat="server" /></td></tr>
<tr><td>確認入力</td><td><asp:TextBox id="new_password2" Columns="50" Rows="1" Text="" runat="server" /></td></tr>
</table>
<asp:Button id="btn_change" Text="Change" OnClick="password_change" runat="server" />
<asp:Label style="color:#080;font-weight:bold;margin-left:1em" id="lbl_change_result" Text="" runat="server" />
<asp:Label style="color:#f00;font-weight:bold;margin-left:1em" id="lbl_system_result" Text="" runat="server" />
<!-- ここから下はデバッグ情報 -->
<asp:Label id="lbl_debug" Text="" runat="server" />
<asp:Label id="lbl_base_dn" Text="" runat="server" />
<asp:Label id="lbl_nt_domain" Text="" runat="server" />
<asp:Label id="lbl_dns_domain" Text="" runat="server" />
<asp:Label id="lbl_user_path" Text="" runat="server" />
<asp:Label id="lbl_sam" Text="" runat="server" />
</form>
</body>
</html>
編集例
ログオンIDのみ認証可能にする
変更前 77行目
searchText = "(&(objectClass=user)(!objectClass=computer)(|(sAMAccountName=" + userName + ")(cn=" + userName + ")))";変更後 77行目
searchText = "(&(objectClass=user)(!objectClass=computer)(sAMAccountName=" + userName + "))";
パスワードの有効期限切れでも変更可能にする
パスワードの有効期限切れまたは「ユーザーは次回ログオン時にパスワード変更が必要」がオンの場合でも変更可能になります。
変更後は、「ユーザーは次回ログオン時にパスワード変更が必要」はオフに変わります。
変更前 106-107行目
変更後は、「ユーザーは次回ログオン時にパスワード変更が必要」はオフに変わります。
変更前 106-107行目
using(PrincipalContext ctx = new PrincipalContext(ContextType.Domain,
dNSDomainName, nTDomainName + @"\" + samName, old_password.Text))
変更後 106-107行目
using(PrincipalContext ctx = new PrincipalContext(ContextType.Domain,
dNSDomainName, nTDomainName + @"\Administrator", @"管理者パスワード"))
管理者パスワードを外部ファイルに保存する場合
変更可能なOUを限定する
ds.SearchRoot の行を追加します。
変更前 80-83行目
変更前 80-83行目
using (DirectorySearcher ds = new DirectorySearcher(searchText))
{
SearchResult sr = ds.FindOne();
if(sr == null)
変更後 80-84行目
using (DirectorySearcher ds = new DirectorySearcher(searchText))
{
ds.SearchRoot = new DirectoryEntry(@"LDAP://OU=testuser,DC=test,DC=lan");
SearchResult sr = ds.FindOne();
if(sr == null)
SearchRoot のパスを調べるには以下のコマンドをドメイン コントローラーで実行します。
C:\> dsquery ou -name testuser OU=testuser,DC=test,DC=lan