Forms Authentication в ASP.NET MVC
В ASP.NET MVC 4 к текущему моменту есть масса реализаций аутентификации, но мне была нужна Forms Authentication. Как обычно, полностью готового решения при поиске в интернетах мне не встретилось, потому спешу поделиться своим. Оказалось, я пропустил пару нюансов, и всё давно уже расписано. Но не выкидывать же теперь статью.
Идея такова, что на все действия вешается требование авторизации (хотя нам достаточно лишь аутентифицировать пользователя), и всякий незалогиненный пользователь будет перенаправляться на страницу входа. После ввода действительного логина и пароля создаётся Authentication cookie, благодаря которому пользователь считается аутентифицированным.
Первым делом смотрим в свойствах у проекта, какой тип аутентификации выставлен (эти значения задаются при создании проекта). Я-то думал, что все настройки хранятся в web.config
и свойствах проекта, которые открываются по правой кнопке мыши на имени проекта (не всего решения), но оказалось, что есть ещё одни свойства (из файла ВАШПРОЕКТ.csproj
):
Вот такие значения должны быть. Соответственно, когда будете хостить сайт в IIS (а не запускать из Студии), там тоже надо изменить эти параметры.
В /web.config
заносим следующее:
<authentication mode="Forms">
<forms loginUrl="/Account/Login" timeout="2880" />
</authentication>
И в фильтр /App_Start/FilterConfig.cs
вот это:
using System.Web;
using System.Web.Mvc;
namespace aspmvc
{
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
// вот тут и вешается атрибут на действия
filters.Add(new AuthorizeAttribute());
}
}
}
После этого в /Global.asax
:
protected void Application_Start()
{
logger.Info("Application Start");
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
Допустим, у вас где-то есть база данных с таблицей логинов и паролей. Создаём модель для этой таблицы /Models/Authoritah.cs
:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
using System.Web;
namespace aspmvc.Models
{
[Table("authoritah", Schema = "dbo")]
public class Authoritah
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long id_auth { get; set; }
[Required]
[Display(Name = "Логин")]
public string login { get; set; }
[Required]
[Display(Name = "Пароль")]
public string password { get; set; }
}
public class AuthoritahContext : DbContext
{
public AuthoritahContext(string connString) : base(connString) { }
public DbSet<Authoritah> Authoritahs { get; set; }
}
}
Теперь создаём контроллер /Controllers/AccountController.cs
:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
namespace aspmvc.Controllers
{
public class AccountController : Controller
{
[AllowAnonymous]
public ActionResult Login()
{
return View();
}
[AllowAnonymous]
[HttpPost]
public ActionResult Login(Models.Authoritah model, string returnUrl)
{
// проверка модели
if (ModelState.IsValid)
{
using (Models.AuthoritahContext db = new Models.AuthoritahContext("ТУТВАШАСТРОКАПОДКЛЮЧЕНИЯИЗWEB.CONFIG"))
{
string login = model.login;
string password = model.password;
bool userValid = db.Authoritahs.Any(user => user.login == login && user.password == password);
// есть такой пользователь в таблице?
if (userValid)
{
// создаём Authentication cookie
FormsAuthentication.SetAuthCookie(login, false);
// и пишем логин в сессию, на всякий
Session.Add("login", login);
// если есть куда возвращаться, то туда, иначе на главную страницу
if (string.IsNullOrEmpty(returnUrl))
{ return RedirectToAction("Index", "Home"); }
else
{ return Redirect(returnUrl); }
}
else
{
ModelState.AddModelError("Ошибка входа", "Вы указали неправильный логин или пароль.");
}
}
}
return View(model);
}
public ActionResult LogOff()
{
FormsAuthentication.SignOut();
Session.Abandon();
return RedirectToAction("Login", "Account");
}
}
}
Если не поставить [AllowAnonymous]
в подсвеченных строках, то пройти аутентификацию будет невозможно, так как приложение будет отказывать в доступе даже для страницы входа.
Осталось представление /Views/Account/Login.cshtml
. Я использую стили Bootstrap, если что:
@model aspmvc.Models.Authoritah
@{
ViewBag.Title = Страница входа";
Layout = "~/Views/Shared/_Layout.cshtml";
}
@{
//if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
if (Request.IsAuthenticated)
{
<div class="alert alert-success">
Вы уже выполнили вход в систему. @Html.ActionLink("Перейти на главную страницу", "Index", "Home").
</div>
}
else
{
<div class="alert alert-warning">
Для работы с системой вам необходимо осуществить вход, указав ваш логин и пароль.
</div>
using (Html.BeginForm())
{
<fieldset>
<div class="input-group" style="margin-bottom:5px;">
@Html.LabelFor(model => model.login, new { Class = "input-group-addon", Style = "min-width: 80px;" })
@Html.TextBoxFor(model => model.login, new { Class = "form-control", Style = "width: 300px;" })
</div>
<div class="input-group" style="margin-bottom:15px;">
@Html.LabelFor(model => model.password, new { Class = "input-group-addon", Style = "min-width: 80px;" })
@Html.PasswordFor(model => model.password, new { Class = "form-control", Style = "width: 300px;" })
</div>
<p>
<input type="submit" value="Вход" class="btn btn-default btn-lg" />
</p>
</fieldset>
}
}
}
Обратите внимание на выделенные строки. В разных местах для проверки, залогинен пользователь на сайте или ещё нет, предлагали различные способы. Например, Request.IsAuthenticated
или User.Identity.IsAuthenticated
, но они у меня всегда возвращали true
, даже сразу после старта приложения, когда ещё не загрузилась сама страница логина, а в User.Identity.Name
лежал мой логин от Windows.
Причиной оказалось то, что как раз таки в свойствах проекта стояла аутентификация Windows (посмотрите первый скриншот и описание к нему). И пока я это не нашёл, приходилось извращаться проверкой наличия Authentication cookie.
Для оживления статьи вот картинка, как выглядит страница входа:
Ну всё, теперь в шаблоне для представлений /Views/Shared/_Layout.cshtml
для полноты картины где-то надо добавить кнопку выхода:
@Html.ActionLink("Выход", "LogOff", "Account")
Social networks
Zuck: Just ask
Zuck: I have over 4,000 emails, pictures, addresses, SNS
smb: What? How'd you manage that one?
Zuck: People just submitted it.
Zuck: I don't know why.
Zuck: They "trust me"
Zuck: Dumb fucks