День добрый, сегодня я расскажу про довольно полезный прием при поддержке крупный сайтов-сервисов. А именно, возможность зайти на ваш сайт под обычным пользователем, не имея пароля (вы же не храните пароли в БД открытым текстом верно?).
Введение
Предположим у вас уже есть готовый, запущенный, живущий бурной жизнью веб сайт. И так уж сложилось что для разных пользователей он выглядит и работает по разному, например в зависимости от ролей, политик безопасности и прочей бизнес логики. Для простоты примера пусть будет интернет банкинг.
Задача
Дать возможность администраторам или, например операторам тех-поддержки как-бы войти под учетной записью конкретного пользователя. Например, намного проще объяснить дозвонившемуся в колл-центр пользователю решение проблемы видя сайт его глазами.
Решение
Для примера я возьму ASP.NET MVC, хотя поклонникам долларовых знаков и драгоценных камней реализовать что-то похожее на любимом фреймворке не должно составить труда. За код сильно не бейте – писался четыре года назад как пример.
В мире Windows Authentication есть уже готовое красивое решение под названием Impersonation. Т.е. когда один пользователь как-бы прикидывается другим, получая его права и возможность выполнять действия от его имени.
Все хорошо, вот только наш гипотетический интернет банкинг использует Forms Authentication т.е. в web.config написано что-то типа <authentication mode=«Forms» (для не посвященных: вход реализован как обычная веб форма для ввода логина и пароля). В таком режиме воспользоваться плюшками Impersonation не получиться, но мы можем попробовать реализовать что-то подобное.
Для начала добавим в AccountController.cs (создается с дефолтным проектом ASP.NET MVC) следующий метод:
public ActionResult Impersonate(string user)
{
//checking if user is logged in
if (Request.IsAuthenticated)
{
//and checking if he is a super user
if (Roles.IsUserInRole("admins"))
{
//saving current auth cookie for a later use
HttpCookie cookie = Request.Cookies[FormsAuthentication.FormsCookieName];
cookie.Name = "__" + cookie.Name;
Response.Cookies.Add(cookie);
//logging out for current user
FormsAuthentication.SignOut();
//making new user logged in without a password
FormsAuthentication.SetAuthCookie(user, true);
//redirect to some page here
return RedirectToAction("Index", "Home");
}
}
//currently logged user has no rights to impersonate
throw new SecurityException();
}
Вся суть в том, что бы залогинить текущего пользователя под другим именем, проверив входит ли он в роль Администраторов (или например «Операторы тех-поддержки 3го уровня») не проверяя правильность ввода пароля. Для этого мы выдадим ему новую соответствующую печеньку авторизации, а старую переименуем, что бы можно было вернуться в режим администратора без повторного ввода пароля.
По шагам это будет выглядеть так:
- Проверяем залогинен ли пользователь
- Проверяем достаточно ли у него прав, что бы прикинуться кем-то другим
- Сохраняем текущую печенюху сессии под другим именем
- Создаем новую для другого пользователя, точно также как мы это делаем при обычном логине после проверки пароля (т.е. после Membership.ValidateUser())
- Редиректимся например на главную страницу
В итоге браузер получит уже две печеньки а сервер будет воспринимать текущую сессию как сессию обычного пользователя.
Для того что бы вызвать только что созданный метод нам понадобиться новая страница со списком пользователей. Полный листинг приводить не буду, думаю, идея понятна:
using (Html.BeginForm("Impersonate", "Account"))
{
var users = Membership.GetAllUsers(); // or Roles.GetUsersInRole(...)
foreach (var userName in users)
{
...
}
}
Итого после выбора необходимого пользователя администратор временно превращается в обычного пользователя.
Осталось только добавить возможность вернуться в режим администратора. Внешний вид сайта изменять мы не хотим добавляя новые элементы, поэтому воспользуемся уже существующей кнопкой выхода. Для этого немного расширим существующий метод выхода (в AccountController.cs):
public ActionResult LogOff()
{
//checking if we impersonated already
HttpCookie cookie = Request.Cookies["__" + FormsAuthentication.FormsCookieName];
if (null != cookie)
{
//logging off
FormsAuthentication.SignOut();
//restoring original auth cookie of a super user
cookie.Name = FormsAuthentication.FormsCookieName;
Response.Cookies.Add(cookie);
//removing previosly saved auth cookie of a super user so we can logout normaly next time
HttpCookie expired = new HttpCookie("__" + FormsAuthentication.FormsCookieName);
expired.Expires = DateTime.Now.AddDays(-1);
Response.Cookies.Add(expired);
}
else
{
//logging off in a ordinary fashion
FormsAuth.SignOut();
}
//redirecting
return RedirectToAction("Index", "Home");
}
В начале проверяем существует ли заранее сохраненная печенька администратора. Если да то переименовываем ее обратно, а про обычного пользователя забываем. В итоге при нажатии кнопки выхода, администратор вернется в свое привычное окружение. А нажав на кнопку выхода еще раз, он разлогиниться полностью.
Конечный результат
Логинимся на сайт администратором
Выбираем пользователя, которым мы хотим стать
Сайт думает что мы обычный пользователь
Если посмотреть на сохраненные печеньки в браузере, можно увидеть оба токена
После нажатия на Log Off, возвращаемся к режиму администратора
Соответственно печеньки восстановлены
Если нажать Log Off еще раз, выходим полностью
Заключение
Итого с минимум изменений мы добавили возможность на время прикинуться другим пользователем. Причем, даже не меняя внешний вид и логику сайта. Для остальной части нашего веб-приложения такие манипуляции абсолютно прозрачны и не должны ничего поломать.
Осталось только продумать логику кому кем можно прикидываться. Что бы, например оператор тех поддержки не мог войти как администратор.
Интересно узнать, может кто-то уже реализовывал похожую схему другими способами? Отписывайтесь в комментариях.
Автор: berlox