Элемент управления Login.

08.05.2019

Сергей Бакланов

Imports System.Data.SqlClient Imports System.Web.Security Public Class login Inherits System.Web.UI.Page Protected WithEvents txtName As System.Web.UI.WebControls.TextBox Protected WithEvents txtPassword As System.Web.UI.WebControls.TextBox Protected WithEvents lbl As System.Web.UI.WebControls.Label Protected WithEvents btnLogin As System.Web.UI.WebControls.Button #Region " Web Form Designer Generated Code " "This call is required by the Web Form Designer. Private Sub InitializeComponent() End Sub "NOTE: The following placeholder declaration is required by the Web Form Designer. "Do not delete or move it. Private designerPlaceholderDeclaration As System.Object Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init "CODEGEN: This method call is required by the Web Form Designer "Do not modify it using the code editor. InitializeComponent() End Sub #End Region Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load "Put user code to initialize the page here End Sub Private Sub btnLogin_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnLogin.Click Dim cn As New SqlConnection("server=localhost;database=FormAuthUsers;uid=sa;pwd=;") Dim cm As New SqlCommand("FindUser", cn) Dim dr As SqlDataReader Dim ticket As FormsAuthenticationTicket Dim n As Integer, strRoles As String, strEncrypted As String " Открываем соединение Try cn.Open() Catch ex As SqlException Response.Write(ex.Message) Exit Sub End Try " Задаём тип команды cm.CommandType = CommandType.StoredProcedure " Добавляем параметры имени Dim prmName = New SqlParameter("@Name", SqlDbType.NVarChar, 50) prmName.Value = txtName.Text cm.Parameters.Add(prmName) " Добавляем параметр пароля Dim prmPass = New SqlParameter("@Password", SqlDbType.NVarChar, 50) prmPass.Value = txtPassword.Text cm.Parameters.Add(prmPass) " Выполняем запрос n = cm.ExecuteScalar If n > 0 Then " Если пользователь с таким именем и паролем существует, то ищем его роли cm = Nothing cm = New SqlCommand("exec FindRoles "" & txtName.Text & """, cn) dr = cm.ExecuteReader() " Составляем список ролей While dr.Read If strRoles = "" Then strRoles &= dr(0) Else strRoles &= ", " & dr(0) End If End While " Создаём аутентификационный билет ticket = New FormsAuthenticationTicket(1, txtName.Text, DateTime.Now, _ DateTime.Now.AddMinutes(20), False, strRoles) " Шифруем билет strEncrypted = FormsAuthentication.Encrypt(ticket) " Сохраняем cookie-файл Response.Cookies.Add(New HttpCookie("UrlAuthz", strEncrypted)) " Возвращаемся на исходную страницу FormsAuthentication.RedirectFromLoginPage(txtName.Text, False) Else " Если пользователь не был найден, то выдаём сообщение об ошибке lbl.Visible = True End If End Sub End Class

В этом примере мы поместили в одну процедуру две операции проверки: одна - аутентификации, другая - авторизации. Сначала мы проходим аутентификацию, запрашивая данные на пользователя с таким-то именем и паролем из базы. Если пользователь не был найден, выводим соответствующее сообщение об ошибке (см. 4 строку снизу). Если же пользователь обнаружен, то мы определяем его роли, опять запрашивая информацию из базы данных. На основе полученных сведений о ролях формируется аутентификационный билет, который впоследствии шифруется и сохраняется в cookie-файле. И, наконец, пользователь благополучно возвращается на страницу default.aspx.

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

Листинг 7. default.aspx

AuthzByUrl Sub Page_Load(sender As Object, e As EventArgs) Handles MyBase.Load If HttpContext.Current.User.Identity.Name = "" Then lblLogin.Text = "You"re not registered, please login" Else lblLogin.Text = "You"re registered as " & _ HttpContext.Current.User.Identity.Name End If End Sub Sub btnLogin_Click(sender As Object, e As EventArgs) Handles btnLogin.Click Response.Redirect("login.aspx") End Sub You"re not registered, please login
GoTo:
  • Admin zone
  • User zone

admin.aspx

admin

После создания этого простого Web-узла вы сможете собственными глазами увидеть плоды своего труда. В приведённом коде указаны все инструкции, необходимые для создания действующей системы безопасности для сайта на базе аутентификации формой и авторизации с помощью URL.

Заимствование полномочий

