Авторизация на основе ролей (Roles API)

199

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

Чтобы использовать эту инфраструктуру, первым делом ее понадобится сделать доступной. Для этого необходимо либо отметить флажок Enable Roles for This Web Site (Разрешить роли для этого веб-сайта) при работе с мастером безопасности, либо щелкнуть на ссылке Enable Roles (Разрешить роли) на вкладке Security (Безопасность) в WAT:

Конфигурирование Roles API

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

<configuration>
  <system.web>
    <roleManager enabled="true">
       <!-- Обратите внимание, что роли можно использовать также с аутентификацией Windows; 
            применять аутентификацию с помощью форм не обязательно. Часто также бывает очень 
            удобно отобразить учетные записи Windows на специальные роли.
            Но сейчас для примера используется аутентификация с помощью форм. -->
    </roleManager>
  </system.web>
</configuration>

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

  1. Создайте хранилище данных либо с помощью aspnet_regsql.exe, либо выполнив командные сценарии T-SQL, находящиеся в каталоге .NET Framework. И то, и другое объяснялось в статье «Настройка Membership API».

  2. Сконфигурируйте поставщика ролей для использования ранее созданного хранилища.

Сконфигурировать поставщика ролей можно с помощью дескриптора <roleManager>. Можно либо использовать другую базу данных, либо полностью другое хранилище. Вдобавок посредством дескриптора <roleManager> можно настроить некоторые свойства, которые не могут быть сконфигурированы в среде WAT:

<configuration>
  <connectionStrings>
    <add name="MyMembershipConnString" 
         connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=aspnetdb;Integrated Security=True"/>
  </connectionStrings>
  <system.web>
    <roleManager enabled="true"
                 defaultProvider="CustomSqlProvider"
                 cacheRolesInCookie="true"
                 cookieName="MyRolesCookie"
                 cookieTimeout="30"
                 cookieSlidingExpiration="true"
                 cookieProtection="All">
      <providers>
        <add name="CustomSqlProvider" type="System.Web.Security.SqlRoleProvider"
             connectionStringName="MyMembershipConnString"
             applicationName="security"/>
      </providers>
    </roleManager>
  </system.web>
</configuration>

После добавления этого элемента конфигурации в файл web.config можно выбрать поставщика через WAT. Для этого нужно просто перейти в панель Provider (Поставщик) и щелкнуть на ссылке Select a Different Provider for Each Feature (Выбрать отдельного поставщика для каждой функции). Выбор поставщика в WAT показан на рисунке ниже:

Выбор поставщика ролей в WAT

В таблице ниже перечислены свойства, которые можно конфигурировать с помощью дескриптора <roleManager>:

Свойства конфигурации <roleManager>
Свойство Описание
enabled

Указывает, включен интерфейс Roles API (true) или нет (false)

defaultProvider

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

cacheRolesInCookie

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

cookieName

Если роли кэшируются в cookie-наборе, в этом атрибуте можно указать имя cookie-набора

cookiePath

Указывает корневой путь cookie-набора, где кэшируются роли для приложения. Это позволяет указать часть приложения, для которого cookie-набор действителен. Значение по умолчанию - /

cookieProtection

Cookie-набор с ролями может быть зашифрован и подписан. В этом атрибуте указывается уровень защиты. Допустимые значения: All (шифровать и подписывать), Encryption, Validation и None

cookieRequireSSL

Указывает, должен ли cookie-набор возвращаться ASP.NET только в случае включенного SSL (true) или в любом другом случае (false). Если атрибут установлен в true, a SSL не активизирован, исполняющая среда просто не возвращает cookie-набор, а потому проверка ролей всегда осуществляется через лежащий в основе поставщик ролей

cookieTimeout

Получает или устанавливает таймаут для cookie-набора ролей в минутах. По умолчанию составляет 30 минут

cookieSlidingExpiration

Указывает, должен таймаут cookie-набора расширяться при каждом запросе пользователя к приложению ASP.NET (true) или нет (false). Значение по умолчанию - true

createPersistentCookie

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

domain

Указывает допустимый домен для cookie-набора ролей

maxCachedResults

Указывает максимальное количество имен ролей в cookie-наборе

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

Дополнительные свойства SqlRoleProvider
Свойство Описание
name

Имя поставщика. Это имя может быть использовано в атрибуте defaultProvider, описанном в предыдущей таблице, для указания поставщика для приложения

applicationName

Имя приложения, для которого выполняется управление ролями

description

Краткое дружественное описание поставщика

connectionStringName

Имя строки соединения, указанной в разделе <connectionStrings> файла web.config, которая будет использоваться для подключения к хранилищу ролей

В дополнение к SqlRoleProvider платформа ASP.NET содержит поставщик, который может быть использован на Windows Server 2003 с помощью диспетчера авторизации (Authorization Manager). Как будет показано позже, можно также создать и применять собственные специальные поставщики. В таблице ниже перечислены основные классы, входящие в Roles API:

Основные классы Roles API
Класс Описание
RoleManagerModule

