Собственная авторизация asp net mvc. Контроль доступа к определенным файлам. Управление контентом в зависимости от роли

Вы создали WebAPI и теперь хотите контролировать доступ к нему? В этой серии статей мы рассмотрим несколько вариантов защиты WebAPI от неавторизрованых пользователей. Серия будет охватывать обе стороны, и аутентификацию и авторизацию пользователей.

  • Аутентификация - позволяет однозначно идентифицировать пользователя. Например, Алиса входит в систему со своим логином и паролем, и сервер использует эти данные для аутентификации Алисы.
  • Авторизация решает может ли пользователь выполнить те или иные действия. Например, Алиса может иметь право на чтение ресурса, но не может создать новый ресурс.

Первая серия статей дает общий обзор аутентификации и авторизации в ASP.NET Web API. Другие статьи описывают общие сценарии аутентификации для WebAPI.

Аутентификация

WebAPI предполагает что аутентификацию проводит хост, в котором он размещается. Для веб-хостинга хостом является IIS, который использует HTTP модули для аутентификации. Вы можете сконфигурировать свой проект на использование модулей аутентификации встроенных в IIS или ASP.NET, или же написать собственный модуль HTTP, для выполнения кастомной проверки подлинности.

Когда хост проводит аутентификацию пользователя, он создает объект IPrincipal, который представляет собой контекст безопасности в котором исполняется код. Созданный объект прикрепляет к текущему потоку, и к нему можно обратиться через свойство Thread.CurrentPrincipal. Контекст безопасности содержит связанный с ним объект Identity, с информацией о пользователе. Если пользователь прошел аутентификацию, свойство Identity.IsAuthenticated вернет значение true. Для анонимных запросов свойство вернет false.

Использование HTTP обработчиков сообщений для аутентификации.

Вместо использования хоста, логику аутентификации можно поместить в обработчик HTTP сообщения. В этом случае, обработчик сообщения исследует HTTP запрос и сам задаст контекст безопасности.

Несколько примеров для чего может понадобиться аутентификация в обработчиках сообщений:

  • HTTP модуль видит все запросы проходящие по каналу ASP.NET, обработчик видит только запросы предназначенные WebAPI
  • Вы можете установить отдельные обработчики сообщений на каждый маршрут, применяя различные схемы аутентификации
  • HTTP модули специфичны для IIS. Обработчики сообщений независимы от хоста, и могут быть использованы как в случае web-хостинга, так и при self-хостинге.
  • HTTP модули учавствуют при логгинге IIS, аудите и т.д.
  • HTTP модули запускаются раньше в цепочке прохождения запроса. Если проводите аутентификацию в обработчике сообщений, контекст безопасности не будет установлен пока не будет запущен обработчик. Кроме того, контекст безопасности возвращается к предыдущему состоянию когда ответ на запрос покинет обработчик сообщения.

В общем, если вы не собираетесь использовать self-хостинг, использование HTTP модулей аутентификации лучший вариант. В противном случае логику стоит вынести в обработчики сообщений.

Установка контекста безопасности

Если ваше приложение выполняет какую-либо логику связанную с аутентификацией, вы должны задать контекст безопасности в двух свойствах:

  • Thread.CurrentPrincipal - стандартный путь задать контекст безопасности для потока в.NET
  • HttpContext.Current.User - свойство специфичное для ASP.NET

Следующий код показывает как задать контекст безопасности:

Private void SetPrincipal(IPrincipal principal) { Thread.CurrentPrincipal = principal; if (HttpContext.Current != null) { HttpContext.Current.User = principal; } }

Для веб-хостинга, вы должны задать контекст безопасности в обоих свойствах, иначе контекст безопасности может стать несогласованным. Для обеспечения независимости вашего кода от способа хостинга , следует выставлять проверку на null для свойства HttpContext.Current, как показано в коде. В случае self-хостинга, его значение будет null и задавать контекст безопасности не требуется.

Авторизация

  • Фильтры авторизации отрабатывают до методов контроллера. Если запрос не авторизован, фильтр вернет сообщение об ошибке, а метод контроллера не будет вызван.
  • В методе контроллера вы можете получить текущий контекст безопасности из свойства ApiController.User. Например, вы можете фильтровать список ресурсов в зависимости от имени пользователя, возвращая только доступные для него.

Использование аттрибута

WebAPI предоставляет встроенный фильтр авторизации, AuthorizeAttribute, этот фильтр проверяет авторизован ли пользователь. Если нет, фильтр вернет код состояния HTTP 401 (Не авторизован), без вызова метода.
Вы можете применять фильтр как глобально, так и на уровне контроллера или на уровне методов.

Фильтр на глобальном уровне : чтобы ограничить доступ для каждого контроллера WebAPI, добавьте фильтр AuthorizeAttribute в глобальный список фильтров:

Public static void Register(HttpConfiguration config) { config.Filters.Add(new AuthorizeAttribute()); }

