Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core
В этой статье рассмотрим, как мы можем аутентифицировать пользователя и разграничить доступ по ролям без всяких провайдеров и ASP.NET Identity средствами инфраструктуры OWIN. И для этого вначале создадим новый проект без аутентификации.
Прежде всего, нам необходимы модели пользователя и роли, а также контекст данных. Поэтому добавим в папку Models следующие классы:
public class User { public int Id { get; set; } public string Email { get; set; } public string Password { get; set; } public Role Role { get; set; } public int RoleId { get; set; } } public class Role { public int Id { get; set; } public string Name { get; set; } }
И также добавим класс контекста данных и инициализатор:
public class UserContext : DbContext { public UserContext():base("DefaultConnection") { } public DbSet<User> Users { get; set; } public DbSet<Role> Roles { get; set; } } public class UserDbInitializer : DropCreateDatabaseAlways<UserContext> { protected override void Seed(UserContext db) { db.Roles.Add(new Role { Id = 1, Name = "admin" }); db.Roles.Add(new Role { Id = 2, Name = "user" }); db.Users.Add(new User { Email = "alice@gmail.com", Password = "123456", RoleId = 1 }); db.Users.Add(new User { Email = "tom@gmail.com", Password = "123456", RoleId = 2 }); base.Seed(db); } }
Пусть в файле web.config у нас будет определена строка подключения DefaultConnection, а в файле Global.asax вызывается вышеопределенный инициализотор БД.
Теперь нам надо подключить OWIN в проект. Для этого добавим через Nuget следующие пакеты:
Microsoft.Owin.Host.SystemWeb Microsoft.Owin.Security.Cookies
Они имеют ряд зависимостей, и вместе с ними будут добавлены пакеты OWIN, Microsoft.Owin и Microsoft.Owin.Security.
Затем добавим в проект в папку App_Start класс OWIN, который назовем Startup:
using Microsoft.Owin; using Owin; using Microsoft.Owin.Security.Cookies; [assembly: OwinStartup(typeof(IdentityUoWApp.Startup))] namespace IdentityUoWApp { public class Startup { public void Configuration(IAppBuilder app) { app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = "ApplicationCookie", LoginPath = new PathString("/Account/Login"), }); } } }
С помощью метода расширения UseCookieAuthentication()
здесь устанавливается, что при аутентификации пользователей будут использоваться куки: AuthenticationType = "ApplicationCookie"
.
Теперь сделаем функционал для входа на сайт. Вначале добавим в проект папку ViewModels и в ней определим класс LoginViewModel - модель входа на сайт:
using System.ComponentModel.DataAnnotations; public class LoginModel { [Required] public string Email { get; set; } [Required] [DataType(DataType.Password)] public string Password { get; set; } }
Затем добавим в папку Controllers новый контроллер AccountController:
using System.Linq; using System.Web; using System.Web.Mvc; using System.Data.Entity; using System.Security.Claims; using Microsoft.Owin.Security; using IdentityUoWApp.ViewModels; // пространство имен LoginViewModel using IdentityUoWApp.Models; // пространство имен моделей using System.Threading.Tasks; namespace IdentityUoWApp.Controllers { public class AccountController : Controller { UserContext db = new UserContext(); private IAuthenticationManager AuthenticationManager { get { return HttpContext.GetOwinContext().Authentication; } } public ActionResult Login() { return View(); } [HttpPost] [ValidateAntiForgeryToken] public async Task<ActionResult> Login(LoginModel model) { if (ModelState.IsValid) { User user = await db.Users.Include(u=>u.Role).FirstOrDefaultAsync(u => u.Email == model.Email && u.Password == model.Password); if (user == null) { ModelState.AddModelError("", "Неверный логин или пароль."); } else { ClaimsIdentity claim = new ClaimsIdentity("ApplicationCookie", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType); claim.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(), ClaimValueTypes.String)); claim.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, user.Email, ClaimValueTypes.String)); claim.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "OWIN Provider", ClaimValueTypes.String)); if (user.Role!=null) claim.AddClaim(new Claim(ClaimsIdentity.DefaultRoleClaimType, user.Role.Name, ClaimValueTypes.String)); AuthenticationManager.SignOut(); AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = true }, claim); return RedirectToAction("Index", "Home"); } } return View(model); } public ActionResult Logout() { AuthenticationManager.SignOut(); return RedirectToAction("Index", "Home"); } } }
Первым делом с помощью контекста OWIN мы получаем объект IAuthenticationManager. Что это за объект? Данный объект управляет аутентификационными куками и выполняет ряд других действий.
В POST-методе Login мы получаем из представления объект LoginModel и по нему определяем пользователя. Если пользователь был найден, то создается объект ClaimsIdentity:
ClaimsIdentity claim = new ClaimsIdentity("ApplicationCookie", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
Первый параметр - "ApplicationCookie" указывате на тип аутентификации, то есть куки. И в конструктор также передаются константы ClaimsIdentity.DefaultNameClaimType и ClaimsIdentity.DefaultRoleClaimType, которые представляют название клейма логина пользователя и название клейма роли.
Далее в объект ClaimsIdentity последовательно добавляются клеймы, которые содержат логин пользователя, его id, роль:
claim.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(), ClaimValueTypes.String)); claim.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, user.Email, ClaimValueTypes.String)); if (user.Role!=null) claim.AddClaim(new Claim(ClaimsIdentity.DefaultRoleClaimType, user.Role.Name, ClaimValueTypes.String));
И также нам надо добавить специальный клейм провайдера:
claim.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", "OWIN Provider", ClaimValueTypes.String));
Причем второй параметр представляет в данном случае произвольное название провайдера - "OWIN Provider".
По похожей схеме происходит создание аутентификационного тикета в ASP.NET Identity, правда, там провайдер четко определен - "Asp Net Identity".
Далее с помощью методов AuthenticationManager.SignOut()
и AuthenticationManager.SignIn()
куки удаляются и вновь устанавливаются.
Таким образом, аутентификационный тикет будет содержать данные о логине пользователя, его роли и id. Мы уже по умолчанию можем получать логин пользователя: User.Identity.Name
.
Но на данный момент в проекте нет никакого способа определять id пользователя и роль. И для этого добавим в папку Models новый класс IdentityExtensions:
using System; using System.Globalization; using System.Security.Claims; using System.Security.Principal; namespace IdentityUoWApp.Models { public static class IdentityExtensions { public static T GetUserId<T>(this IIdentity identity) where T : IConvertible { if (identity == null) { throw new ArgumentNullException("identity"); } var ci = identity as ClaimsIdentity; if (ci != null) { var id = ci.FindFirst(ClaimTypes.NameIdentifier); if (id != null) { return (T)Convert.ChangeType(id.Value, typeof(T), CultureInfo.InvariantCulture); } } return default(T); } public static string GetUserRole(this IIdentity identity) { if (identity == null) { throw new ArgumentNullException("identity"); } var ci = identity as ClaimsIdentity; string role = ""; if (ci != null) { var id = ci.FindFirst(ClaimsIdentity.DefaultRoleClaimType); if (id != null) role = id.Value; } return role; } } }
Данный класс создает два метода расширения GetUserId и GetUserRole для получения из аутентификационного тикета id и роли пользователя.
И мы можем, например, использовать разграничение входа и эти методы в имеющемся у нас HomeController:
using System.Web.Mvc; using IdentityUoWApp.Models; namespace IdentityUoWApp.Controllers { [Authorize] public class HomeController : Controller { public ActionResult Index() { return View(); } [Authorize(Roles ="admin")] public ActionResult About() { ViewBag.Message = User.Identity.GetUserRole(); return View(); } public ActionResult Contact() { int id = User.Identity.GetUserId<int>(); ViewBag.Message = "Ваш id: " + id.ToString(); return View(); } } }