Этот модуль гарантирует, что роли будут назначены текущему зарегистрированному пользователю при каждом запросе. Он прикрепляется к событию Application _AuthenticateRequest и создает экземпляр RolePrincipal, который содержит роли, назначенные пользователю, если Roles API разрешен в web.config

RoleProvider

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

RoleProviderCollection

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

SqlRoleProvider

Реализация поставщика ролей для баз данных SQL Server

WindowsTokenRoleProvider

Получает информацию о ролях для аутентифицированного пользователя Windows на основе групповых ассоциаций Windows

AuthorizationStoreRoleProvider

Реализация поставщика ролей для сохранения ролей в хранилище на основе диспетчера авторизации (Authorization Manager). Диспетчер Authorization Manager входит в состав Windows Server 2003 и позволяет декларативно определять роли приложений и привилегии этих ролей. Разрабатываемое приложение может использовать Authorization Manager для программной авторизации пользователей

Roles

Класс Roles служит первичным интерфейсом к хранилищу ролей. Этот класс включает методы для программного управления ролями

RolePrincipal

Реализация IPrincipal, которая подключается к сконфигурированным ролям с аутентифицированным пользователем. Создается автоматически модулем RoleManagerModule, если интерфейс Roles API разрешен

После конфигурирования Roles API можно создавать пользователей и роли, а затем назначать пользователей на эти роли с помощью либо WAT, либо класса Roles в коде. На вкладке Security (Безопасность) в WAT щелкните на ссылке Create or Manage Rotes (Создание ролей и управление ими). После этого можно будет создавать роли, а также добавлять к ним пользователей:

Добавление пользователей к ролям

После конфигурирования пользователей и ролей потребуется настроить правила авторизации для приложения. Все необходимые детали уже известны. Нужно просто настроить соответствующие разделы <authorization> в различных каталогах приложения. К счастью, делать это вручную не придется. Как показано на рисунке ниже, в разделе Add New Access Rule (Добавить новое правило доступа) на вкладке Security доступно множество переключателей для конфигурирования правил:

Конфигурирование правил доступа с помощью WAT

Когда включен Roles API, модуль RoleManagerModule автоматически создает экземпляр RolePrincipal, содержащий как идентичность аутентифицированного пользователя, так и его роли. RolePrincipal - это просто специальная реализация IPrincipal, базового интерфейса для всех классов принципалов. Поэтому он поддерживает функциональность по умолчанию, такую как доступ к аутентифицированной идентичности и метод для верификации условий членства в ролях через метод IsInRole().

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

protected void Page_Load(object sender, EventArgs e)
{
        if (this.User.Identity.IsAuthenticated)
        {
            RolePrincipal rp = (RolePrincipal)User;
            StringBuilder RoleInfo = new StringBuilder();
            RoleInfo.AppendFormat("<h2>Привет {0}</h2>", rp.Identity.Name); 
            RoleInfo.AppendFormat("<b>Провайдер:</b>{0}<br>", rp.ProviderName); 
            RoleInfo.AppendFormat("<b>Версия:</b> {0}<BR>", rp.Version); 
            RoleInfo.AppendFormat("<b>Дата создания:</b> {0}<BR>", rp.ExpireDate.ToShortDateString()); 
            RoleInfo.Append("<b>Роли:</b> "); 
            string[] roles = rp.GetRoles();
            for (int i = 0; i < roles.Length; i++) 
            {
                if (i > 0) RoleInfo.Append(", ");
                RoleInfo.Append(roles[i]);
            }
            
            LabelRoleInformation.Text = RoleInfo.ToString();
        }
}

Использование элемента управления LoginView с ролями

Ранее вы ознакомились с подробностями об элементах управления безопасностью, поставляемых в составе ASP.NET. Одним из них был элемент LoginView. В статье, приведенной по ссылке, он применялся для отображения различных элементов управления для анонимных и зарегистрированных пользователей. Для реализации своей функциональности этот элемент управления использует шаблоны - <LoggedInTemplate> и <AnonymousTemplate>.

Этот элемент управления поддерживает один дополнительный шаблон, позволяющий строить различные представления на основе ролей, к которым принадлежит пользователь. Для этой цели потребуется добавить шаблон RoleGroup с элементами управления <asp:RoleGroup>. Внутри каждого элемента <asp:RoleGroup> в свойстве Roles указывается разделенный запятыми список ролей, для которых будет отображаться соответствующий шаблон <ContentTemplate>:

<asp:LoginView ID="LoginView1" runat="server">
	<LoggedInTemplate>
	    <h2>Этот текст увидят все аутентифицированные пользователи</h2>
	</LoggedInTemplate>
	<RoleGroups>
	    <asp:RoleGroup Roles="Admin">
	        <ContentTemplate>
		<h2>Только для админов</h2>
	        </ContentTemplate>
	    </asp:RoleGroup>
	    <asp:RoleGroup Roles="Guest">
	        <ContentTemplate>
		<h2>Увидят только гости</h2
	        </ContentTemplate>
	    </asp:RoleGroup>
	    <asp:RoleGroup Roles="User, Moderator">
	        <ContentTemplate>
		<h2>Увидят обычные пользователи</h2>
	        </ContentTemplate>
	    </asp:RoleGroup>
	</RoleGroups>