Заимствование полномочий - это такой режим работы, при котором приложение ASP.NET функционирует от имени конкретного пользователя. Казалось бы, какой смыл вводить заимствование полномочий, если при аутентификации Windows пользователь и так заходит под конкретной учётной записью? Но всё дело в том, что идентификатор пользователя при аутентификации и идентификатор пользователя при заимствовании полномочий - это разные вещи, и применяются они соответственно для получения различной информации.

По умолчанию режим заимствования полномочий в среде ASP.NET отключён. Для его активизации нужно добавить в файл Web.config тэг и присвоить его атрибуту impersonate значение true. Следующий фрагмент файла конфигурации проекта демонстрирует, как это должно выглядеть:

Web.config

Для демонстрации работы этого режима, используйте следующий код (листинг 8) в странице default.aspx:

default.aspx

Impersonation User: IsAuthenticated Authentication type Name WindowsIdentity: IsAuthenticated Authentication type Name

default.aspx.vb

Imports System.Security.Principal Public Class WebForm1 Inherits System.Web.UI.Page #Region " Web Form Designer Generated Code " "This call is required by the Web Form Designer. Private Sub InitializeComponent() End Sub "NOTE: The following placeholder declaration is required by the Web Form Designer. "Do not delete or move it. Private designerPlaceholderDeclaration As System.Object Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init "CODEGEN: This method call is required by the Web Form Designer "Do not modify it using the code editor. InitializeComponent() End Sub #End Region Protected WithEvents clmIsAuthU As System.Web.UI.HtmlControls.HtmlTableCell Protected WithEvents clmAuthTypeU As System.Web.UI.HtmlControls.HtmlTableCell Protected WithEvents clmNameU As System.Web.UI.HtmlControls.HtmlTableCell Protected WithEvents clmIsAuthW As System.Web.UI.HtmlControls.HtmlTableCell Protected WithEvents clmAuthTypeW As System.Web.UI.HtmlControls.HtmlTableCell Protected WithEvents clmNameW As System.Web.UI.HtmlControls.HtmlTableCell Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Dim wi As WindowsIdentity " User.Identity With context.User.Identity clmIsAuthU.InnerText = .IsAuthenticated.ToString clmAuthTypeU.InnerText = .AuthenticationType.ToString clmNameU.InnerText = .Name End With " System.Security.Principal.WindowsIdentity wi = WindowsIdentity.GetCurrent With wi clmIsAuthW.InnerText = .IsAuthenticated.ToString clmAuthTypeW.InnerText = .AuthenticationType.ToString clmNameW.InnerText = .Name End With End Sub End Class

В обработчике события загрузки формы для получения идентификатора пользователя объекта WindowsIdentity используется метод GetCurrent, возвращающий идентификатор учётной записи, от имени которой функционирует процесс ASP.NET.

При запуске этого приложения с отключенным заимствованием полномочий () перед вами появится экран, представленный на рисунке 3. Как можно наблюдать, при отключенном заимствовании полномочий в объекте WindowsIdentity содержится идентификатор системного пользователя ASPNET.

Теперь, если вы активизируете заимствование полномочий, то увидите результат, представленный в таблице 1.

Таблица 1. Включенное заимствование полномочий и отключенный анонимный доступ

WindowsIdentity:

IsAuthenticated True
Authentication type Negotiate
Name BIGDRAGON\B@k$
IsAuthenticated True
Authentication type NTLM
Name BIGDRAGON\B@k$

Как видите, результаты одинаковые, поскольку оба объекта получают информацию о текущем пользователе. Но предыдущие два примера были ориентированы на условия с запрещённым анонимным доступом для аутентификации средствами Windows. Если разрешить анонимный доступ к приложению, то объект User.Identity не вернёт никакого имени пользователя, а его свойство IsAuthenticated будет иметь значение False. В этом нет ничего удивительного, т. к. если в системе аутентификации Windows разрешён анонимный доступ, то пользователь работает анонимно, то есть не проходит аутентификацию.

В это же время у объекта WindowsIdentity свойство IsAuthenticated будет иметь значение True, а в качестве имени пользователя будет стоять строка следующего формата: IUSR_ , как показано в таблице 2.

Таблица 2. Заимствование полномочий и анонимный доступ разрешены

WindowsIdentity:

IsAuthenticated False
Authentication type
Name
IsAuthenticated True
Authentication type NTLM
Name BIGDRAGON\IUSR_BIGDRAGON

