Приветствую. Сегодня мне бы хотелось рассказать в совсем небольшом уроке (уровень скорее для очень начинающих), как можно достаточно быстро и легко настроить аутентификацию пользователей, а так же авторизацию при их доступе к некоторому функционалу на Вашем сайте, используя штатные средства фреймворка MVC(4).
Вводная
Я сейчас пишу личный простенький сайт для учета и ведения расходов, доходов, напоминания о периодических платежах (жкх, кредиты, школа и т.п.) + аналитика (в основном диаграммы), поскольку меня и мою жену функциональность Google Docs устраивать перестала. Соответственно, встал вопрос о том, как закрыть информацию, в данном случае финансового состояния семьи от посторонних глаз под аутентификацию а так же распределить роли доступа (авторизация) - что могут жена, ребенок, анонимные пользователи, а что может администратор глава семьи.
UPD: описал способы создания пользователей, ролей более правильным способом (не надо лезть напрямую в БД) Код, показывающий меню, стоит перевести в более правильный вид, соответствующий идеологии MVC, поскольку текущий код далек от образцового и написан быстро, для демонстрации, я над этим работаю.
Небольшое предисловие - Причины, побудившие меня написать эту статью и немного заметок для начинающих">
Проект создавался в рамках изучения C#, MVC4 - я новичок. Я потратил несколько вечеров на поиск, возню с пользовательскими провайдерами и их настройкой, пока не понял, что весь этот код не нужен мне на данном этапе. Следствием стало переписывание статьи по когда-то вбитому мне в голову принципу - чем меньше изменений вносится в любой объект, будь то конфигурационный файл, документ или код на текущем уровне моих знаний либо представлений, тем потом будет проще. Возможно я упустил какие-то важные нюансы (я начинающий ), поэтому буду рад как критике, так и подсказкам аудитории.
В процессе изучения второй инструкции я натолкнулся на проблемы, связанные с тем, что некоторые вещи в 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).
Немного подробнее: В данном списке подключений у нас есть соединение по умолчанию "DefaultConnection", у нас оно будет использоваться только для хранения пользовательской информации, поэтому мы это менять не будем, все данные приложения будут храниться в другой базе данных, payDBContext. В настройках DefaultConnection мы меняем:
Начальный каталог "Initial Catalog=aspnet-MyMoney-2132141343", это имя базы данных в момент прикрепления БД к серверу БД, поэтому в случае работы нескольких баз данных с этим одинаковым именем, могут быть неоднозначности
пункт "AttachDBFilename=/DataDirectory/\users.mdf", имя файла базы данных, где будет храниться информация о пользователях и ролях. Имя базы данных, созданной нами в каталоге App_Data, "users.mdf".
Теперь можно запустить сайт, зайти и зарегистрировать, например двух пользователей Admin и User
После чего в обозревателе баз данных прямо в студии открываем структуру нашей БД
И получим нечто подобное
Для добавления ролей можно в accountmodel.cs добавить такой код:
public class UsersContext : DbContext
{
public UsersContext()
: base("users")
{
}
public DbSet<UserProfile> UserProfiles { get; set; }
public DbSet<webpages_Roles> webpages_Roles { get; set; } // добавляем таблицу ролей
}
// описание таблицы ролей
[Table("webpages_Roles")]
public class webpages_Roles
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int RoleId { get; set; }
[Display(Name = "Имя роли")]
public string RoleName { get; set; }
}
Тогда мы сможем после пересборки проекта создать контроллер roles (не забудьте закрыть его для пользователей с административной ролью)
Пример добавления пользователей лучше посмотреть в контроллере "Account", методы Register и модицифировать его для добавления пользователя в одну или несколько ролей, используя Roles.AddUserToRole(), например, закрыв для администраторской роли, либо добавить свой контроллер. который будет добавлять пользователя в роль.
У меня за работу с финансовой информацией отвечает несколько контроллеров, поэтому в описании каждого контроллера (можно закрыть или наоборот, открыть отдельные методы) надо вставить соответствующую настройку доступа:
[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
У меня есть один контроллер с коротким именем, использующий одно представление как меню, вот код представления. Набросал по быстрому, чтобы показать работу с ролями.
код представления
@{
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.
Да, за некоторые фрагменты кода меня, как начинающего надо больно бить по рукам, поэтому я с удовольствием приму поправки и улучшения данного кода.