ГлавнаяРегистрацияВход Приветствую Вас Гость | RSS
   
Меню сайта
Разделы новостей
mp3player
Главная » 2007 » Июль » 8 » Создание системы авторизации, основанной на ролях, в ASP.NET приложении
Создание системы авторизации, основанной на ролях, в ASP.NET приложении
21:02
Автор: Dimon aka Manowar (aspnetman@aspnetmania.com)
Источник: www.aspnetmania.com

Почти все программисты, начинающие работать с ASP.NET, приходят в дикий восторг услышав фразу "авторизация на основе ролей". Но когда они знакомятся с этим поближе восторг обычно сменяется унынием и возмущением "неужели нельзя было сделать это более красивей и удобней - без заморачивания с файлом конфигурации и с более гибким управлением?". Но расстраиваться нет причин – не все так плохо, как кажется на второй взгляд :).

Для начала вспомним, что же нам предлагают создатели ASP.NET для управления доступом к ресурсам веб приложения пользователям, имеющим определенные роли (или говоря более привычным для администраторов языком – пользователям, входящим в определенные группы). А предлагают они следующее решение:

  1. Для аутентифицированного пользователя создать экземпляр класса GenericPrincipal, содержащий кроме всего прочего информацию о ролях этого пользователя.
  2. Определить в файле конфигурации web.config с помощью тегов <authorization> и <allow roles="..">/<deny roles=".."> права доступа пользователей с указанными ролями к указанным ресурсам.

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

Столкнувшись с данной проблемой, мне пришлось сразу же отбросить использование web.config для определения прав и обязанностей. Хотя бы потому, что реальным назначением этих самых прав доступа пользователей с определенными ролями должен был заниматься (и занимается до сих пор) человек, для которого XML вообще и web.config в частности сущности весьма далекие. Поэтому необходимо было создать подобное решение основываясь на следующих предпосылках:

  1. Вся информация должна храниться в БД.
  2. Должен быть понятный и удобный интерфейс для назначения прав доступа к файлам, для добавления/редактирования пользователей и ролей.
  3. В результате проверки прав доступа пользователь либо должен попасть на запрашиваемый ресурс, либо же получить сообщение о недостатке прав.
  4. Администратор должен иметь права на все.

Начал я естественно с БД. В результате получились 4 таблицы для хранения информации о пользователях, группах пользователей, страницах сайта и отношениях между пользователями и группами:

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

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

Лучшее место для создания экземпляра класса GenericPrincipal – обработчик события AuthenticateRequest класса HttpApplication. Рассмотрим этот процесс в подробностях.

Первым делом нужно проверить аутентифицирован ли пользователь:

protected void Application_AuthenticateRequest(Object sender, EventArgs e) 
{
 if (Request.IsAuthenticated == true) 
 {

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

 String[] roles;
 if ((Request.Cookies["mff_roles"] == null) || (Request.Cookies["mff_roles"].Value == "")) 
 {

Куки со списком ролей еще не установлены – будем получать их из базы данных.

 administrators admin = new administrators();
 DataView dv = admin.MemberOf(Int32.Parse(User.Identity.Name));

Данный метод возвращает результат следующей SQL конструкции

select 
name 
from 
groups inner join administrators_groups on
groups.group_uid = administrators_groups.group_uid
where
admin_uid = @admin_uid

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

 String roleStr = "";
 foreach (DataRowView drv in dv) 
 {
 roleStr += String.Format("{0};", drv["name"]);
 }

 roleStr = roleStr.Remove(roleStr.Length - 1, 1);

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

 FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
 1, 
 Context.User.Identity.Name, 
 DateTime.Now, 
 DateTime.Now.AddHours(1), 
 false, 
 roleStr 
 );
 roles = roleStr.Split(new Char[] {';'});

Затем он шифруется

 String cookieStr = FormsAuthentication.Encrypt(ticket);

И записывается в куки.

 Response.Cookies["mff_roles"].Value = cookieStr;
 Response.Cookies["mff_roles"].Path = "/";
 Response.Cookies["mff_roles"].Expires = DateTime.Now.AddHours(1);
 }
 else 
 {

Если же куки со списком ролей уже существуют, тогда они извлекаются и дешифруются

 FormsAuthenticationTicket ticket = 
 FormsAuthentication.Decrypt(Context.Request.Cookies["mff_roles"].Value);

И сохраняются в массиве строк, необходимом для конструктора класса GenericPrincipal.

 ArrayList userRoles = new ArrayList();
 foreach (String role in ticket.UserData.Split( new char[] {';'} )) 
 {
 userRoles.Add(role);
 }
 roles = (String[]) userRoles.ToArray(typeof(String));
 }

И, наконец, создается экземпляр класса GenericPrincipal, содержащий всю необходимую информацию.

 Context.User = new GenericPrincipal(Context.User.Identity, roles);
 }
}