</asp:LoginView>

Элемент управления LoginView в приведенном коде отображает различное содержимое для зарегистрированных пользователей и для пользователей, назначенных на специфические роли. Например, для пользователей в роли Admin он отображает текст "Только для админов", в то время как для пользователей из роли Guest выводится текст "Увидят только гости". LoggedInTemplate будет отображаться только для аутентифицированных пользователей без соответствующего элемента <asp:RoleGroup>. Как только подходящая группа ролей для пользователя найдена, содержимое LoggedInTemplate не отображается.

Пример использования ролей

Программный доступ к ролям

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

Большинство свойств класса Roles просто отображаются на настройки дескриптора <roleManager>, описанные выше. Поэтому в таблице ниже перечислены только дополнительные свойства и методы класса Roles, которые можно использовать для программного доступа и управления Roles API:

Члены класса Roles
Член Описание
Provider

Возвращает поставщика, в данный момент используемого приложением

Providers

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

AddUserToRole()

Принимает имя пользователя и имя роли как строковые параметры и добавляет указанного пользователя к указанной роли

AddUserToRoles()

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

AddUsersToRole()

Принимает массив строк с именами пользователей и строковый параметр с именем роли и добавляет указанных пользователей к указанной роли

AddUsersToRoles()

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

CreateRole()

Создает новую роль

DeleteRole()

Удаляет существующую роль

FindUsersInRole()

Принимает строку, представляющую имя роли, и вторую строку, указывающую шаблон соответствия имен пользователей. Возвращает список пользователей, ассоциированных с ролью и соответствующих шаблону - второму параметру метода (usernameToMach)

GetAllRoles()

Возвращает строковый массив, содержащий имена ролей, доступных в хранилище ролей сконфигурированного поставщика

GetRolesForUser()

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

GetUsersInRole()

Возвращает список пользователей, которые ассоциированы с ролью, переданной в параметре

IsUserInRole()

Возвращает true, если указанный пользователь является членом указанной роли

RemoveUserFromRole()

Удаляет отдельного пользователя из роли

RemoveUserFromRoles()

Удаляет пользователя из всех указанных ролей

RemoveUsersFromRole()

Удаляет всех указанных пользователей из одной указанной роли

RemoveUsersFromRoles()

Удаляет всех указанных пользователей из всех указанных ролей

RoleExists()

Возвращает true, если роль существует, и false в противном случае

Удачным применением программного доступа к ролям является автоматическое ассоциирование пользователей с ролями при их самостоятельной первичной регистрации. Конечно, это удобно только для определенных ролей. Предположим, что приложение поддерживает роль под названием User, и каждый индивидуальный пользователь должен быть членом этой роли. Если вы регистрируете пользователя сами, то можете установить это отношение вручную. Но если приложение поддерживает автоматическую регистрацию для пользователей из Интернета, это делать нельзя. Поэтому нужно как-то обеспечивать автоматическую ассоциацию каждого пользователя с ролью User.

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

Определенно, для этой цели потребуется событие уровня приложения, хотя оно и не будет инициировано приложением конфигурации, поскольку это - другое приложение. Остается только перехват события Application.AuthenticateRequest - внутри этого события можно проверить, является ли пользователь членом роли User. Если нет, пользователь может быть добавлен автоматически. Это смещает задачу автоматического добавления пользователя к роли в точку аутентификации, которая по определению касается каждого пользователя. Чтобы сделать это, необходимо добавить в проект глобальный класс приложения (global.asax) и поместить в него приведенный ниже код:

protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
        if (User != null && User.Identity.IsAuthenticated && Roles.Enabled)
        {
            string UserRoleName = 
                ConfigurationManager.AppSettings["UserRoleName"];
            
            if (!Roles.IsUserInRole(UserRoleName) && Roles.RoleExists(UserRoleName))
            {
                Roles.AddUserToRole(User.Identity.Name, UserRoleName);
            }
        }
}

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

Этот код читает имя роли User из конфигурационного файла, так что оно не кодируется жестко в приложении. Затем используется класс Roles для проверки того, принадлежит ли данный пользователь к этой роли, и если нет, проверяется существование самой роли. Если пользователь не ассоциирован с ролью, но уже зарегистрирован в системе, вызывается метод Roles.AddUsersToRole() для программного добавления пользователя к роли User.

Можно было бы решить, что в приведенном коде необходимо использовать User.IsInRole(), однако это неправильно. Когда вызывается Application_AuthenticateRequest уровня приложения, сам модуль RoleManagerModule пока не вызван. Поэтому экземпляр RolePrincipal с ассоциацией пользователя и его ролей еще не создан, а потому вызов вроде User.IsInRole("Everyone") вернет false. Позднее в коде страницы, например, в процедуре Page_Load, экземпляр RolePrincipal уже инициализирован, и вызов User.IsInRole("Everyone") отработает правильно.

Пройди тесты
Лучший чат для C# программистов