На двух стульях: ASP.NET Identity и авторизация по Windows в ASP.NET MVC

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Для начала расскажу, что приложение, которое я разрабатывал, долго существовало на небольшом «подстольном» сервере в виде прототипа, которым в работе пользовалось небольшое число сотрудников. По прошествии некоторого времени, руководство приняло решение тиражировать это приложение в пром – с переносом на пром-сервер и организацией доступов к нему сотрудникам всего структурного подразделения.

Естественно, как это всегда бывает, сопровождение выдало нам список требований, которым должны соответствовать приложения, размещаемые на пром-серверах. Одним из таких требований было реализация авторизации по учетной записи Windows, а старую авторизацию по логину/паролю использовать было нельзя. О том, с какими подводными камнями мы столкнулись в ходе реализации такой, казалось бы, простой фичи, и как мы их решили, и пойдет речь в этом посте. Как я и упомянул ранее, в начальной точке этой истории у нас было классическое MVC-приложение. Информация о пользователях, их ролях (Admin, Common) и доступах к определенным действиям и процедурам хранилась в БД MS SQL. Упрощенно структуру этого сегмента БД можно представить вот так:

По названию таблиц можно догадаться, что в самом приложении эта связка таблиц захватывалась Entity Framework 6, а после использовалась подсистемой ASP.NET Identity. В начале сессии пользователю выводилась форма для входа, в которую он вводил свои учетные данные, после чего происходил редирект на домашнюю страницу приложения. Далее, исходя из того, какие доступы у данного пользователя прописаны в БД, и какими привилегиями он обладает, система подстраивала UI под эти данные.

Авторизация была реализована с помощью HTML-форм путём применения стандартного хелпера Html.BeginForm, отсылающего введенные данные по нажатию кнопки Submit. Вот как это выглядело с точки зрения кода:

@using (Html.BeginForm("Login", "Auth", FormMethod.Post, new { @class = "form-signin" }))
{
    @Html.AntiForgeryToken()
    <div class="form-group form-ie">
        <span class="oi oi-person"></span>
        @Html.TextBoxFor(x => x.Login, new { @class = "form-control", @placeholder = "Логин", @id = "username" })
        @Html.ValidationMessageFor(x => x.Login)
    </div>

    <div class="form-group form-ie">
        <span class="oi oi-lock-locked"></span>
        @Html.PasswordFor(x => x.Password, new { @class = "form-control", @placeholder = "Пароль", @id = "inputPassword" })
        @Html.ValidationMessageFor(x => x.Password)
    </div>
    <input type="submit" class="btn btn-mybtn-lg btn-my btn-block text-uppercase" value="Войти" />
}

Далее логин с паролем передавались в контроллер авторизации AuthController, который в себе хранил UserManager, SignInManager и AppDbContext (пронаследованный от IdentityDBContext) из ASP.NET Identity. Вот как выглядел код этого контроллера.

[AllowAnonymous]
[RoutePrefix("Auth")]
public class AuthController : Controller
{
	private AppDbContext _dbContext;
	private ApplicationSignInManager _signInManager;
	private ApplicationUserManager _userManager;
	public ApplicationSignInManager SignInManager
	{
		get
		{
			return _signInManager ?? HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
		}
		private set
		{
			_signInManager = value;
		}
	}

	public ApplicationUserManager UserManager
	{
		get
		{
			return _userManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
		}
		private set
		{
			_userManager = value;
		}
	}

	public AppDbContext DbContext
	{
		get
		{
			return _dbContext ?? HttpContext.GetOwinContext().Get<AppDbContext>();
		}
		private set
		{
			_dbContext = value;
		}
	}

	public AuthController()
	{
	}

	[HttpGet]
	public ActionResult Index()
	{
		return View(new AuthViewModel());
	}

	[HttpPost]
	[ValidateAntiForgeryToken]
	public async Task<ActionResult> Login(AuthViewModel model)
	{
		var result = await SignInManager.PasswordSignInAsync(model.Login, model.Password, false, false);
		if (result == SignInStatus.Success)
		{
			return RedirectToAction("Index", "Home");
		}
		Log.Warning("Ошибка авторизации: Неправильный логин или пароль");
		ModelState.AddModelError("Password", "Неправильный логин или пароль");
		return View("Index", model);
	}