Теперь все готово к авторизации пользователя. Сам процесс авторизации проходит в обработчике события AuthorizeRequest все того же класса HttpApplication. Рассмотрим его подробнее:

protected void Application_AuthorizeRequest(Object sender, EventArgs e) 
{
 if(Request.IsAuthenticated)
 {

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

 string pageName = Request.FilePath.Remove(0, 1);
 if((pageName == "login.aspx") || (pageName == "logout.aspx") || (pageName == "error.aspx") 
 || (pageName.ToLower() == "accessdenied.aspx"))
 return;

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

 DataView pages = (DataView) Context.Cache["admin_pages"];
 if(pages == null)
 {
 pages page = new pages();
 pages = page.List();
 Context.Cache.Insert("admin_pages", pages, null, DateTime.Now.AddHours(12), TimeSpan.Zero);
 }

Находим интересующую нас cтраницу.

 pages.RowFilter = "PageName = '" + pageName + "'";
 if(pages.Count > 0)
 {

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

 foreach(string role in pages[0]["GroupsList"].ToString().Split(new char[] {','}))
 {
 if(Context.User.IsInRole(role))
 {
 return;
 }
 }

Если пользователь не принадлежит ни одной из ролей – проверяем не является ли пользователь администратором (у меня для администраторов заведена предопреденная группа Administrators).

 if(Context.User.IsInRole("Administrators"))
 {
 return;
 }
 }
 else

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

 if(Context.User.IsInRole("Administrators"))
 return;

В этом месте обработчика мы окажемся только в случае, когда все предыдущие проверки не прошли. И означать это будет то, что у пользователя нет прав на доступ к запрашиваемой странице. Сообщим ему об этом перенаправив его на страницу, сообщающую, что Access Denied! :)

 Context.RewritePath("/AccessDenied.aspx");
 }
}

Вот и все. Осталось добавить Forms аутентификацию и запретить доступ ко всем файлам веб приложения вставив в web.config следующие строки:

<authentication mode="Forms">
 <forms loginUrl="/login.aspx" name=".ADMINAUTH" timeout="45"/>
</authentication>
<authorization>
 <deny users="?" />
</authorization>

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

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

Категория: ASP.Net | Просмотров: 890 | Добавил: VVS | Рейтинг: 0.0/0 |
Всего комментариев: 6
6 Brandonjogex  
0
home health remedies <a href=""> https://forums.dieviete.lv/profils/127605/forum/ </a> erectile dysfunction wiki

5 Muhdayday  
0
[url=http://tutdlenet.ru/]
бизнес шаблоны dle[/url]

4 axophyArror  
0
[url=http://silven.ru/khkhkh-onlajjn/]
мама порно онлайн[/url]

3 axophyArror  
0
[url=http://silven.ru/khkhkh-onlajjn/]
порно онлайн пьяные[/url]

2 axophyArror  
0
[url=http://silven.ru/khkhkh-onlajjn/]
игры порно онлайн[/url]

1 Turryreda  
0
Сделав добро, не кайся.

Имя *:
Email *:
Код *:
Поиск
Форма входа
Наш опрос
Чего Вам не хватает на сайте?
Всего ответов: 21
Друзья сайта
Статистика
Возраст