Свойство name объекта WindowsIdentity имеет такое значение потому, что оно возвращает идентификатор пользователя, под которым работает процесс ASP.NET, а не пользователь Web-узла. А поскольку процесс не может работать анонимно, то он получает имя от IIS, если его невозможно получить от текущего пользователя.

Если вы были внимательны при выполнении операций по разрешению/запрету анонимного доступа, то могли заметить, что в поле Имя пользователя была как раз подставлена строка вышеуказанного формата: IUSR_ (рис. 4).

Рисунок 4. В поле Имя пользователя содержится строка, определяющая имя процесса ASP.NET при анонимном доступе

Кроме того, в среде ASP.NET предусмотрена возможность указания, от кого именно заимствовать полномочия. С этой целью в тэге предусмотрен атрибут userName, в котором и указывается имя пользователя, у которого необходимо заимствовать полномочия.

В следующем фрагменте из файла Web.config, показано, как это должно выглядеть на практике:

Web.config :

После запуска тестового приложения с такой конфигурацией на выполнение, состояние объекта User.Identity останется неизменным, а вот в свойстве name объекта WindowsIdentity вместо строки формата IUSR_ появится имя, указанное в атрибуте userName тэга из файла конфигурации проекта, как показано в таблице 3.

Таблица 3. Процесс ASP.NET работает от имени конкретного пользователя

WindowsIdentity:

IsAuthenticated False
Authentication type
Name
IsAuthenticated True
Authentication type NTLM
Name BIGDRAGON\AlBa

Если вы отмените анонимный доступ, то объект User.Identity будет содержать идентификатор зарегистрированного пользователя, а в объекте WindowsIdentity по-прежнему будет оставаться имя пользователя, переданное через атрибут userName.

На этом закончим изучение авторизации как средства безовасности среды ASP.NET. Дальнейшее изучение механизма авторизации требует изучения средств авторизации Windows. Среди них можно выделить списки контроля доступа на низком и высоком уровне, контроль доступа архитектуры клиент/сервер, ролевая безопасность Windows и так далее.

Если эта тема вас действительно заинтересовала, то вы можете найти массу материала в библиотеке MSDN:

  • Темы безопасности в рамках ASP.NET доступны в следующей ветке библиотеки MSDN: .NET Development/.NET Security;
  • По вопросам безопасности всей системы в целом следует обращаться к разделу Security/Security (General)/SDK Documentation.

Если у вас нет MSDN-библиотеки, то к её самому свежему изданию можно обратиться через интернет по адресу: http://msdn.microsoft.com/library/ .

В третьей, заключительной части данной статьи, мы рассмотрим очень актуальную и интересную тему - криптографию. Кроме теории и алгоритмов криптографии мы рассмотрим с различных сторон средства шифрования, предоставляемые платформой.NET Framework, и создадим простенький метод шифрования.

В составе технологии ASP.Net Web Forms имеется набор ЭУ для создания и использования учетных записей пользователей:

  • Login – ЭУ для подключения пользователя (ввод имени и пароля) и проверки его соответствия данным, которые содержатся в БД. Если данные совпадают (т. е. пользователь прошел аутентификацию, то выполняется переход к запрашиваемой странице).
  • LoginView – позволяет показывать разную информацию для подключенных пользователей. Например, можно использовать эту страницу для отображения информации, которая доступна только аутентифицированным пользователям.
  • LoginStatus – ЭУ, показывающий ссылку на страницу подключения для пользователей, которые не были аутентифицированы (Logout), и ссылку на страницу отключения для подключенных пользователей (Login).
  • LoginName – ЭУ, показывающий текущее имя пользователя, если он подключен к системе.
  • Password Recovery – ЭУ для выполнения восстановления пароля пользователей путем отправки e-mail-сообщсния или при ответе пользователя на секрет ный вопрос.
  • CreateUserWizard – ЭУ, который собирает информацию о новом пользователе и создает в БД новую учетную запись.
  • ChangePassword – ЭУ, позволяющий подключенному пользователю сменить пароль.

Элемент управления Login предоставляет готовый к использованию интерфейс, который запрашивает имя и пароль пользователя. Он включает кнопку с атрибутом CommandName-"Login” для подключения пользователя. При нажатии пользователем на данную кнопку ЭУ автоматически выполняет проверку соответствия введенного имени и пароля пользователя с данными, содержащимися в БД, а затем вызывает переход к запрашиваемой web-форме приложения. Данный ЭУ является полностью расширяемым и позволяет переопределить его разметку, стиль и свойства, а также самому обрабатывать события, чтобы изменить стандартное поведение. Пример настройки описания ЭУ Login показан ниже:

