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

Вы создали 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 жаргона, и русский перевод не воспринимается как должно.

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

Небольшая справка, для тех, кто до сих пор путает эти два термина. Аутентификация — это проверка факта, кто к нам пытается зайти в систему. Чаще всего, аутентификация реализуется с помощью запроса у пользователя логина и пароля. Именно по ним и идёт распознавание пользователя.

Авторизация — это проверка на то, есть ли у данного пользователя разрешение выполнять данную функцию. Грубо говоря, чаще всего — это механизм ролей (например, если это администратор, то ему можно делать любые действия; если это пользователь — только читать сообщения).

В ASP.NET MVC 5 в очередной раз изменился механизм работы с авторизацией и аутентификацией. Теперь для этих задач используется OWIN (The Open Web Interface for .NET) - это спецификация определяющая интерфейс и описывающая взаимодействие между всеми компонентами. Короче говоря, это абстракция (middleware), которая отделяет от приложения обязанности аутентификации.

Благодаря OWIN, из коробки в ASP.NET MVC 5 появилось довольно большое количество крутых плюшек. Например, вы сразу же можете использовать аутентификацию через facebook или google. Кроме того, можно довольно просто сделать аутентификацию и через vkontakte.

Однако с OWIN есть и некоторые проблемы. Главная из них заключается в том, что этот механизм довольно новый, и в сети доступно мало примеров. Совсем не понятно, как расширить стандартного User-а, как изменить названия таблиц AspNetUsers, AspNetUserRoles, AspNetUserLogins, AspNetUserClaims, AspNetRoles, которые создаются по умолчанию, как расширить класс ролей (да и вообще, как работать с ролями — ведь изначально такого примера нет).

Я сам начал искать ответы на данные вопросы. Спустя некоторое время, нашёл цикл статей, описывающих ответы на вопросы, поставленные выше.

— базовая работа с аутентификацией.

— расширение Пользовательского класса, а также первичная работа с Ролями

— Расширение стандартного класса Ролей

Сергей Бакланов

Imports System.Data.SqlClient Imports System.Web.Security Public Class login Inherits System.Web.UI.Page Protected WithEvents txtName As System.Web.UI.WebControls.TextBox Protected WithEvents txtPassword As System.Web.UI.WebControls.TextBox Protected WithEvents lbl As System.Web.UI.WebControls.Label Protected WithEvents btnLogin As System.Web.UI.WebControls.Button #Region " Web Form Designer Generated Code " "This call is required by the Web Form Designer. Private Sub InitializeComponent() End Sub "NOTE: The following placeholder declaration is required by the Web Form Designer. "Do not delete or move it. Private designerPlaceholderDeclaration As System.Object Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init "CODEGEN: This method call is required by the Web Form Designer "Do not modify it using the code editor. InitializeComponent() End Sub #End Region Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load "Put user code to initialize the page here End Sub Private Sub btnLogin_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnLogin.Click Dim cn As New SqlConnection("server=localhost;database=FormAuthUsers;uid=sa;pwd=;") Dim cm As New SqlCommand("FindUser", cn) Dim dr As SqlDataReader Dim ticket As FormsAuthenticationTicket Dim n As Integer, strRoles As String, strEncrypted As String " Открываем соединение Try cn.Open() Catch ex As SqlException Response.Write(ex.Message) Exit Sub End Try " Задаём тип команды cm.CommandType = CommandType.StoredProcedure " Добавляем параметры имени Dim prmName = New SqlParameter("@Name", SqlDbType.NVarChar, 50) prmName.Value = txtName.Text cm.Parameters.Add(prmName) " Добавляем параметр пароля Dim prmPass = New SqlParameter("@Password", SqlDbType.NVarChar, 50) prmPass.Value = txtPassword.Text cm.Parameters.Add(prmPass) " Выполняем запрос n = cm.ExecuteScalar If n > 0 Then " Если пользователь с таким именем и паролем существует, то ищем его роли cm = Nothing cm = New SqlCommand("exec FindRoles "" & txtName.Text & """, cn) dr = cm.ExecuteReader() " Составляем список ролей While dr.Read If strRoles = "" Then strRoles &= dr(0) Else strRoles &= ", " & dr(0) End If End While " Создаём аутентификационный билет ticket = New FormsAuthenticationTicket(1, txtName.Text, DateTime.Now, _ DateTime.Now.AddMinutes(20), False, strRoles) " Шифруем билет strEncrypted = FormsAuthentication.Encrypt(ticket) " Сохраняем cookie-файл Response.Cookies.Add(New HttpCookie("UrlAuthz", strEncrypted)) " Возвращаемся на исходную страницу FormsAuthentication.RedirectFromLoginPage(txtName.Text, False) Else " Если пользователь не был найден, то выдаём сообщение об ошибке lbl.Visible = True End If End Sub End Class

