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 の作成や書き換えは不要です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | <%@ 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行目
変更後 77行目
1 | searchText = "(&(objectClass=user)(!objectClass=computer)(|(sAMAccountName=" + userName + ")(cn=" + userName + ")))"; |
1 | searchText = "(&(objectClass=user)(!objectClass=computer)(sAMAccountName=" + userName + "))"; |
パスワードの有効期限切れでも変更可能にする
パスワードの有効期限切れまたは「ユーザーは次回ログオン時にパスワード変更が必要」がオンの場合でも変更可能になります。
変更後は、「ユーザーは次回ログオン時にパスワード変更が必要」はオフに変わります。
変更前 106-107行目
変更後 106-107行目
管理者パスワードを外部ファイルに保存する場合
変更後は、「ユーザーは次回ログオン時にパスワード変更が必要」はオフに変わります。
変更前 106-107行目
1 2 | using(PrincipalContext ctx = new PrincipalContext(ContextType.Domain, dNSDomainName, nTDomainName + @"\" + samName, old_password.Text)) |
1 2 | using(PrincipalContext ctx = new PrincipalContext(ContextType.Domain, dNSDomainName, nTDomainName + @"\Administrator", @"管理者パスワード")) |
変更可能なOUを限定する
ds.SearchRoot の行を追加します。
変更前 80-83行目
変更後 80-84行目
SearchRoot のパスを調べるには以下のコマンドをドメイン コントローラーで実行します。
変更前 80-83行目
1 2 3 4 | using (DirectorySearcher ds = new DirectorySearcher(searchText)) { SearchResult sr = ds.FindOne(); if(sr == null) |
1 2 3 4 5 | using (DirectorySearcher ds = new DirectorySearcher(searchText)) { ds.SearchRoot = new DirectoryEntry(@"LDAP://OU=testuser,DC=test,DC=lan"); SearchResult sr = ds.FindOne(); if(sr == null) |
1 2 | C:\> dsquery ou -name testuser OU=testuser,DC=test,DC=lan |