ASP.Net MVC Identity 認証
ASP.NET MVC プロジェクトを作成
認証を選択するとデフォルトの認証ロジックが貼るため、認証なしで作成する。
プロジェクトにNugetから追加する。
AppUser
UserNameとPasswordのみの簡単なものにします。 適当なフォルダーに作成します。
public class AppUser : IUser<string> { public string Id { get; set; } = Guid.NewGuid().ToString(); public string UserName { get; set; } public string Password { get; set; } }
UserManager
UserStoreクラスを制御するラッピングクラス
Create(IdentityFactoryOptions, IOwinContext)を実装して、インスタンス生成時に呼ばれる様にしている。 現状としてはどんな仕組みで呼び出し時にCreateでパラメータを渡さずに動いているのかがさっぱりわからない。
using Microsoft.AspNet.Identity; public class AppUserManager : UserManager<AppUser> { private AppUserManager(IUserStore<AppUser> store) : base(store) { } public static AppUserMananger Create(IdentityFactoryOptions<AppUserMananger> options, IOwinContext context) { return new AppUserMananger(new AppUserStore()); } }
IRole
public class AppRole : IRole<string> { public string Id { get; set; } = Guid.NewGuid().ToString(); public string Name { get; set ; } }
RoleMananager
using Microsoft.AspNet.Identity; public class AppRoleManager : RoleManager<AppRole> { public AppRoleManager(IRoleStore<AppRole, string> store) : base(store) { } }
SignInManager
using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Microsoft.Owin.Security; public class AppSignInManager : SignInManager<AppUser, string> { private AppSignInManager(UserManager<AppUser, string> userManager, IAuthenticationManager authenticationManager) : base(userManager, authenticationManager) { } public static AppSignInManager Create(IdentityFactoryOptions<AppSignInManager> options, IOwinContext context) { AppUserMananger manager = context.GetUserManager<AppUserMananger>(); return new AppSignInManager(manager, context.Authentication); } }
UserStore
ユーザー情報を扱うためのクラス(CRUD処理)
public class AppUserStore : IUserStore<AppUser>, IUserStore<AppUser,string>, IUserPasswordStore<AppUser,string>, IUserRoleStore<AppUser, string>, IRoleStore<AppRole, string> { // DBの代わりにダミーデータ設定 // ユーザマスタ private static List<AppUser> Users { get; } = new List<AppUser> { new AppUser { Id = "1", UserName = "user1", Password="abc" }, new AppUser { Id = "2", UserName = "user2", Password="def" } }; // ロールマスタ private static List<AppRole> Roles { get; } = new List<AppRole> { new AppRole { Id = "1", Name = "emploryee"}, new AppRole { Id = "999", Name = "admin" } }; // ユーザー・ロールマスタ private static List<Tuple<string, string>> UserRoleMap { get; } = new List<Tuple<string, string>> { Tuple.Create("1", "1"), Tuple.Create("2","999") }; public Task AddToRoleAsync(AppUser user, string roleName) { var role = Roles.FirstOrDefault(x => x.Name == roleName); if (role == null) { throw new InvalidOperationException(); } var userRoleMap = UserRoleMap.FirstOrDefault(x => x.Item1 == user.Id && x.Item2 == role.Id); if (userRoleMap == null) { UserRoleMap.Add(Tuple.Create(user.Id, role.Id)); } return Task.Delay(0); } public Task CreateAsync(AppUser user) { Users.Add(user); return Task.Delay(0); } public Task CreateAsync(AppRole role) { Roles.Add(role); return Task.Delay(0); } public Task DeleteAsync(AppUser user) { Users.Remove(Users.First(x => x.Id == user.Id)); return Task.Delay(0); } public Task DeleteAsync(AppRole role) { Roles.Remove(Roles.First(x => x.Id == role.Id)); return Task.Delay(0); } public void Dispose() { } public Task<AppUser> FindByIdAsync(string userId) { return Task.FromResult(Users.FirstOrDefault(u => u.Id == userId)); } public Task<AppUser> FindByNameAsync(string userName) { return Task.FromResult(Users.FirstOrDefault(u => u.UserName == userName)); } public Task<string> GetPasswordHashAsync(AppUser user) { return Task.FromResult(new PasswordHasher().HashPassword(user.Password)); } public Task<IList<string>> GetRolesAsync(AppUser user) { IList<string> roleNames = UserRoleMap.Where(x => x.Item1 == user.Id) .Select(x => x.Item2) .Select(x => Roles.First(y => y.Id == x)) .Select(x => x.Name) .ToList(); return Task.FromResult(roleNames); } public Task<bool> HasPasswordAsync(AppUser user) { return Task.FromResult(user.Password != null); } public async Task<bool> IsInRoleAsync(AppUser user, string roleName) { var roles = await this.GetRolesAsync(user); return roles.FirstOrDefault(x => x.ToUpper() == roleName.ToUpper()) != null; } public Task RemoveFromRoleAsync(AppUser user, string roleName) { var role = Roles.FirstOrDefault(x => x.Name == roleName); if (role == null) { return Task.FromResult(default(object)); } var userRoleMap = UserRoleMap.FirstOrDefault(x => x.Item1 == user.Id && x.Item2 == role.Id); if (UserRoleMap != null) { UserRoleMap.Remove(userRoleMap); } return Task.Delay(0); } public Task SetPasswordHashAsync(AppUser user, string passwordHash) { user.Password = passwordHash; return Task.Delay(0); } public async Task UpdateAsync(AppUser user) { var target = await this.FindByIdAsync(user.Id); if (target == null) { return; } target.UserName = user.UserName; target.Password = user.Password; return; } public Task UpdateAsync(AppRole role) { var t = Roles.FirstOrDefault(r => r.Id == role.Id); if (t == null) { return Task.FromResult(default(object)); } t.Name = role.Name; return Task.FromResult(default(object)); } Task<AppRole> IRoleStore<AppRole, string>.FindByIdAsync(string roleId) { return Task.FromResult(Roles.FirstOrDefault(u => u.Id == roleId)); } Task<AppRole> IRoleStore<AppRole, string>.FindByNameAsync(string roleName) { return Task.FromResult(Roles.FirstOrDefault(u => u.Name == roleName)); } }
StartUp
ルートのStartUp.csを作成します。
using System; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin; using Microsoft.Owin.Security; using Microsoft.Owin.Security.Cookies; using Owin; [assembly: OwinStartup(typeof(MvcWeb2.OwinStartUp))] namespace MvcWeb2 { public class OwinStartUp { public void Configuration(IAppBuilder app) { // 後から「HttpContext.GetOwinContext().Get」で取り出して使える。 // Visual Studioのテンプレートに書いているコメントですが、だそうです。(DBは実装していないので、DbContextは作成していません。 app.CreatePerOwinContext<AppUserMananger>(AppUserMananger.Create); app.CreatePerOwinContext<AppSignInManager>(AppSignInManager.Create); app.CreatePerOwinContext<AppRoleManager>((options, context) => new AppRoleManager(context.Get<AppUserStore>())); app.CreatePerOwinContext<AppSignInManager>((options, context) => new AppSignInManager(context.GetUserManager<AppUserManager>(), context.Authentication)); app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Auth/Login") }); } } }
Controller
using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.Owin; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using System.Web; using System.Web.Mvc; [Authorize] public class AuthController : Controller { private AppSignInManager _appSignInManager; private AppUserManager _appUserManager; public AppSignInManager SignInManager { get { // StartUp.csでCreatePerOwinContext登録したオブジェクトを取り出す。 return _appSignInManager ?? HttpContext.GetOwinContext().Get<AppSignInManager>(); } private set { _appSignInManager = value; } } public AppUserManager UserManager { get { // StartUp.csでCreatePerOwinContext登録したオブジェクトを取り出す。 return _appUserManager ?? HttpContext.GetOwinContext().Get<AppUserManager>(); } private set { _appUserManager = value; } } // GET: Auth/Login [AllowAnonymous] public ActionResult Login(string returnUrl) { ViewBag.ReturnUrl = returnUrl; return View(new LoginViewModel()); } // // POST: /Auth/Login [AllowAnonymous] [HttpPost] public async Task<ActionResult> Login(LoginViewModel model, string returnUrl) { if (!this.ModelState.IsValid) { ViewBag.ReturnUrl = returnUrl; return View(model); } // 認証 (SignInManagerを使用しない場合) //var userManager = new UserManager<AppUser>(new AppUserStore()); //var user = await userManager.FindAsync(model.UserName, model.Password); //if (user == null) //{ // // 認証失敗したらエラーメッセージを設定してログイン画面を表示する // this.ModelState.AddModelError("", "ユーザ名かパスワードが違います"); // ViewBag.ReturnUrl = returnUrl; // return View(model); //} //// クレームベースのIDを作って //var identify = await userManager.CreateIdentityAsync( // user, // DefaultAuthenticationTypes.ApplicationCookie); //// 認証情報を設定 //var authentication = this.HttpContext.GetOwinContext().Authentication; //authentication.SignIn(identify); // SignInManagerを利用する場合 var user = await UserManager.FindAsync(model.UserName, model.Password); if (user == null) { this.ModelState.AddModelError("", "ユーザ名かパスワードが違います"); return View(model); } await SignInManager.SignInAsync(user, false, false); // 元のページへリダイレクト return Redirect(returnUrl); } }
Auth/LoginView
@model MvcWeb2.Controllers.LoginViewModel @{ ViewBag.Title = "Login"; } <h2>Login</h2> @Html.ValidationSummary(true) @* ReturnUrlをパラメータに渡すようにしたフォームを作る *@ @using (Html.BeginForm(new { ReturnUrl = ViewBag.ReturnUrl })) { <div class="form-group"> @Html.LabelFor(m => m.UserName) @Html.TextBoxFor(m => m.UserName) @Html.ValidationMessageFor(m => m.UserName) </div> <div class="form-group"> @Html.LabelFor(m => m.Password) @Html.PasswordFor(m => m.Password) @Html.ValidationMessageFor(m => m.Password) </div> <input type="submit" value="ログイン" class="btn-default" /> }
Auth/LoginViewModel
public class LoginViewModel { [Required(ErrorMessage = "ユーザー名を入れてください")] [Display(Name = "ユーザー名")] public string UserName { get; set; } [Required(ErrorMessage = "パスワードを入れてください")] [Display(Name = "パスワード")] public string Password { get; set; } }