В этом примере мы поместили в одну процедуру две операции проверки: одна - аутентификации, другая - авторизации. Сначала мы проходим аутентификацию, запрашивая данные на пользователя с таким-то именем и паролем из базы. Если пользователь не был найден, выводим соответствующее сообщение об ошибке (см. 4 строку снизу). Если же пользователь обнаружен, то мы определяем его роли, опять запрашивая информацию из базы данных. На основе полученных сведений о ролях формируется аутентификационный билет, который впоследствии шифруется и сохраняется в cookie-файле. И, наконец, пользователь благополучно возвращается на страницу default.aspx.

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

Листинг 7. default.aspx

AuthzByUrl Sub Page_Load(sender As Object, e As EventArgs) Handles MyBase.Load If HttpContext.Current.User.Identity.Name = "" Then lblLogin.Text = "You"re not registered, please login" Else lblLogin.Text = "You"re registered as " & _ HttpContext.Current.User.Identity.Name End If End Sub Sub btnLogin_Click(sender As Object, e As EventArgs) Handles btnLogin.Click Response.Redirect("login.aspx") End Sub You"re not registered, please login
GoTo:
  • Admin zone
  • User zone

admin.aspx

admin

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

Заимствование полномочий

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

По умолчанию режим заимствования полномочий в среде ASP.NET отключён. Для его активизации нужно добавить в файл Web.config тэг и присвоить его атрибуту impersonate значение true. Следующий фрагмент файла конфигурации проекта демонстрирует, как это должно выглядеть:

Web.config

Для демонстрации работы этого режима, используйте следующий код (листинг 8) в странице default.aspx:

default.aspx

Impersonation User: IsAuthenticated Authentication type Name WindowsIdentity: IsAuthenticated Authentication type Name

default.aspx.vb

Imports System.Security.Principal Public Class WebForm1 Inherits System.Web.UI.Page #Region " Web Form Designer Generated Code " "This call is required by the Web Form Designer. Private Sub InitializeComponent() End Sub "NOTE: The following placeholder declaration is required by the Web Form Designer. "Do not delete or move it. Private designerPlaceholderDeclaration As System.Object Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init "CODEGEN: This method call is required by the Web Form Designer "Do not modify it using the code editor. InitializeComponent() End Sub #End Region Protected WithEvents clmIsAuthU As System.Web.UI.HtmlControls.HtmlTableCell Protected WithEvents clmAuthTypeU As System.Web.UI.HtmlControls.HtmlTableCell Protected WithEvents clmNameU As System.Web.UI.HtmlControls.HtmlTableCell Protected WithEvents clmIsAuthW As System.Web.UI.HtmlControls.HtmlTableCell Protected WithEvents clmAuthTypeW As System.Web.UI.HtmlControls.HtmlTableCell Protected WithEvents clmNameW As System.Web.UI.HtmlControls.HtmlTableCell Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Dim wi As WindowsIdentity " User.Identity With context.User.Identity clmIsAuthU.InnerText = .IsAuthenticated.ToString clmAuthTypeU.InnerText = .AuthenticationType.ToString clmNameU.InnerText = .Name End With " System.Security.Principal.WindowsIdentity wi = WindowsIdentity.GetCurrent With wi clmIsAuthW.InnerText = .IsAuthenticated.ToString clmAuthTypeW.InnerText = .AuthenticationType.ToString clmNameW.InnerText = .Name End With End Sub End Class

В обработчике события загрузки формы для получения идентификатора пользователя объекта WindowsIdentity используется метод GetCurrent, возвращающий идентификатор учётной записи, от имени которой функционирует процесс ASP.NET.

При запуске этого приложения с отключенным заимствованием полномочий () перед вами появится экран, представленный на рисунке 3. Как можно наблюдать, при отключенном заимствовании полномочий в объекте WindowsIdentity содержится идентификатор системного пользователя ASPNET.

Теперь, если вы активизируете заимствование полномочий, то увидите результат, представленный в таблице 1.

Таблица 1. Включенное заимствование полномочий и отключенный анонимный доступ

WindowsIdentity:

IsAuthenticated True
Authentication type Negotiate
Name BIGDRAGON\B@k$
IsAuthenticated True
Authentication type NTLM
Name BIGDRAGON\B@k$

Как видите, результаты одинаковые, поскольку оба объекта получают информацию о текущем пользователе. Но предыдущие два примера были ориентированы на условия с запрещённым анонимным доступом для аутентификации средствами Windows. Если разрешить анонимный доступ к приложению, то объект User.Identity не вернёт никакого имени пользователя, а его свойство IsAuthenticated будет иметь значение False. В этом нет ничего удивительного, т. к. если в системе аутентификации Windows разрешён анонимный доступ, то пользователь работает анонимно, то есть не проходит аутентификацию.

В это же время у объекта WindowsIdentity свойство IsAuthenticated будет иметь значение True, а в качестве имени пользователя будет стоять строка следующего формата: IUSR_ , как показано в таблице 2.

Таблица 2. Заимствование полномочий и анонимный доступ разрешены