Подключение к системе

string.Compare(p.Email, userName, true) == 0); if (retUser != null) { CreateCookie(userName); } return retUser; } private void CreateCookie(string userName, bool isPersistent = false) { var ticket = new FormsAuthenticationTicket(1, userName, DateTime.Now, DateTime.Now.Add(FormsAuthentication.Timeout), isPersistent, string.Empty, FormsAuthentication.FormsCookiePath); // Encrypt the ticket. var encTicket = FormsAuthentication.Encrypt(ticket); // Create the cookie. var AuthCookie = new HttpCookie(cookieName) { Value = encTicket, Expires = DateTime.Now.Add(FormsAuthentication.Timeout) }; HttpContext.Response.Cookies.Set(AuthCookie); } public void LogOut() { var httpCookie = HttpContext.Response.Cookies; if (httpCookie != null) { httpCookie.Value = string.Empty; } } private IPrincipal _currentUser; public IPrincipal CurrentUser { get { if (_currentUser == null) { try { HttpCookie authCookie = HttpContext.Request.Cookies.Get(cookieName); if (authCookie != null && !string.IsNullOrEmpty(authCookie.Value)) { var ticket = FormsAuthentication.Decrypt(authCookie.Value); _currentUser = new UserProvider(ticket.Name, Repository); } else { _currentUser = new UserProvider(null, null); } } catch (Exception ex) { logger.Error("Failed authentication: " + ex.Message); _currentUser = new UserProvider(null, null); } } return _currentUser; } } #endregion }

Суть сводится к следующему, мы, при инициализации запроса, получаем доступ к HttpContext.Request.Cookies и инициализируем UserProvider:

Var ticket = FormsAuthentication.Decrypt(authCookie.Value); _currentUser = new UserProvider(ticket.Name, Repository);

Для авторизации в IRepository добавлен новый метод IRepository.Login. Реализация в SqlRepository:
public User Login(string email, string password) { return Db.Users.FirstOrDefault(p => string.Compare(p.Email, email, true) == 0 && p.Password == password); }

UserProvider, собственно, реализует интерфейс IPrincipal (в котором есть проверка ролей и доступ к IIdentity).
Рассмотрим класс UserProvider (/Global/Auth/UserProvider.cs):

Public class UserProvider: IPrincipal { private UserIndentity userIdentity { get; set; } #region IPrincipal Members public IIdentity Identity { get { return userIdentity; } } public bool IsInRole(string role) { if (userIdentity.User == null) { return false; } return userIdentity.User.InRoles(role); } #endregion public UserProvider(string name, IRepository repository) { userIdentity = new UserIndentity(); userIdentity.Init(name, repository); } public override string ToString() { return userIdentity.Name; }

Наш UserProvider знает про то, что его IIdentity классом есть UserIdentity , а поэтому знает про класс User , внутри которого мы реализуем метод InRoles(role) :

Public bool InRoles(string roles) { if (string.IsNullOrWhiteSpace(roles)) { return false; } var rolesArray = roles.Split(new { "," }, StringSplitOptions.RemoveEmptyEntries); foreach (var role in rolesArray) { var hasRole = UserRoles.Any(p => string.Compare(p.Role.Code, role, true) == 0); if (hasRole) { return true; } } return false; }

В метод InRoles мы ожидаем, что придет запрос о ролях, которые допущены к ресурсу, разделенные запятой. Т.е., например, “admin,moderator,editor”, если хотя бы одна из ролей есть у нашего User – то возвращаем зачение «истина» (доступ есть). Сравниваем по полю Role.Code, а не Role.Name.
Рассмотрим класс UserIdentity (/Global/Auth/UserIdentity.cs):
public class UserIndentity: IIdentity { public User User { get; set; } public string AuthenticationType { get { return typeof(User).ToString(); } } public bool IsAuthenticated { get { return User != null; } } public string Name { get { if (User != null) { return User.Email; } //иначе аноним return "anonym"; } } public void Init(string email, IRepository repository) { if (!string.IsNullOrEmpty(email)) { User = repository.GetUser(email); } } }
В IRepository добавляем новый метод GetUser(email) . Реализация для SqlRepository.GetUser() (LessonProject.Model:/SqlRepository/User.cs):

Public User GetUser(string email) { return Db.Users.FirstOrDefault(p => string.Compare(p.Email, email, true) == 0); }

Почти все готово. Выведем CurrentUser в BaseController:
public IAuthentication Auth { get; set; } public User CurrentUser { get { return ((UserIndentity)Auth.CurrentUser.Identity).User; } }

Да, это не очень правильно, так как здесь присутствует сильное связывание. Поэтому сделаем так, введем еще один интерфейс IUserProvider , из которого мы будем требовать вернуть нам авторизованного User:
public interface IUserProvider { User User { get; set; } } … public class UserIndentity: IIdentity, IUserProvider { /// /// Текщий пользователь /// public User User { get; set; } … public IAuthentication Auth { get; set; } public User CurrentUser { get { return ((IUserProvider)Auth.CurrentUser.Identity).User; } }
А теперь попробуем инициализировать это всё.
Вначале добавим наш IAuthentication + CustomAuthentication в регистрацию к Ninject (/App_Start/NinjectWebCommon.cs):

Kernel.Bind().To().InRequestScope();

Потом создадим модуль, который будет на событие AuthenticateRequest совершать действие авторизации:
public class AuthHttpModule: IHttpModule { public void Init(HttpApplication context) { context.AuthenticateRequest += new EventHandler(this.Authenticate); } private void Authenticate(Object source, EventArgs e) { HttpApplication app = (HttpApplication)source; HttpContext context = app.Context; var auth = DependencyResolver.Current.GetService(); auth.HttpContext = context; context.User = auth.CurrentUser; } public void Dispose() { } }

Вся соль в строках: auth.HttpContext = context и context.User = auth.CurrentUser . Как только наш модуль авторизации узнает о контексте и содержащихся в нем кукисах, ту же моментально получает доступ к имени, по нему он в репозитории получает данныепользователя и возвращает в BaseController. Но не сразу всё, а по требованию.
Подключаем модуль в Web.config:

План таков:

  • Наверху показываем, авторизован пользователь или нет. Если авторизован, то его email и ссылка на выход, если нет, то ссылки на вход и регистрацию
  • Создаем форму для входа
  • Если пользователь правильно ввел данные – то авторизуем его и отправляем на главную страницу
  • Если пользователь выходит – то убиваем его авторизацию

Поехали. Добавляем Html.Action(“UserLogin”, “Home”) – это partial view (т.е. кусок кода, который не имеет Layout) – т.е. выводится где прописан, а не в RenderBody().
_Layout.cshtml (/Areas/Default/Views/Shared/_Layout.cshtml):

@RenderBody() HomeController.cs: public ActionResult UserLogin() { return View(CurrentUser); }

UserLogin.cshtml (/Areas/Default/Views/Home/UserLogin.cshtml):

@model LessonProject.Model.User @if (Model != null) {

  • @Model.Email
  • @Html.ActionLink("Выход", "Logout", "Login")
  • } else {
  • @Html.ActionLink("Вход", "Index", "Login")
  • @Html.ActionLink("Регистрация", "Register", "User")
  • }

    Контроллер входа выхода LoginController (/Areas/Default/Controllers/LoginController.cs):

    Public class LoginController: DefaultController { public ActionResult Index() { return View(new LoginView()); } public ActionResult Index(LoginView loginView) { if (ModelState.IsValid) { var user = Auth.Login(loginView.Email, loginView.Password, loginView.IsPersistent); if (user != null) { return RedirectToAction("Index", "Home"); } ModelState["Password"].Errors.Add("Пароли не совпадают"); } return View(loginView); } public ActionResult Logout() { Auth.LogOut(); return RedirectToAction("Index", "Home"); } }

    LoginView.cs (/Models/ViewModels/LoginView.cs):
    public class LoginView { public string Email { get; set; } public string Password { get; set; } public bool IsPersistent { get; set; } }

    Страница для входа Index.cshtml (/Areas/Default/Views/Index.cshtml):

    @model LessonProject.Models.ViewModels.LoginView @{ ViewBag.Title = "Вход"; Layout = "~/Areas/Default/Views/Shared/_Layout.cshtml"; } Вход @using (Html.BeginForm("Index", "Login", FormMethod.Post, new { @class = "form-horizontal" })) { Вход Email @Html.TextBox("Email", Model.Email, new { @class = "input-xlarge" })

    Введите Email

    @Html.ValidationMessage("Email") Пароль @Html.Password("Password", Model.Password, new { @class = "input-xlarge" }) @Html.ValidationMessage("Password") Войти }

    Запускаем и проверяем:

    Все исходники находятся по адресу