Фильтр на уровне контроллера : для ограничения доступа к конкретному контроллеру, добавьте фильтр в качестве атрибута класса контроллера:

// Require authorization for all actions on the controller. public class ValuesController: ApiController { public HttpResponseMessage Get(int id) { ... } public HttpResponseMessage Post() { ... } }

Фильтр на уровне метода : для ограничения доступа к методу, добавьте к нему атрибут:

Public class ValuesController: ApiController { public HttpResponseMessage Get() { ... } // Require authorization for a specific action. public HttpResponseMessage Post() { ... } }

Кроме того, в можете задать ограничение на контроллер, и разрешить анонимный доступ на отдельные методы при помощи атрибута . В следующем примере, доступ к методу Post ограничен, а метод Get доступен для анонимных вызовов:

Public class ValuesController: ApiController { public HttpResponseMessage Get() { ... } public HttpResponseMessage Post() { ... } }

В предыдущих примерах, фильтр позволял получить доступ любому пользователю прошедшему проверку подлинности, запрещая доступ только анонимным пользователям. Вы также можете предоставить доступ конкретным пользователям или ролям:

// Restrict by user: public class ValuesController: ApiController { } // Restrict by role: public class ValuesController: ApiController { }

Фильтр AuthorizeAttribute для контроллеров WebAPI находится в пространстве имен System.Web.Http. Существует аналогичный фильтр для контроллеров MVC в пространстве имен System.Web.Mvc. Этот тип несовместим с контроллерами WebAPI.

Кастомные фильтр авторизации

Кастомный фильтр должен быть унаследован от одного из следующих типов:

  • AuthorizeAttribute . Наследуйте этот класс для реализации синхронной логики авторизации на основе текущего пользователя или роли пользователя.
  • AuthorizationFilterAttribute . Данный класс пригодится для реализации логики авторизации необязательно основанной на пользователе или его роли.
  • IAuthorizationFilter . Реализуйте этот интерфейс для асинхронной логики авторизации. Например ваша авторизация предполагает асинхронные или сетевые вызовы (если логика завязана на процессорных вычислениях, то лучше наследоваться от AuthorizationFilterAttribute, чтобы не писать асинхронные методы)

Следующая диаграмма показывает иерархию классов фильтров:

Авторизация внутри метода контроллера

В некоторых случаях можно разрешить выполнение запроса, но изменить поведение на основе контекста безопасности. Например, результат запроса зависит от роли пользователя. Внутри метода контроллера вы можете получить текущий контекст безопасности обратившись к свойству ApiController.User:

Public HttpResponseMessage Get() { if (User.IsInRole("Administrators")) { // ... } }

* Перевод выполнен в вольном стиле, возможно кого-то покоробят англицизмы наподобие "кастомный", но эти слова стали частью it жаргона, и русский перевод не воспринимается как должно.

Предоставление каждому посетителю сайта уникальной учётной записи позволяет вам однозначно идентифицировать любого из них. Идентификация позволяет веб-сайту менять оформление и контент в зависимости от предпочтений и интересов посетителей.

Многие современные сайты содержат различные разделы, предназначенные для разных групп пользователей: от обычных посетителей до администраторов. Также вы можете использовать авторизацию пользователей для предоставления им доступа к различным ресурсам на персональной основе.

Корпоративные сайты, расположенные в защищённых внутренних сетях предприятий, могут содержать страницы с отчётами, предназначенными только для руководителей или сотрудников определённых отделов. Рядовые сотрудники не должны иметь доступа к этим ресурсам.

Также современные веб-сайты могут иметь различные настройки, одни из которых предназначены для удобства широкого круга пользователей, в то время как другие рассчитаны только на использование веб-мастерами. Авторизация позволяет определить, какие функции сайта необходимы и достаточны для каждого конкретного пользователя.

Например, в электронном магазине вы наверняка позволите каждому пользователю просматривать витрину и пользоваться корзиной заказов.

Такие функции, как заказ товаров, корректировка заказов и отслеживание доставки должны быть доступны только зарегистрированным пользователям. И вы уж точно не захотите, чтобы те или другие могли просматривать или менять заказы, сделанные другими пользователями.

Ваша задача как веб-разработчика состоит в том, чтобы защитить от несанкционированного доступа не только сам сайт, но и данные, оставляемые на нём пользователями. Нарушение безопасности чужих данных может привести к серьёзным последствиям .

Что такое авторизация?

Как мы уже говорили, предоставление пользователям уникальных учётных записей позволяет идентифицировать их. Механизм «узнавания » сайтом зарегистрированных посетителей называется аутентификацией.

При помощи механизма аутентификации мы можем подстроить сайт под нужды и полномочия каждого пользователя, начиная с банальной персонализации приветствия, выдаваемого пользователю при входе на сайт.

Большинство сайтов, относящихся к сегменту электронной коммерции, могут хранить адреса, номера кредитных карт и другую информацию о своих клиентах. Это избавляет покупателей от необходимости вводить эти данные заново при каждом заказе.

Эти сайты также могут накапливать информацию о заказах, просмотрах товаров и привычках своих клиентов, чтобы предлагать им сопутствующие товары или получать полезную маркетинговую статистику. Можно давать посетителям возможность настроить под себя оформление электронного магазина, выбрать любимые категории товаров и так далее.

Аутентификация сама по себе позволяет разработчику использовать многие полезные функции и настроить сайт согласно предпочтениям каждого конкретного пользователя.

Авторизация же использует уникальный идентификатор пользователя для того, чтобы определить, какие действия пользователь имеет право совершить на сайте: какие страницы просмотреть, какие данные изменить и т. д.

Управление доступом на основе групп и ролей

Хотя права можно назначать индивидуально каждому пользователю сайта, управление этим процессом существенно осложняется по мере роста числа пользователей. Любое изменение в системе безопасности сайта может потребовать перенастройки прав большого количества пользователей, что требует времени и чревато ошибками.

Для решения этой проблемы пользователи, имеющие одинаковые права или потребности, собираются в группы. Отдельные права также могут явно или неявно группироваться в так называемые роли. Роли, в свою очередь, могут назначаться отдельным пользователям или группам. В некоторых системах «роль » и «группа » являются синонимами.

Новый пользователь на сайте включается в какую-либо группу. Это может делаться автоматически либо вручную, администратором сайта. Таким образом, пользователь получает заранее определённый набор прав и ограничений.

Чтобы отразить изменение политики безопасности сайта на всех имеющихся и будущих пользователях, администратору достаточно откорректировать свойства группы.

Однако в случае принадлежности пользователя к нескольким группам или ролям в системе должен быть предусмотрен механизм решения конфликтов прав. Например, пользователь блога может быть включён в две группы, одной из которых разрешено создавать посты, а другой – только просматривать. Какое право присваивать пользователю в итоге?

Большая часть систем оставляет в действии минимальные права либо предусматривает приоритет запретов над разрешениями. В таком случае наш гипотетический пользователь не будет иметь права писать в блог, так как ограничение полномочий будет преобладать над разрешением.

Хорошей практикой является разделение пользователей на группы по виду деятельности на сайте. Например, наш блог может иметь следующие группы пользователей: «Авторы », «Редакторы », «Модераторы » и т.д.

Альтернативный подход будет состоять в том, чтобы выделить группы «Создание статей », «Правка статей », «Удаление комментариев » и т. д. Такой подход будет обладать значительной гибкостью, но при этом придётся поддерживать на сайте большее количество различных групп.

Защита страниц средствами ASP.NET

Первой задачей при построении сайта будет являться разделение доступа к страницам. Я сосредоточу ваше внимание на возможностях ASP.NET , хотя многие другие фреймворки и системы управления контентом имеют схожие концепции, но существенно иные команды и настройки.

При защите сайта на ASP.NET используются три направления разделения доступа:

  • Система роутинга адресов;
  • Веб-формы (файлы и папки );
  • Структуры MVC .

Защита сайта на веб-формах

Как веб-формы, так и роутинг в ASP.NET использует для защиты доступа web.config . Основа конфигурации для защиты доступа к ресурсам сайта выглядит примерно так:

XML -элемент location определяет путь к защищаемому ресурсу: папке, странице или элементу роутинга. В данном примере он задаёт страницу adminhome.aspx . Можно защитить содержимое папки целиком, указав путь к ней. Если атрибут path отсутствует, настройки безопасности применяются к той папке, в которой находится файл web.config , со всеми её подпапками.

Элемент authorization определяет, кто имеет или не имеет доступа к защищаемому ресурсу. Права проверяются последовательно, начиная с первого и до тех пор, пока совпадение не будет найдено. Вложенный элемент allow задаёт разрешение, а deny – запрещение доступа к ресурсу для заданного пользователя или роли.

В нашем примере правило будет проверено первым. Если пользователь обладает ролью администратора, доступ ему будет предоставлен, и проверка условий на этом прекратится.

В противном случае проверка продолжится до следующего правила: , которое лишит доступа к ресурсу всех пользователей. Таким образом, наш пример даёт доступ к файлу только администраторам. Всем остальным будет отказано в доступе.

Существует несколько специальных символов, обозначающих часто используемые группы. Мы уже видели символ «* », обозначающий всех пользователей.

Символ «? » обозначает анонимного (не успевшего зарегистрироваться ) пользователя. Несколько групп или отдельных пользователей могут быть перечислены через запятую. Пользователи и роли могут смешиваться в одном правиле, например:

Защита MVC-сайта

Разработка сайта согласно методологии MVC сосредотачивается не на файлах и папках, а на контроллерах и их действиях. Соответствующим образом меняется и защита. По умолчанию все действия всех контроллеров доступны всем пользователям, как и в случае с веб-формами. Вам доступны те же пользователи и роли, но файлов web.config больше нет.

Вместо этого вы применяете атрибут непосредственно к контроллерам и действиям. Например, если у вас есть контроллер, доступ к которому должны иметь только администраторы, вы можете добавить соответствующую роль в тэг. Учтите, что использование тэгов в означает неявный запрет доступа для всех пользователей, не перечисленных в них:

Public class AdminController: Controller { ...

Для обозначения анонимов и всех пользователей доступны те же символы «? » и «* ». Вы можете применять установки ко всему контроллеру или к отдельным действиям. При этом настройки действий будут иметь более высокий приоритет, чем настройки всего контроллера:

Public ActionResult AdminView() { ...

Если вы не укажете пользователей или ролей в атрибуте , то доступ будет разрешён всем зарегистрированным пользователям.

В ASP.NET 4 был добавлен атрибут , который позволил разрешить анонимному пользователю доступ к отдельным действиям защищённого контроллера.

Управление контентом в зависимости от роли

Ограничив доступ к страницам и контроллерам, следующим шагом вы должны убедиться в том, что доступ к самому серверному коду осуществляется правильно. Некоторые объекты легко защитить, потому что к ним должны иметь доступ только пользователи с определённой ролью, и доступ этот – полный.

В более сложных случаях доступ к одной и той же странице должен осуществляться пользователями с разными ролями, но при этом представление страницы для них должно быть разным. Помимо ограничения доступа к критичным объектам, удостоверьтесь также, что пользователи не видят элементов управления и ссылок, которыми не могут и не должны воспользоваться.

Например, не имеет смысла показывать ссылку на панель администрирования рядовым пользователям. Клиент, не имеющий отправленных заказов, не должен видеть кнопку трекинга. Даже если элемент не активен или ведёт на страницу авторизации, он может смутить простого пользователя и дать злоумышленнику пищу для размышления.

Код, исполняемый сервером на странице, доступной нескольким ролям, должен всегда проверять права пользователя и основывать свои действия на результате этой проверки.

Если на странице, просматриваемой как обычными посетителями, так и администраторами, должна быть ссылка на ресурс, доступный только администратору, то в варианте, предназначенном для обычного пользователя, её нужно скрыть.

При программной проверке доступа изначально предполагайте наименьшие права, а уже потом дополняйте код проверками и выводом элементов и ссылок, которые нужно обезопасить.

Всегда проверяйте, насколько безопасен код, обрабатывающий GET -запросы. Например, ваш веб-магазин имеет запрос для удаления заказа:

UpdateOrder.aspx?order=33&action=delete

Теперь, представьте хакера, удаляющего все заказы перебором параметра order . Проверяется ли принадлежность заказа пользователю в соответствующем обработчике?

В другом случае отсутствие проверки в чём-то вроде:

UpdateOrder.aspx?order=33&action=refund

позволит злоумышленнику получить возмещение за чужой или не оплаченный заказ. Никогда не полагайтесь на скрытие ссылок как единственный механизм защиты от не авторизованного доступа.

Аспекты безопасности пользовательских сессий

Защита самих данных авторизации является отдельной задачей безопасности, хоть и близко соотносящейся с безопасностью доступа к ресурсам и коду. Во-первых, на безопасность механизма аутентификации влияет продолжительность сессии. В ASP.NET этот параметр задаётся в web.config следующим образом:

В этом примере время жизни сессии составит 30 минут. Атрибут slidingExpiration определяет, сбрасывает ли счётчик истечения сессии поступление запроса от пользователя. Если установить данному атрибуту значение false , пользователю придётся осуществлять вход каждые 30 минут, даже во время активной работы с сайтом.

Также необходимо учитывать возможность кражи сессии. Большинство веб-фреймворков хранят идентификатор сессии в маленьком кусочке текстовых данных, называемом кукой или печенькой (cookie ), в браузере пользователя.

Если кука никак не защищена, её может использовать злоумышленник, перехватив трафик легитимного пользователя и представившись системе этим пользователем.

  • Tutorial

Цель урока : Изучить способ авторизации через Cookie, использование стандартных атрибутов доступа к контроллеру и методу контроллера. Использование IPrincipal. Создание собственного модуля (IHttpModule) и собственного фильтра IActionFilter.

Небольшое отступление: На самом деле в asp.net mvc все учебники рекомендуют пользоваться уже придуманной системой авторизации, которая называется AspNetMembershipProvider, она была описана в статье http://habrahabr.ru/post/142711/ (сейчас доступ уже закрыт), но обьяснено это с точки зрения «нажимай и не понимай, что там внутри». При первом знакомстве с asp.net mvc меня это смутило. Далее, в этой статье http://habrahabr.ru/post/143024/ - сказано, что пользоваться этим провайдером – нельзя. И я согласен с этим. Здесь же, мы достаточно глубоко изучаем всякие хитрые asp.net mvc стандартные приемы, так что это один из основных уроков.

Кукисы
Кукисы – это часть информации, отсылаемая сервером браузеру, которую браузер возвращает обратно серверу вместе с каждым (почти каждым) запросом.

Сервер в заголовок ответа пишет:
Set-Cookie: value[; expires=date][; domain=domain][; path=path][; secure]
Например:

HTTP/1.1 200 OK Content-type: text/html Set-Cookie: name=value Set-Cookie: name2=value2; Expires=Wed, 09-Jun-2021 10:18:14 GMT
Браузер (если не истекло время действия кукиса) при каждом запросе:
GET /spec.html HTTP/1.1 Host: www.example.org Cookie: name=value; name2=value2 Accept: */*

Устанавливаем cookie (/Areas/Default/Controllers/HomeController.cs):
public ActionResult Index() { var cookie = new HttpCookie() { Name ="test_cookie", Value = DateTime.Now.ToString("dd.MM.yyyy"), Expires = DateTime.Now.AddMinutes(10), }; Response.SetCookie(cookie); return View(); }

В Chrome проверяем установку:

Для получения кукисов:
var cookie = Request.Cookies["test_cookie"];

Делаем точку остановки и проверяем:

Примечание: подробнее можно изучить кукисы по следующей ссылке:
http://www.nczonline.net/blog/2009/05/05/http-cookies-explained/

Авторизация
В нашем случае авторизация будет основана на использовании кукисов. Для этого изучим следующие положения:
  • FormsAuthenticationTicket – мы воспользуемся этим классом, чтобы хранить данные авторизации в зашифрованном виде
  • Нужно реализовать интерфейс IPrincipal и установить в HttpContext.User для проверки ролей и IIdentity интерфейса.
  • Для интерфейса IIdentity сделать реализацию
  • Вывести в BaseController в свойство CurrentUser значение пользователя, который сейчас залогинен.

Приступим.
Создадим интерфейс IAuthentication и его реализацию CustomAuthentication (/Global/Auth/IAuthentication.cs):

Public interface IAuthentication { ///

/// Конекст (тут мы получаем доступ к запросу и кукисам) /// HttpContext HttpContext { get; set; } User Login(string login, string password, bool isPersistent); User Login(string login); void LogOut(); IPrincipal CurrentUser { get; } }

Реализация (/Global/Auth/CustomAuthentication.cs):
public class CustomAuthentication: IAuthentication { private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); private const string cookieName = "__AUTH_COOKIE"; public HttpContext HttpContext { get; set; } public IRepository Repository { get; set; } #region IAuthentication Members public User Login(string userName, string Password, bool isPersistent) { User retUser = Repository.Login(userName, Password); if (retUser != null) { CreateCookie(userName, isPersistent); } return retUser; } public User Login(string userName) { User retUser = Repository.Users.FirstOrDefault(p => string.Compare(p.Email, userName, true) == 0); if (retUser != null) { CreateCookie(userName); } return retUser; } private void CreateCookie(string userName, bool isPersistent = false) { var ticket = new FormsAuthenticationTicket(1, userName, DateTime.Now, DateTime.Now.Add(FormsAuthentication.Timeout), isPersistent, string.Empty, FormsAuthentication.FormsCookiePath); // Encrypt the ticket. var encTicket = FormsAuthentication.Encrypt(ticket); // Create the cookie. var AuthCookie = new HttpCookie(cookieName) { Value = encTicket, Expires = DateTime.Now.Add(FormsAuthentication.Timeout) }; HttpContext.Response.Cookies.Set(AuthCookie); } public void LogOut() { var httpCookie = HttpContext.Response.Cookies; if (httpCookie != null) { httpCookie.Value = string.Empty; } } private IPrincipal _currentUser; public IPrincipal CurrentUser { get { if (_currentUser == null) { try { HttpCookie authCookie = HttpContext.Request.Cookies.Get(cookieName); if (authCookie != null && !string.IsNullOrEmpty(authCookie.Value)) { var ticket = FormsAuthentication.Decrypt(authCookie.Value); _currentUser = new UserProvider(ticket.Name, Repository); } else { _currentUser = new UserProvider(null, null); } } catch (Exception ex) { logger.Error("Failed authentication: " + ex.Message); _currentUser = new UserProvider(null, null); } } return _currentUser; } } #endregion }

Суть сводится к следующему, мы, при инициализации запроса, получаем доступ к HttpContext.Request.Cookies и инициализируем UserProvider:

Var ticket = FormsAuthentication.Decrypt(authCookie.Value); _currentUser = new UserProvider(ticket.Name, Repository);

Для авторизации в IRepository добавлен новый метод IRepository.Login. Реализация в SqlRepository:
public User Login(string email, string password) { return Db.Users.FirstOrDefault(p => string.Compare(p.Email, email, true) == 0 && p.Password == password); }

UserProvider, собственно, реализует интерфейс IPrincipal (в котором есть проверка ролей и доступ к IIdentity).
Рассмотрим класс UserProvider (/Global/Auth/UserProvider.cs):

Public class UserProvider: IPrincipal { private UserIndentity userIdentity { get; set; } #region IPrincipal Members public IIdentity Identity { get { return userIdentity; } } public bool IsInRole(string role) { if (userIdentity.User == null) { return false; } return userIdentity.User.InRoles(role); } #endregion public UserProvider(string name, IRepository repository) { userIdentity = new UserIndentity(); userIdentity.Init(name, repository); } public override string ToString() { return userIdentity.Name; }

Наш UserProvider знает про то, что его IIdentity классом есть UserIdentity , а поэтому знает про класс User , внутри которого мы реализуем метод InRoles(role) :

Public bool InRoles(string roles) { if (string.IsNullOrWhiteSpace(roles)) { return false; } var rolesArray = roles.Split(new { "," }, StringSplitOptions.RemoveEmptyEntries); foreach (var role in rolesArray) { var hasRole = UserRoles.Any(p => string.Compare(p.Role.Code, role, true) == 0); if (hasRole) { return true; } } return false; }

В метод InRoles мы ожидаем, что придет запрос о ролях, которые допущены к ресурсу, разделенные запятой. Т.е., например, “admin,moderator,editor”, если хотя бы одна из ролей есть у нашего User – то возвращаем зачение «истина» (доступ есть). Сравниваем по полю Role.Code, а не Role.Name.
Рассмотрим класс UserIdentity (/Global/Auth/UserIdentity.cs):
public class UserIndentity: IIdentity { public User User { get; set; } public string AuthenticationType { get { return typeof(User).ToString(); } } public bool IsAuthenticated { get { return User != null; } } public string Name { get { if (User != null) { return User.Email; } //иначе аноним return "anonym"; } } public void Init(string email, IRepository repository) { if (!string.IsNullOrEmpty(email)) { User = repository.GetUser(email); } } }
В IRepository добавляем новый метод GetUser(email) . Реализация для SqlRepository.GetUser() (LessonProject.Model:/SqlRepository/User.cs):

Public User GetUser(string email) { return Db.Users.FirstOrDefault(p => string.Compare(p.Email, email, true) == 0); }

Почти все готово. Выведем CurrentUser в BaseController:
public IAuthentication Auth { get; set; } public User CurrentUser { get { return ((UserIndentity)Auth.CurrentUser.Identity).User; } }

Да, это не очень правильно, так как здесь присутствует сильное связывание. Поэтому сделаем так, введем еще один интерфейс IUserProvider , из которого мы будем требовать вернуть нам авторизованного User:
public interface IUserProvider { User User { get; set; } } … public class UserIndentity: IIdentity, IUserProvider { ///

/// Текщий пользователь /// public User User { get; set; } … public IAuthentication Auth { get; set; } public User CurrentUser { get { return ((IUserProvider)Auth.CurrentUser.Identity).User; } }
А теперь попробуем инициализировать это всё.
Вначале добавим наш IAuthentication + CustomAuthentication в регистрацию к Ninject (/App_Start/NinjectWebCommon.cs):

Kernel.Bind().To().InRequestScope();

Потом создадим модуль, который будет на событие AuthenticateRequest совершать действие авторизации:
public class AuthHttpModule: IHttpModule { public void Init(HttpApplication context) { context.AuthenticateRequest += new EventHandler(this.Authenticate); } private void Authenticate(Object source, EventArgs e) { HttpApplication app = (HttpApplication)source; HttpContext context = app.Context; var auth = DependencyResolver.Current.GetService(); auth.HttpContext = context; context.User = auth.CurrentUser; } public void Dispose() { } }

Вся соль в строках: auth.HttpContext = context и context.User = auth.CurrentUser . Как только наш модуль авторизации узнает о контексте и содержащихся в нем кукисах, ту же моментально получает доступ к имени, по нему он в репозитории получает данныепользователя и возвращает в BaseController. Но не сразу всё, а по требованию.
Подключаем модуль в Web.config:

План таков:

  • Наверху показываем, авторизован пользователь или нет. Если авторизован, то его email и ссылка на выход, если нет, то ссылки на вход и регистрацию
  • Создаем форму для входа
  • Если пользователь правильно ввел данные – то авторизуем его и отправляем на главную страницу
  • Если пользователь выходит – то убиваем его авторизацию

Поехали. Добавляем Html.Action(“UserLogin”, “Home”) – это partial view (т.е. кусок кода, который не имеет Layout) – т.е. выводится где прописан, а не в RenderBody().
_Layout.cshtml (/Areas/Default/Views/Shared/_Layout.cshtml):

@RenderBody() HomeController.cs: public ActionResult UserLogin() { return View(CurrentUser); }

UserLogin.cshtml (/Areas/Default/Views/Home/UserLogin.cshtml):

@model LessonProject.Model.User @if (Model != null) {

  • @Model.Email
  • @Html.ActionLink("Выход", "Logout", "Login")
  • } else {
  • @Html.ActionLink("Вход", "Index", "Login")
  • @Html.ActionLink("Регистрация", "Register", "User")
  • }

    Контроллер входа выхода LoginController (/Areas/Default/Controllers/LoginController.cs):

    Public class LoginController: DefaultController { public ActionResult Index() { return View(new LoginView()); } public ActionResult Index(LoginView loginView) { if (ModelState.IsValid) { var user = Auth.Login(loginView.Email, loginView.Password, loginView.IsPersistent); if (user != null) { return RedirectToAction("Index", "Home"); } ModelState["Password"].Errors.Add("Пароли не совпадают"); } return View(loginView); } public ActionResult Logout() { Auth.LogOut(); return RedirectToAction("Index", "Home"); } }

    LoginView.cs (/Models/ViewModels/LoginView.cs):
    public class LoginView { public string Email { get; set; } public string Password { get; set; } public bool IsPersistent { get; set; } }

    Страница для входа Index.cshtml (/Areas/Default/Views/Index.cshtml):

    @model LessonProject.Models.ViewModels.LoginView @{ ViewBag.Title = "Вход"; Layout = "~/Areas/Default/Views/Shared/_Layout.cshtml"; }

    Вход

    @using (Html.BeginForm("Index", "Login", FormMethod.Post, new { @class = "form-horizontal" })) {
    Вход
    @Html.TextBox("Email", Model.Email, new { @class = "input-xlarge" })

    Введите Email

    @Html.ValidationMessage("Email")
    @Html.Password("Password", Model.Password, new { @class = "input-xlarge" }) @Html.ValidationMessage("Password")
    }

    Запускаем и проверяем:

    Все исходники находятся по адресу

    Последнее обновление: 31.10.2015

    Релиз ASP.NET MVC 5 ознаменовался выходом новой системой авторизации и аутентификации в.NET приложениях под названием ASP.NET Identity. Эта система пришла на смену провайдерам Simple Membership, которые были введены в ASP.NET MVC 4.

    Нажав на кнопку Change Authentication , мы можем изменить тип аутентификации, выбрав одно из следующих:

      No Authentication : ASP.NET Identity и встроенная система аутентификации отсутствует

      Individual User Accounts : проект по умолчанию включает систему ASP.NET Identity, которая позволяет авторизовать как пользователей внутри приложения, так и с помощью внешних сервисов, как google, твиттер и т.д.

      Organizational Accounts : подходит для сайтов и веб-приложений отдельных компаний и организаций

      Windows Authentication : система аутентификации для сетей intranet с помощью учетных записей Windows

    Оставим значение по умолчанию, то есть Individual User Accounts и создадим проект.

    Созданный проект уже по умолчанию имеет всю необходимую для авторизации инфраструктуру: модели, контроллеры, представления. Если мы заглянем в узел References (Библиотеки), то увидим там ряд ключевых библиотек, которые и содержит необходимые для авторизации и аутентификации классы:

    Это ряд библиотек OWIN, которые добавляют функциональность OWIN в проект, а также три библиотеки собственно ASP.NET Identity:

      Microsoft.AspNet.Identity.EntityFramework : содержит классы Entity Framework, применяющие ASP.NET Identity и осуществляющие связь с SQL Server

      Microsoft.AspNet.Identity.Core : содержит ряд ключевых интерфейсов ASP.NET Identity. Реализация этих интерфейсов позволит выйти за рамки MS SQL Server и использовать в качестве хранилища учетных записей другие СУБД, в том числе системы NoSQL

      Microsoft.AspNet.Identity.OWIN : привносит в приложение ASP.NET MVC аутентификацию OWIN с помощью ASP.NET Identity

    Поскольку вся инфраструктура уже имеется в проекте, запустим проект, на отобразившейся в браузере странице найдем ссылку Register и нажмем на нее. На открывшейся странице регистрации введем какие-нибудь данные:

    После регистрации логин будет отображаться в правом верхнем углу веб-страницы веб-приложения. Осуществив регистрацию, мы можем разлогиниться, нажав на LogOff, и снова войти в систему. Таким образом, мы можем уже начать пользоваться встроенной системой аутентификации в приложении ASP.NET MVC 5. Теперь же рассомотрим ее основные моменты.

    Во-первых, где это все хранится? Куда попадают данные зарегистрированных пользователей?

    В данном случае используется подход Code First. В файле web.config уже имеется строка подключения по умолчанию, которая задает каталог базы данных. Если мы раскроем папку App_Data, то сможем увидеть созданную базу данных:

    Если вдруг в папке база данных не видна, нажмем вверху окна Solution Explorer на кнопку Show All Files (Показать все файлы).

    Мы можем открыть эту базу данных в окне Server Explorer и увидеть ее содержимое:

    По умолчанию при регистрации первого пользователя создается следующий набор таблиц:

      MigrationHistory : используется EntityFramework для миграций БД

      AspNetRoles : содержит определения ролей

      AspNetUserClaims : таблица, хранящая набор клеймов (claim). Claim представляет иную модель авторизации по сравнению с ролями. Грубо говоря, claim содержит некоторую информацию о пользователе, например, адрес электронной почты, логин, возраст и т.д. И эта информация позволяет идентифицировать пользователя и наделить его соответствующими правами доступа.

      AspNetUserLogins : таблица логинов пользователя

      AspNetUserRoles : таблица, устанавливающая для пользователей определенные роли

      AspNetUsers : собственно таблица пользователей. Если мы ее откроем, то увидим данные зарегистрированного пользователя

    Ключевыми объектами в AspNet Identity являются пользователи и роли . Вся функциональность по созданию, удалению пользователей, взаимодействию с хранилищем пользователей хранится в классе UserManager . Для работы с ролями и их управлением в AspNet Identity определен класс RoleManager . Классы UserManager и RoleManager находятся в библиотеке Microsoft.AspNet.Identity.Core.

    Каждый пользователь для UserManager представляет объект интерфейса IUser. А все операции по управлению пользователями производятся через хранилище, представленное объектом IUserStore.

    Каждая роль представляет реализацию интерфейса IRole, а управление ролями классом RoleManager происходит через хранилище IRoleStore.

    Непосредственную реализацию интерфейсов IUser, IRole, IUserStore и IRoleStore предоставляет пространство имен Microsoft.AspNet.Identity.EntityFramework:

    Класс IdentityUser является реализацией интерфейса IUser. А класс хранилища пользователей - UserStore реализует интерфейс IUserStore.

    Подобным образом класс IdentityRole реализует интерфейс IRole, а класс хранилища ролей - RoleStore реализует интерфейс IRoleStore.

    А для взаимодействия с базой данных в пространстве имен Microsoft.AspNet.Identity.EntityFramework определен класс контекста IdentityDbContext

    В приложении ASP.NET MVC мы не будем работать напрямую с классами IdentityUser и IdentityDbContext. По умолчанию в проект в папку Models добавляется файл IdentityModels.cs , который содержит определения классов пользователей и контекста данных:

    Public class ApplicationUser: IdentityUser { public async Task GenerateUserIdentityAsync (UserManager manager) { var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie); return userIdentity; } } public class ApplicationDbContext: IdentityDbContext { public ApplicationDbContext() : base("DefaultConnection", throwIfV1Schema: false) { } public static ApplicationDbContext Create() { return new ApplicationDbContext(); } }

    В приложении мы не работаем напрямую с классами IdentityUser и IdentityDbContext, а имеем дело с классами-наследниками.

    Класс ApplicationUser наследует от IdentityUser все свойства. И кроме того добавляет метод GenerateUserIdentityAsync() , в котором с помощью вызова UserManager.CreateIdentityAsync создается объект ClaimsIdentity . Данный объект содержит информацию о данном пользователе.

    Подобная архитектура позволяет взять уже готовый функционал и при необходимости добавить новый, например, добавить для пользователя новое свойство или добавить новую таблицу в бд.

    Я не буду подробно расписывать весь функционал AspNet Identity, который по умолчанию добавляется в проект, обозначу вкратце лишь основные возможности.

    Во-первых, чтобы задействовать AspNet Identity, в проект в папку App_Start добавляются два файла. Файл Startup.Auth.cs содержит класс запуска приложения OWIN. Поскольку AspNet Identity использует инфраструктуру OWIN, то данный класс является одним из ключевых и необходимых для работы.

    Файл IdentityConfig.cs содержит ряд дополнительных вспомогательных классов: сервисы для двухфакторной валидации с помощью email и телефона EmailService и SmsService , класс менеджера пользователей ApplicationUserManager , добавляющий к UserManager ряд дополнительных функций, и класс ApplicationSignInManager , используемый для входа и выхода с сайта.

    Базовая функциональность системы аутентификации и управления учетными записями расположена в двух контроллерах: AccountController и ManageController

    В AccountController определены методы для логина, регистрации, верификации кода, отправленного по email или по смс, сброс пароля, напоминание пароля, вход на сайт с помощью внешних сервисов. Контроллер ManageController используется для управления учетной записью и предполагает возможности по смене пароля и управлению телефонными номерами в системе. Для обоих контроллеров уже по умолчанию генерируются все необходимые представления и специальные модели представлений.

    Несмотря на то, что по умолчанию нам уже предоставляется готовый функционал, однако в ряде случаев, например, для отправки смс или электронной почты необходима дополнительная настройка. Теперь рассмотрим основные моменты системы AspNet Identity.