WindowsIdentity:

IsAuthenticated False
Authentication type
Name
IsAuthenticated True
Authentication type NTLM
Name BIGDRAGON\IUSR_BIGDRAGON

Свойство name объекта WindowsIdentity имеет такое значение потому, что оно возвращает идентификатор пользователя, под которым работает процесс ASP.NET, а не пользователь Web-узла. А поскольку процесс не может работать анонимно, то он получает имя от IIS, если его невозможно получить от текущего пользователя.

Если вы были внимательны при выполнении операций по разрешению/запрету анонимного доступа, то могли заметить, что в поле Имя пользователя была как раз подставлена строка вышеуказанного формата: IUSR_ (рис. 4).

Рисунок 4. В поле Имя пользователя содержится строка, определяющая имя процесса ASP.NET при анонимном доступе

Кроме того, в среде ASP.NET предусмотрена возможность указания, от кого именно заимствовать полномочия. С этой целью в тэге предусмотрен атрибут userName, в котором и указывается имя пользователя, у которого необходимо заимствовать полномочия.

В следующем фрагменте из файла Web.config, показано, как это должно выглядеть на практике:

Web.config :

После запуска тестового приложения с такой конфигурацией на выполнение, состояние объекта User.Identity останется неизменным, а вот в свойстве name объекта WindowsIdentity вместо строки формата IUSR_ появится имя, указанное в атрибуте userName тэга из файла конфигурации проекта, как показано в таблице 3.

Таблица 3. Процесс ASP.NET работает от имени конкретного пользователя

WindowsIdentity:

IsAuthenticated False
Authentication type
Name
IsAuthenticated True
Authentication type NTLM
Name BIGDRAGON\AlBa

Если вы отмените анонимный доступ, то объект User.Identity будет содержать идентификатор зарегистрированного пользователя, а в объекте WindowsIdentity по-прежнему будет оставаться имя пользователя, переданное через атрибут userName.

На этом закончим изучение авторизации как средства безовасности среды ASP.NET. Дальнейшее изучение механизма авторизации требует изучения средств авторизации Windows. Среди них можно выделить списки контроля доступа на низком и высоком уровне, контроль доступа архитектуры клиент/сервер, ролевая безопасность Windows и так далее.

Если эта тема вас действительно заинтересовала, то вы можете найти массу материала в библиотеке MSDN:

  • Темы безопасности в рамках ASP.NET доступны в следующей ветке библиотеки MSDN: .NET Development/.NET Security;
  • По вопросам безопасности всей системы в целом следует обращаться к разделу Security/Security (General)/SDK Documentation.

Если у вас нет MSDN-библиотеки, то к её самому свежему изданию можно обратиться через интернет по адресу: http://msdn.microsoft.com/library/ .

В третьей, заключительной части данной статьи, мы рассмотрим очень актуальную и интересную тему - криптографию. Кроме теории и алгоритмов криптографии мы рассмотрим с различных сторон средства шифрования, предоставляемые платформой.NET Framework, и создадим простенький метод шифрования.

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

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

Некоторые из этих проверок Windows осуществляет автоматически, а другие можно кодировать декларативно, используя файл web.config. Но некоторые проверки придется выполнять непосредственно в коде с использованием объекта IPrincipal.

Авторизация URL

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

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

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

Правила авторизации

Другими словами, существуют два типа правил: разрешить (allow) и запретить (deny). Тех и других можно добавлять столько угодно. Каждое правило идентифицирует одного или более пользователей либо ролей (групп пользователей). Вдобавок можно использовать атрибут verbs, чтобы создавать правила, применимые только к специфичным типам HTTP-запросов (GET, POST, HEAD ИЛИ DEBUG).

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

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

Можно использовать дополнительный шаблонный символ - звездочку (*), которая представляет всех пользователей. Например, следующий раздел открывает доступ аутентифицированным и анонимным пользователям:

Такое правило требуется редко, потому что оно уже присутствует в файле machine.config. После того как среда ASP.NET применит все правила из файла web.config, она применяет правила из machine.config. В результате любому пользователю, кому явно закрыт доступ, автоматически его получает.

Теперь посмотрим, что произойдет, если в раздел добавить более одного правила:

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

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

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

Конфигурирование доступа для определенных пользователей

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

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

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

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

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

Следует обратить внимание на одну деталь. Формат имен пользователей в этих примерах предполагает аутентификацию с помощью форм. При такой аутентификации имя пользователя назначается при вызове метода RedirectFromLoginPage(). В этой точке UrlAuthorizationModule также использует это имя и проверит его по списку правил авторизации.

Контроль доступа к определенным каталогам

Общепринятое проектное решение для приложения предусматривает размещение файлов, которые требуют аутентификации, в отдельном каталоге. Благодаря конфигурационным файлам ASP.NET, этот подход реализуется просто. Оставьте элемент 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" })) { Вход Email @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") Войти }

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

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