Приветствую.
Сегодня мне бы хотелось рассказать в совсем небольшом уроке (уровень скорее для очень начинающих), как можно достаточно быстро и легко настроить аутентификацию пользователей, а так же авторизацию при их доступе к некоторому функционалу на Вашем сайте, используя штатные средства фреймворка MVC(4).
Вводная
Я сейчас пишу личный простенький сайт для учета и ведения расходов, доходов, напоминания о периодических платежах (жкх, кредиты, школа и т.п.) + аналитика (в основном диаграммы), поскольку меня и мою жену функциональность Google Docs устраивать перестала.
Соответственно, встал вопрос о том, как закрыть информацию, в данном случае финансового состояния семьи от посторонних глаз под аутентификацию а так же распределить роли доступа (авторизация) — что могут жена, ребенок, анонимные пользователи, а что может администратор глава семьи.
Я потратил несколько вечеров на поиск, возню с пользовательскими провайдерами и их настройкой, пока не понял, что весь этот код не нужен мне на данном этапе. Следствием стало переписывание статьи по когда-то вбитому мне в голову принципу — чем меньше изменений вносится в любой объект, будь то конфигурационный файл, документ или код на текущем уровне моих знаний либо представлений, тем потом будет проще. Возможно я упустил какие-то важные нюансы (я начинающий ), поэтому буду рад как критике, так и подсказкам аудитории.
Я считаю, что пользователь уже создал хотя бы один простой сайт на данном фреймворке, например, воспользовавшись базовой инструкцией по созданию простого каталога Ваших фильмов на фреймворке MVC4 (у меня ушло около 20 минут).
Или же изучая MVC по второй, очень хорошей и более сложной инструкцией, по созданию онлайн магазина продажи музыкальных альбомов, (она касается MVC3, но, тем не менее, изучение MVC я рекомендую начинать с данной инструкции).
В процессе изучения второй инструкции я натолкнулся на проблемы, связанные с тем, что некоторые вещи в MVC4 изменились, по сравнению с MVC3, и брать устаревший код контроллера от предыдущей модели фреймворка и модели считаю плохой идеей, поэтому решил разобраться с этой задачей.
Предварительное условие:
При создании нового проекта MVC4 по умолчанию вверху, справа при открытии сайта есть небольшое меню из двух пунктов — «Регистрация» и «Выполнить вход».
Техническое задание:
«Активировать» и запустить на нашем сайте ролевую модель доступа с распределением функционала.
По умолчанию все, что нам надо, уже есть и работает, спасибо разработчикам, но кое что нам придется дописать самостоятельно.
Рабочая среда:
У меня стоят привычные мне русскоязычные варианты Visual Studio 2012 Express, .Net 4.5, SQL 2012 и MVC4 (а так же TFS2012 Express. Всё это живет на Windows 2008R2), в случае установки у Вас английской локализации названия, пункты меню и другие элементы интерфейса будут называться по другому, поэтому я по максимуму абстрагировался от скриншотов экрана.
Решение задачи
Подготовка хранилища
Я предпочитаю отделять данные приложения от авторизации, поэтому создал отдельную базу данных users,
- В каталоге App_Data надо создать новую, пустую базу данных, назовём её «users» для идентификации её содержимого.
- В нашем web приложении надо описать подключение к нашей новой базе данных, для этого надо открыть файл web.config, который находится в корневом каталоге нашего приложения, найти (обычно в самом начале файла), пункт, описывающий соединения с источниками данных. (я взял конфиг из уже работающего сайта, поэтому у Вас верным и совпадающим пунктом будет только имя соединения DefaultConnection).
<connectionStrings> <add name="DefaultConnection" connectionString="Data Source=(LocalDb)v11.0;Initial Catalog=aspnet-MyMoney-2132141343;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|users.mdf" providerName="System.Data.SqlClient" /> <add name="payDBContext" connectionString="Data Source=(LocalDb)v11.0;Initial Catalog=aspnet-MyMoney-data;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|MyMoney.mdf" providerName="System.Data.SqlClient" /> </connectionStrings>
Немного подробнее:
В данном списке подключений у нас есть соединение по умолчанию «DefaultConnection», у нас оно будет использоваться только для хранения пользовательской информации, поэтому мы это менять не будем, все данные приложения будут храниться в другой базе данных, payDBContext.
В настройках DefaultConnection мы меняем:- Начальный каталог «Initial Catalog=aspnet-MyMoney-2132141343», это имя базы данных в момент прикрепления БД к серверу БД, поэтому в случае работы нескольких баз данных с этим одинаковым именем, могут быть неоднозначности
- пункт «AttachDBFilename=|DataDirectory|users.mdf», имя файла базы данных, где будет храниться информация о пользователях и ролях. Имя базы данных, созданной нами в каталоге App_Data, «users.mdf».
- Теперь можно запустить сайт, зайти и зарегистрировать, например двух пользователей Admin и User
- После чего в обозревателе баз данных прямо в студии открываем структуру нашей БД
И получим нечто подобное
- Открываем табличку «UserProfile» в режиме редактирования, фактически нас интересуют UserID для созданных ранее пользователей Admin и User.
- Открываем таблицу «webpages_Roles» в режиме заполнения, и вводим роль Admin, в колонку «RoleName», идентификатор заполнится автоматически, и вводим вторую роль «User». Запоминаем полученные для ролей идентификаторы.
Открываем в режиме заполнения таблицу «webpages_UsersInRoles»
и вводим в соответствующие поля ID пользователя и ID соответствующей ему роли, но с небольшим отличием:
Мы хотим, чтобы Администратор входил как в роль администратора, так и в роль пользователя. Поэтому в этот раз строк будет 3.И получим нечто подобноеУ меня за работу с финансовой информацией отвечает несколько контроллеров, поэтому в описании каждого контроллера (можно закрыть или наоборот, открыть отдельные методы) надо вставить соответствующую настройку доступа:
[Authorize(Roles = "Admin")]
namespace MyMoney.Controllers { [Authorize(Roles = "Admin")] public class catController : Controller {
Или же вставить такую строку для входа в методы нескольких ролей
[Authorize(Roles = "Admin, User")]
Теперь я хочу в зависимости от роли пользователя немного изменить список меню.
- В каталоге /Views/Shared я создаю частичное представление (оно же Partial View) с названием "_Menu"
исходный код, кстати, кто подскажет как его лучше оптимизировать, а то утянул
@{ var menus = new[] { new { LinkText="На главную", ActionName="Index",ControllerName="Home",Roles="All" }, new { LinkText="О себе", ActionName="About",ControllerName="Home",Roles="All" }, new { LinkText="Контакты", ActionName="Contact",ControllerName="Home",Roles="All" }, new { LinkText="Финансы", ActionName="Index",ControllerName="payments",Roles="Admin,User" }, }; } <ul id="menu"> @if (HttpContext.Current.User.Identity.IsAuthenticated) { String[] roles = Roles.GetRolesForUser(); var links = from item in menus where item.Roles.Split(new String[] { "," }, StringSplitOptions.RemoveEmptyEntries) .Any(x => roles.Contains(x) || x == "All") select item; foreach (var link in links) { @: <li> @Html.ActionLink(link.LinkText, link.ActionName,link.ControllerName)</li> } } else{ var links = from item in menus where item.Roles.Split(new String[]{","},StringSplitOptions.RemoveEmptyEntries) .Any(x=>new String[]{"All","Anonymous"}.Contains(x)) select item; foreach ( var link in links){ @: <li> @Html.ActionLink(link.LinkText, link.ActionName, link.ControllerName)</li> } } </ul>
- Теперь надо его подключить в разметку /Views/Shared/_Layout.cshtml
Ищем в файле _Layout.cshtml этот код
<section id="login"> @Html.Partial("_LoginPartial") </section> <nav> <ul id="navlist"> <li class="first"><a href="@Url.Content("~")" id="current">Home</a></li> <li><a href="@Url.Content("~/Store/")">Store</a></li> <li>@{Html.RenderAction("CartSummary", "ShoppingCart");}</li> <li><a href="@Url.Content("~/StoreManager/")">Admin</a></li> </ul> </nav>
и меняем блок
<nav> ... </nav>
на
<nav> @Html.Partial("~/Views/Shared/_Menu.cshtml") </nav>
- У меня есть один контроллер с коротким именем, использующий одно представление как меню, вот код представления.
Набросал по быстрому, чтобы показать работу с ролями.код представления@{ var menus = new[] { new { LinkText="Home", ActionName="Index",ControllerName="Home",Roles="All" }, new { LinkText="About", ActionName="About",ControllerName="Home",Roles="Anonymous" }, new { LinkText="Contact", ActionName="Contact",ControllerName="Home",Roles="Anonymous" }, new { LinkText="Добавить платёж", ActionName="Create",ControllerName="pay",Roles="Admin,User" }, new { LinkText="Просмотр платежей", ActionName="Index",ControllerName="pay",Roles="Admin,User" }, new { LinkText="Добавить категорию", ActionName="Create",ControllerName="cat",Roles="Admin" }, new { LinkText="Просмотр категорий", ActionName="Index",ControllerName="cat",Roles="Admin,User" }, new { LinkText="Добавить пользователя", ActionName="Create",ControllerName="user",Roles="Admin" }, new { LinkText="Просмотр пользователей", ActionName="Index",ControllerName="user",Roles="Admin" }, new { LinkText="Добавить тип", ActionName="Create",ControllerName="type",Roles="Admin" }, new { LinkText="Просмотр типов", ActionName="Index",ControllerName="type",Roles="Admin" }, //new { LinkText="", ActionName="",ControllerName="",Roles="Administrator" }, }; } @{ ViewBag.Title = "Управление финансами"; } <h2>Управление финансами</h2> <p>Вы: @User.Identity.Name</p> <p>Вы входите в группы:</p> <table> <tr> @{ foreach (string item in Roles.GetRolesForUser()) { <li>@item</li> } } </tr> </table> <ul id="list"> @if (HttpContext.Current.User.Identity.IsAuthenticated) { String[] roles = Roles.GetRolesForUser(); var links = from item in menus where item.Roles.Split(new String[] { "," }, StringSplitOptions.RemoveEmptyEntries) .Any(x => roles.Contains(x) || x == "All") select item; foreach (var link in links) { @: <li> @Html.ActionLink(link.LinkText, link.ActionName, link.ControllerName)</li> } } else { var links = from item in menus where item.Roles.Split(new String[] { "," }, StringSplitOptions.RemoveEmptyEntries) .Any(x => new String[] { "All", "Anonymous" }.Contains(x)) select item; foreach (var link in links) { @: <li> @Html.ActionLink(link.LinkText, link.ActionName, link.ControllerName)</li> } } </ul>
Теперь надо очистить, пересобрать приложение и можно запускать. Если зная ссылку на один из контроллеров, закрытых для анонимного входа, попробовать зайти — получите форму авторизации с последующим обратным переходом на страничку, которая являлась инициатором авторизации.
Выглядит это так:Анонимный входВход с административной рольюВход с ролью пользователяЗамечания напоследокЕдинственный минус — надо самому дописывать методы для добавления/удаления пользовательских профилей — не лезть же каждый раз в таблички ролей, профилей и т.п.Второе, о чем я сейчас думаю — надо в свою БД финансов привязать получение данных текущего авторизованного пользователя, т.к. сейчас в гугль докс приходится руками выбирать, кто был инициатором расхода/дохода — а вводить два раза информацию глупо. А в идеале — получать авторизацию и из Windows сессии, если броузер IE.
Да, за некоторые фрагменты кода меня, как начинающего надо больно бить по рукам, поэтому я с удовольствием приму поправки и улучшения данного кода.
Спасибо за внимание, надеюсь я кому-то помог.
- В каталоге /Views/Shared я создаю частичное представление (оно же Partial View) с названием "_Menu"
Автор: foxmuldercp