ASP.NET MVC AD認証ログイン
注意
このポストはまだ成功確認が取れていない状態のメモです。 環境が整い次第テストを行う予定で、そのときのための準備になります。
AD認証でのログインについて調べる
Visual StudioのテンプレートではUseOpenIdConnectAuthenticationにより、OpenIdを利用したADログインになるため、 やりたいことの参考が出来なかったため、調べた事をメモする。
MVC Web ApplicationをMVCで認証なしで作成する。
プロジェクトにNugetから追加する。
参照にPrincipalContextを使うためのライブラリを追加
- System.DirectoryServices
- System.DirectoryServices.AccountManagement
User
IUserを継承したユーザを作成する。 ただし、生成にはUserPrincpalから取得するようにする。
using Microsoft.AspNet.Identity; using System.DirectoryServices.AccountManagement; using System.Threading.Tasks; public class AppUser : IUser<string> { private UserPrincipal _adUser; public AppUser(UserPrincipal adUser) { this._adUser = adUser; } public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<AppUser> manager) { var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie); return userIdentity; } #region IUser<string> Members public string Id => _adUser.SamAccountName; public string UserName { get => _adUser.SamAccountName; set { throw new System.NotImplementedException(); } } #endregion }
UserStore
ユーザ関連のCLUD処理を行うStoreクラスを定義する。 PrincipalContextからFindByIdentityにより、ユーザの取得を行う。
using Microsoft.AspNet.Identity; using System.DirectoryServices.AccountManagement; using System.Threading.Tasks; public class AppUserStore : IUserStore<AppUser>, IUserStore<AppUser, string> { private readonly PrincipalContext _context; private AppUserStore(PrincipalContext context) { _context = context; } // context.Get<PrincipalContext>()でADのcontextを取得 public static AppUserStore Create(IdentityFactoryOptions<AppUserStore> options, IOwinContext context) { // PrincipalContextをOwinContextから取得するようにする。StartUpでCreatePerOwinContextに追加しておく //var principalContext = new PrincipalContext(ContextType.Domain); var principalContext = context.Get<PrincipalContext>(); return new AppUserStore(principalContext); } #region IUserStore<MyUser, string> Members public Task CreateAsync(AppUser user) { throw new NotImplementedException(); } public Task DeleteAsync(AppUser user) { throw new NotImplementedException(); } public void Dispose() { throw new NotImplementedException(); } // UserPrincipal.FindByIdentityにより、検索する public Task<AppUser> FindByIdAsync(string userId) { var user = UserPrincipal.n(_context, userId); return Task.FromResult<AppUser>(new AppUser(user)); } public Task<AppUser> FindByNameAsync(string userName) { var user = UserPrincipal.FindByIdentity(_context, userName); return Task.FromResult<AppUser>(new AppUser(user)); } public Task UpdateAsync(AppUser user) { throw new NotImplementedException(); } #endregion }
UserMananger
UserStoreクラスの総裁を行うクラス
using Microsoft.AspNet.Identity; using System.DirectoryServices.AccountManagement; using System.Threading.Tasks; public class AppUserMananger : UserManager<AppUser> { private readonly PrincipalContext _context; private AppUserMananger(IUserStore<AppUser> store, PrincipalContext context) : base(store) { _context = context; } public static AppUserMananger Create(IdentityFactoryOptions<AppUserMananger> options, IOwinContext context) { var userStore = context.Get<AppUserStore>(); var principalContext = context.Get<PrincipalContext>(); return new AppUserMananger(userStore, principalContext); } public override async Task<bool> CheckPasswordAsync(AppUser user, string password) { return await Task.FromResult(_context.ValidateCredentials(user.UserName, password, ContextOptions.Negotiate)); } }
SignInManager
ログイン状態を管理するクラス
using Microsoft.AspNet.Identity; using System.DirectoryServices.AccountManagement; using System.Threading.Tasks; public class AppSignInManager : SignInManager<AppUser, string> { UserManager<AppUser, string> _manager; private AppSignInManager(UserManager<AppUser, string> userManager, IAuthenticationManager authenticationManager) : base(userManager, authenticationManager) { _manager = UserManager; } public static AppSignInManager Create(IdentityFactoryOptions<AppSignInManager> options, IOwinContext context) { // UserManagerはOwinContextから取得する。 AppUserMananger manager = context.GetUserManager<AppUserMananger>(); return new AppSignInManager(manager, context.Authentication); } public override Task<ClaimsIdentity> CreateUserIdentityAsync(AppUser user) { return user.GenerateUserIdentityAsync((AppUserMananger)UserManager); } public override async Task<SignInStatus> PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout) { var result = SignInStatus.Failure; try { var user = await this.UserManager.FindAsync(userName, password); if (user != null) { await this.SignInAsync(user, isPersistent, true); result = SignInStatus.Success; } } catch { result = SignInStatus.Failure; } return result; } }
StartUp
using Owin; using Microsoft.Owin.Security; using Microsoft.Owin.Security.Cookies; using Microsoft.AspNet.Identity; using System.Threading.Tasks; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using System.DirectoryServices.AccountManagement; using System.Security.Claims; [assembly: OwinStartup(typeof(MvcWeb.OwinStartUp))] public class OwinStartUp { public void Configuration(IAppBuilder app) { app.CreatePerOwinContext(() => new PrincipalContext(ContextType.Domain)); app.CreatePerOwinContext<AppUserMananger>(AppUserMananger.Create); app.CreatePerOwinContext<AppSignInManager>(AppSignInManager.Create); app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType); app.UseCookieAuthentication(new CookieAuthenticationOptions() { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login"), Provider = new CookieAuthenticationProvider { OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<AppUserMananger, AppUser>( validateInterval: TimeSpan.FromMinutes(30), regenerateIdentity: (manager,user) => user.GenerateUserIdentityAsync(manager)) } }); } }
LoginViewModel
public class LoginViewModel { [Required] [Display(Name = "ユーザID")] public string UserID { get; set; } [Required] [DataType(DataType.Password)] [Display(Name = "パスワード")] public string Password { get; set; } [Display(Name = "このアカウントを記憶する")] public bool RememberMe { get; set; } }
AccountController
http://localhost/Acount/LoginからADのユーザ名、パスワードでログインする処理
[Authorize] public class AccountController : Controller { // // GET: /Account/Login [AllowAnonymous] public ActionResult Login(string returnUrl) { ViewBag.ReturnUrl = returnUrl; return View(); } // // POST: /Account/Login [HttpPost] [AllowAnonymous] public async Task<ActionResult> Login(LoginViewModel model, string returnUrl) { if (!ModelState.IsValid) { return View(model); } AppSignInManager signInManager = HttpContext.GetOwinContext().Get<AppSignInManager>(); AppUserMananger userManager = HttpContext.GetOwinContext().GetUserManager<AppUserMananger>(); // これは、アカウント ロックアウトの基準となるログイン失敗回数を数えません。 // パスワード入力失敗回数に基づいてアカウントがロックアウトされるように設定するには、shouldLockout: true に変更してください。 var result = await signInManager.PasswordSignInAsync(model.UserID, model.Password, model.RememberMe, shouldLockout: false); switch (result) { case SignInStatus.Success: return RedirectToLocal(returnUrl); case SignInStatus.LockedOut: return View("Lockout"); case SignInStatus.RequiresVerification: return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe }); case SignInStatus.Failure: default: ModelState.AddModelError("", "無効なログイン試行です。"); return View(model); } } private ActionResult RedirectToLocal(string returnUrl) { if (Url.IsLocalUrl(returnUrl)) { return Redirect(returnUrl); } return RedirectToAction("Index", "Home"); } }
Login View
@using MvcWeb.Models @model LoginViewModel @{ ViewBag.Title = "ログイン"; } <h2>@ViewBag.Title.</h2> <div class="row"> <div class="col-md-8"> <section id="loginForm"> @using (Html.BeginForm("Login", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" })) { @Html.AntiForgeryToken() <h4>ローカル アカウントを使用してログインします。</h4> <hr /> @Html.ValidationSummary(true, "", new { @class = "text-danger" }) <div class="form-group"> @Html.LabelFor(m => m.UserID, new { @class = "col-md-2 control-label" }) <div class="col-md-10"> @Html.TextBoxFor(m => m.UserID, new { @class = "form-control" }) @Html.ValidationMessageFor(m => m.UserID, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" }) <div class="col-md-10"> @Html.PasswordFor(m => m.Password, new { @class = "form-control" }) @Html.ValidationMessageFor(m => m.Password, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <div class="checkbox"> @Html.CheckBoxFor(m => m.RememberMe) @Html.LabelFor(m => m.RememberMe) </div> </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="ログイン" class="btn btn-default" /> </div> </div> <p> @Html.ActionLink("新しいユーザーとして登録する", "Register") </p> @* これを有効にする前に、パスワード リセット機能に対するアカウント確認を有効にしてください。 <p> @Html.ActionLink("パスワードを忘れた場合", "ForgotPassword") </p>*@ } </section> </div> <div class="col-md-4"> <section id="socialLoginForm"> @Html.Partial("_ExternalLoginsListPartial", new ExternalLoginListViewModel { ReturnUrl = ViewBag.ReturnUrl }) </section> </div> </div> @section Scripts { @Scripts.Render("~/bundles/jqueryval") }