	private IAuthenticationManager AuthenticationManager
	{
		get
		{
			return HttpContext.GetOwinContext().Authentication;
		}
	}

	[HttpGet]
	[ValidateAntiForgeryToken]
	public ActionResult LogOff()
	{
		AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
		return RedirectToAction("Index", "Auth");
	}
}

Сам факт авторизации в системе в других контроллерах проверялся посредством применения фильтра-нотации [Authorize], а принадлежность к роли – посредством применения [Authorize(Roles = “role1”)].

[Authorize]
public class HomeController : Controller
{
	private AppDbContext _dbContext;

	public AppDbContext DbContext
	{
		get
		{
			return _dbContext ?? HttpContext.GetOwinContext().Get<AppDbContext>();
		}
		private set
		{
			_dbContext = value;
		}
	}

	public HomeController()
	{
	}

	[Authorize(Roles = "Common, Admin")]
	public ActionResult Index()
	{
		///something is happening
		return View();
	}
}

Как заметит знакомый с вышеописанным стеком человек, не происходит вообще ничего необычного – это базовые элементы, знакомые каждому ASP.NET-разработчику.

Итак, после получения требования об изменении порядка авторизации, мы стали менять его. Для тех, кто с этим не знаком — в ASP.NET существуют следующие типы авторизации, которые можно поставить как с конфига, так и с помощью шаблона Visual Studio при создании проекта:

  1. Без авторизации;

  2. Авторизация на основе отдельных учётных записей (логин+пароль, классика)

  3. Авторизация с помощью Active Directory, Microsoft Azure или Office 365.

  4. Авторизация с помощью учётной записи Windows.

Так как у нас нет возможности использовать Active Directory ввиду требований сопровождения, остаётся один вариант – авторизация с помощью УЗ Windows.

Поигравшись немного со сменой способа авторизации в пустых приложениях и убедившись, что в них всё работает, я сделал то же самое с нашим приложением, заменив authentication mode на «Windows» в web.config.

Итак, настало время прогона. Изначально я предполагал, что после изменения авторизации можно будет подгонять логин пользователя в SignInManager, после чего проводить авторизацию по-старому (только без пароля) – т.е., что SignInManager будет маппить логин с таблицей AspNetUsers и вносить в контекст текущей пользовательской сессии соответствующий AspNetIdentity. Для чистоты эксперимента я удалил себя из таблицы с пользователями. Иии…я все равно спокойно авторизовался. Покопавшись в переменных, я понял, что при смене authentication mode на «Windows» используется другой вид Identity: не AspNetIdentity, а WindowsIdentity. При использовании WindowsIdentity любой пользователь, который вошёл в Windows – априори авторизован, причем автономно – никакой связи с БД и EF не наблюдалось. Это означало, что если ничего не исправить, то…

Ну вы поняли

Источник: https://habr.com/ru/post/672382/


Интересные статьи

Интересные статьи

На Хабре неоднократно обсуждалось, что сторонние антивирусы — источник дополнительной угрозы. Они внедряются в ОС на уровне ядра и увеличивают поверхность атаки за счёт собственных уязвимостей. Не...
Идентификация пользователей на сайтах без "зарегистрироваться", "забыл пароль" с помощью пары кликов в браузере. Один или сто аккаунтов одного пользователя на одном или с...
Если вы работали в современных Linux-системах, включая большинство ОС для Raspberry Pi, то вы, возможно, знакомы с командой sudo. Обычно она позволяет авторизованному пользователю повышат...
В последнем обновлении популярный среди безопасников и хакеров дистрибутив Kali Linux получил любопытный режим «Undercover» — «под прикрытием», суть которого сводится к визуальной мимикрии под гр...
Практически все коммерческие интернет-ресурсы создаются на уникальных платформах соответствующего типа. Среди них наибольшее распространение получил Битрикс24.