![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |
На сегодняшний день информация имеет очень большое значение, часто утечка даже самого незначительного её количества может оказаться огромной потерей. Во избежание подобных инцидентов нужно уметь её защищать. Для этого есть множество способов и средств, начиная от обычного шифрования информации методом Цезаря(перемещение каждого символа на 3 вперёд) и заканчивая внедрением карт доступа и средств биосканирования. Каждое средство предназначено для своих целей, поэтому нужно чётко представлять, что вы хотите сделать, чего пытаетесь добиться. Разумеется, самыми надёжным методом является внедрение отдельных устройств идентификации личности. Это могут быть сканеры сетчатки глаза, сканеры отпечатков пальцев, отдельные кардридеры и другие изощрённые способы. Но такие средства стоят очень дорого, и их поддержка тоже оказывается далеко не самым дешёвым и доступным удовольствием. Применение подобных технологий могут себе позволить только крупные компании и государственные учреждения, имеющие как финансовые возможности, так и ярко выраженные потребности в этом. Технические средства позволяют не только обезопасить систему от несанкционированного доступа, но и предоставляют полный контроль за перемещением персонала как в реальном физическом мире, так и в виртуальном, т. е. по сетям компании.
Наряду с дорогими и высокотехнологичными системами безопасности существуют технологии более низкого ранга, но всё же способные обеспечить безопасность на своём уровне – на уровне программном. Давайте познакомимся с ними поближе, а точнее, со средствами, которые наиболее распространены в среде ASP.NET и .NET Framework. Здесь основное внимание будет уделено таким аспектам безопасности ASP.NET, как аутентификация, авторизация и криптография.
Для начала следует провести чёткую грань между понятиями аутентификации и авторизации. Аутентификация – процесс выяснения и проверки личности пользователя с помощью получения от него пары логин/пароль и сравнения их с каким-либо заслуживающим доверия источником. Наглядным примером аутентификации может служить вход в Windows. Авторизация – это проверка прав доступа к определённому ресурсу или проверка привилегий на выполнение определённых действий. Например, авторизация в Windows проходит каждый раз, когда вы пытаетесь изменить настройки системы, добавить или удалить пользователей, и если у вас не окажется соответствующих прав для выполнения подобных действий, то операционная система выдаст вам соответствующую ошибку.
Давайте теперь перейдём к описанию процесса аутентификации непосредственно в рамках среды ASP.NET, предоставляющей выбор из трех видов аутентификации:
Как и следует из названия, этот метод основывается на использовании учётных записей Windows. Такой метод уместен, если все действующие учётные записи и группы хранятся на заранее определённом домене, и между клиентами и сервером нет прокси-серверов, блокирующих Windows-аутентификацию. При этом нужно быть очень осторожными, назначая права доступа пользователям, поскольку вы одновременно задаёте и права для работы в Windows. Чтобы настроить ASP.NET на работу в режиме Windows-аутентификации, необходимо изменить файл настройки Web-проекта Web.config или, при необходимости, конфигурационный файл сервера, расположенный по адресу
WINDOWS_FOLDER\Microsoft.NET\Framework\.NET version\CONFIG\Machine.config.
В следующем примере будет использован исключительно файл проекта – Web.config. В этом файле нужно найти раздел authentication и присвоить его атрибуту mode значение Windows:
<authentication mode="Windows" />
|
Теперь можно приступить непосредственно к программированию и реализации аутентификации на основе Windows. .NET Framework предоставляет для этого два основных класса:
Имя пользователя и название группы хранятся в объекте WindowsIdentity в формате DOMAIN\UserName и DOMAIN\Group, соответственно. Исключение составляют лишь встроенные группы, например группа Administrators, обратиться к которой можно через WindowsIdentity, используя строку подключения: BUILTIN\Administrators. Другой способ задать встроенную группу – воспользоваться перечислением System.Security.Principal.WindowsBuiltInRole.
Из рисунка 1 видно, что объект WindowsIdentity позволяет:
Рисунок 1. Объект WindowsIdentity.
В приложениях ASP.NET к объекту WindowsIdentity можно обратиться следующим образом:
HttpContext.Current.User.Identity |
WindowsIdentity позволяет, кроме всего прочего, определить, к какой роли принадлежит текущий пользователь. Свойство User в этой цепочке реализует интерфейс IPrincipal, дающий возможность определить роль пользователя путём вызова функции IsInRole, имеющей следующий синтаксис:
Public Overridable Function IsInRole(ByVal role AsString) AsBoolean Member of: System.Security.Principal.Iprincipal |
Давайте немного отвлечемся от теории и попробуем реализовать практический пример. Для этого создайте новый проект ASP.NET Web Application и введите следующий код:
<%@ Page Language="vb" AutoEventWireup="false" Codebehind="default.aspx.vb" Inherits="AuthSample.WebForm1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title>Authentication Sample</title> <meta name="GENERATOR" content="Microsoft Visual Studio .NET 7.1"> <meta name="CODE_LANGUAGE" content="Visual Basic .NET 7.1"> <meta name=vs_defaultClientScript content="JavaScript"> <meta name=vs_targetSchema content="http://schemas.microsoft.com/intellisense/ie5"> </head> <body MS_POSITIONING="GridLayout"> <form id="Form1" method="post" runat="server"> </form> </body> </html> |
Public Class WebForm1 Inherits System.Web.UI.Page #Region" Web Form Designer Generated Code "'Этот вызов нужен Web Form Designer-у. <System.Diagnostics.DebuggerStepThrough()> PrivateSub InitializeComponent() EndSub'NOTE: The following placeholder declaration is required by the Web 'Form Designer. Do not delete or move it.Private designerPlaceholderDeclaration As System.ObjectPrivateSub Page_Init(ByVal sender As System.Object, _ ByVal e As System.EventArgs) HandlesMyBase.Init 'CODEGEN: This method call is required by the Web Form Designer'Do not modify it using the code editor. InitializeComponent() EndSub #End Region PrivateSub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) HandlesMyBase.Load Dim s AsString s = "<p><b>Name:</b> " & HttpContext.Current.User.Identity.Name & "</p>" _ & "<p><b>Authentication type:</b> " _ & HttpContext.Current.User.Identity.AuthenticationType.ToString _ & "</p>" & "<p><b>Is authenticated:</b> " _ & HttpContext.Current.User.Identity.IsAuthenticated.ToString & "</p>" _ & "<p><b>Is admin:</b> " _ & HttpContext.Current.User.IsInRole("Administrator").ToString _ & "</p>" Response.Write(s) EndSubEndClass |
Если был выбран режим аутентификации Windows, и настройки IIS не вызвали никаких конфликтных ситуаций, то вы получите соответствующую информацию о текущем пользователе. Если же поле имени пользователя и типа аутентификации оказались пустыми, нужно настроить IIS. Для этого выполните следующие действия:
Рисунок 2. Настройка IIS.
На этом мы закончим рассмотрение Windows-аутентификации и перейдём к аутентификации формой.
Аутентификация формой или, как её ещё называют, аутентификация на основе Cookie-файлов, имеет ряд преимуществ по сравнению с Windows-аутентификацией:
Но, несмотря на такое изобилие возможностей аутентификации на основе формы, существует и одно весомое ограничение – пользователь должен разрешить применение cookie-файлов. Без этого аутентификация формой с применением средств ASP.NET работать не будет. Обратите внимание на слова "…с применением средств ASP.NET…". Это означает, что не будет работать механизм, освобождающий разработчика от рутинных операций бесконечных проверок. Иными словами, все запросы, поступившие от пользователя, ещё не прошедшего аутентификацию, переадресовываются на страницу регистрации, где он вводит необходимую информацию (чаще всего имя пользователя и пароль). Полученные сведения передаются среде ASP.NET, где происходит их проверка. В случае успеха пользователю передаётся cookie-файл, в котором содержится билет для авторизации (Authorization ticket), имя пользователя и ключ для идентификации пользователя при следующих обращениях. В результате все последующие обращения броузера будут автоматически включать в заголовки сведения из cookie, направляемые на проверку в среду ASP.NET. Поэтому, если пользователь не разрешит использование cookie, проверять, прошёл ли он аутентификацию, придется при каждом обращении. В предыдущих версиях ASP для этого применяли, например, объект Session, примерно следующим образом:
If Not (Session("Registered") = "1") Then Response.Redirect("login.asp") EndIf |
Для использования аутентификации формой сначала нужно настроить конфигурацию Web-проекта. Для этого измените содержимое тега <authentication> файла Web.config:
<authentication mode="Forms"> <forms name="ASP_XML_Form" loginUrl="login.aspx" protection="All" timeout="30" path="/" requireSSL="false" slidingExpiration="true" /> </authentication> |
Давайте подробнее рассмотрим этот код. Атрибут mode определяет способ аутентификации. В предыдущих примерах использовалось значение Windows, которое включало режим Windows-аутентификации. Теперь же будет использоваться режим Forms. Кроме этих двух значений, допустимы Passport и None – первое определяет аутентификацию на основе Microsoft Passport, о которой речь пойдёт позже, вторая отключает её вообще. Тег <forms> присущ только аутентификации на основе формы и включает в себя следующие сведения:
Выше были описаны все возможные атрибуты тега forms, но для корректной работы приложения достаточно использовать лишь 3 из них, как это показано в следующей строке:
<forms name="ASP_XML_Form" loginUrl="login.aspx" protection="All" /> |
Давайте теперь создадим простую страницу регистрации пользователей, которая сверяет введённые имя и пароль с данными из XML-файла. Для этого сначала создайте XML-файл с именем users.xml следующего вида:
<?xml version="1.0" encoding="utf-8" ?> <users> <user> <name>John</name> <password>one</password> </user> <user> <name>Mike</name> <password>two</password> </user> <user> <name>Bill</name> <password>three</password> </user> </users> |
Теперь можно приступить к созданию проекта регистрации пользователей:
<%@ Page Language="vb" AutoEventWireup="false" Codebehind="default.aspx.vb" Inherits="FormAuth._default"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title>default</title> <meta name="GENERATOR" content="Microsoft Visual Studio .NET 7.1"> <meta name="CODE_LANGUAGE" content="Visual Basic .NET 7.1"> <meta name=vs_defaultClientScript content="JavaScript"> <meta name=vs_targetSchema content="http://schemas.microsoft.com/intellisense/ie5"> </head> <body MS_POSITIONING="GridLayout"> <form id="Form1" method="post" runat="server"> </form> </body> </html> |
Imports System.Web.Security PublicClass _default Inherits System.Web.UI.Page #Region" Web Form Designer Generated Code "'This call is required by the Web Form Designer. <System.Diagnostics.DebuggerStepThrough()> _ PrivateSub InitializeComponent() EndSub' NOTE: The following placeholder declaration is required ' by the Web Form Designer.'Do not delete or move it.Private designerPlaceholderDeclaration As System.ObjectPrivateSub Page_Init(ByVal sender As System.Object, _ ByVal e As System.EventArgs) HandlesMyBase.Init 'CODEGEN: This method call is required by the Web Form Designer'Do not modify it using the code editor. InitializeComponent() EndSub #End Region PrivateSub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) HandlesMyBase.Load 'Put user code to initialize the page hereIf context.Current.User.Identity.Name = ""Then Response.Redirect("login.aspx") Else Response.Write("<h2><I>Hello " _ & context.Current.User.Identity.Name & "</I></h2>") EndIfEndSubEndClass |
<%@ Page Language="vb" AutoEventWireup="false" Codebehind="login.aspx.vb" Inherits="FormAuth.WebForm1"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEAD> <title>Registration</title> <meta name="GENERATOR" content="Microsoft Visual Studio .NET 7.1"> <meta name="CODE_LANGUAGE" content="Visual Basic .NET 7.1"> <meta name="vs_defaultClientScript" content="JavaScript"> <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5"> </HEAD> <body MS_POSITIONING="GridLayout"> <form id="Form1" method="post" runat="server"> <table border> <tr> <td>Name</td> <td><asp:TextBox ID="txtName" Runat=server></asp:TextBox></td> </tr> <tr> <td>Password</td> <td><asp:TextBox ID="txtPassword" Runat=server TextMode=Password> </asp:TextBox></td> </tr> <tr> <td colspan=2 align=right><asp:Button ID="btnLogin" Text="Login" Runat=server></asp:Button></td> </tr> </table> <p><asp:Label ID="lbl" Runat=server Visible=False ForeColor=Maroon Font-Bold=True> Authentication failed</asp:Label></p> </form> </body> </HTML> |
Imports System.Xml Imports System.Web.Security PublicClass WebForm1 Inherits System.Web.UI.Page ProtectedWithEvents txtName As System.Web.UI.WebControls.TextBox ProtectedWithEvents txtPassword As System.Web.UI.WebControls.TextBox ProtectedWithEvents lbl As System.Web.UI.WebControls.Label ProtectedWithEvents btnLogin As System.Web.UI.WebControls.Button #Region" Web Form Designer Generated Code "'This call is required by the Web Form Designer. <System.Diagnostics.DebuggerStepThrough()> PrivateSub InitializeComponent() EndSub'NOTE: The following placeholder declaration 'is required by the Web Form Designer.'Do not delete or move it.Private designerPlaceholderDeclaration As System.ObjectPrivateSub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) HandlesMyBase.Init 'CODEGEN: This method call is required by the Web Form Designer'Do not modify it using the code editor. InitializeComponent() EndSub #End Region PrivateSub btnLogin_Click(ByVal sender AsObject, ByVal e As EventArgs) Handles btnLogin.Click Dim xd AsNew XmlDocument, xr As XmlNodeReader Dim sName AsString, sPass AsString' Открываем XML-файл xd.Load(Server.MapPath("users.xml")) ' Активируем XmlNodeReader xr = New XmlNodeReader(xd.Item("users")) ' Ищем нужного пользователяWhile xr.Read If xr.Name = "name"And xr.NodeType = XmlNodeType.Element Then sName = xr.ReadString ' Если не то имя пользователя, то переходим к другомуIf sName <> txtName.Text Then xr.Skip() ElseIf xr.Name = "password"And xr.NodeType = XmlNodeType.Element ThenIf xr.ReadString() = txtPassword.Text Then' Если пароли совпали, значит аутентификация проведена успешно FormsAuthentication.RedirectFromLoginPage(txtName.Text, True) Else' Если нет, то переходим к другому пользователю xr.Skip() EndIfEndIfEndWhile' Если эта строка выполняется, значит данные о пользователе ‘ были введены неверно lbl.Visible = TrueEndSubEndClass |
Давайте теперь проведём "разбор полётов". Все действия начинаются на странице default.aspx, где проверяется, есть ли у текущего пользователя имя:
If context.Current.User.Identity.Name = ""Then Response.Redirect("login.aspx") Else Response.Write("<h2><I>Hello " & context.Current.User.Identity.Name & "</I></h2>") EndIf |
Если имя есть, то на экран будет выведено приветствие, в противном случае пользователь будет переадресован на страницу регистрации login.aspx, где ему будет предложено ввести своё имя и пароль. Введённые сведения сверяются с данными из XML-файла. Если пользователь не будет найден, появится сообщение об ошибке (рисунок 3). В ином случае он будет благополучно переадресован на исходную страницу default.aspx, которая, обнаружив, что у текущего пользователя имя определено, поприветствует его.
Рисунок 3. Неверные данные при регистрации.
Если вы успешно прошли аутентификацию и увидели приветствие, то закройте окно броузера и попробуйте заново запустить страницу default.aspx. Вы сразу увидите перед собой приветствие для того пользователя, чьё имя вы вводили в последний раз. Дело в том, что при регистрации на машине клиента был сохранен cookie-файл. Это произошло в тот момент, когда в коде была вызывана функция RedirectFromLoginPage, и её параметру CreatePersistentCookie было передано значение True:
FormsAuthentication.RedirectFromLoginPage(txtName.Text, True) |
Чтобы исключить передачу cookie-файла, достаточно вызвать эту функцию, передав значение False в параметре CreatePersistentCookie. Есть и другой способ – в странице default.aspx добавьте обработчик события выгрузки страницы со следующим кодом:
Private Sub Page_Unload(ByVal sender AsObject, _ ByVal e As System.EventArgs) HandlesMyBase.Unload FormsAuthentication.SignOut() EndSub |
В результате, после выгрузки главной страницы, пользователь выйдет с сайта. При следующей попытке открыть на сайт ему будет предложено пройти аутентификацию.
В предыдущем примере мы хранили все данные о пользователях в отдельном XML-файле. Но ASP.NET предоставляет возможность хранить сведения об учетных записях прямо в файле конфигурации Web-проекта. Преимуществом этого метода является то, что для его реализации требуется значительно меньше программного кода, поскольку в данном случае программисту не нужно вручную просматривать XML-файл в поисках соответствующих совпадений – он лишь вызывает одну-единственную функцию, которая и выполняет всю работу. Чтобы понять принцип работы этого механизма, давайте ещё раз обратимся к файлу конфигурации, а точнее к тэгу <forms>. Этот тэг, помимо уже описанных ранее атрибутов, может также включать элемент <credentials> - сертификаты:
<authentication mode="Forms"> <forms name="ASP_XML_Form" loginUrl="login.aspx" protection="All" timeout="30" path="/" requireSSL="false" slidingExpiration="true"> <credentials passwordFormat="Clear"> <user name="John" password="one"/> <user name="Mike" password="two"/> <user name="Bill" password="three"/> </credentials> </forms> </authentication> |
Как видно, элемент <credentials> содержит один единственный атрибут – passwordFormat. Этот атрибут определяет способ хранения пароля и принимает следующие значения:
Если вы выберете какой-нибудь из алгоритмов хэширования, то пароль уже нельзя будет хранить в исходной форме в файле конфигурации – его нужно будет сначала хэшировать и лишь после этого присвоить полученный результат атрибуту password. В противном случае, когда ASP.NET будет проводить аутентификацию, пароли просто не совпадут.
Теперь, когда мы имеем свежеиспечённую базу учётных записей, давайте вернёмся к предыдущему приложению и изменим код обработчика события нажатия кнопки регистрации в странице login.aspx:
Private Sub btnLogin_Click(ByVal sender AsObject, ByVal e As EventArgs) _ Handles btnLogin.Click If FormsAuthentication.Authenticate(txtName.Text, txtPassword.Text) Then' Если пользователь найден в разделе сертификатов, значит, ' регистрация проведена успешно FormsAuthentication.RedirectFromLoginPage(txtName.Text, False) Else' Иначе – выводим сообщение об ошибке lbl.Visible = TrueEndIfEndSub |
Теперь сравните этот код с тем, который использовался в предыдущем примере. Как видите, он сократился до единственного запроса, возвращающего True или False — мы избавились от цикла и множества проверок.
Чтобы не забегать вперед, пока не будем рассматривать пример кода, работающего с хэшированными паролями. Дело в том, что в 3-й части этой статьи, которая будет посвящена криптографии, будет рассказано обо всех тонкостях хэширования и шифрования данных, и вы сможете сами применить эти методы на практике.
Давайте теперь рассмотрим пример работы с ещё одним хранилищем данных о пользователях – с базой данных MS SQL Server. Большинство сайтов для хранения содержимого используют базы данных. Сведения о пользователях также не являются исключением и вполне могут занять своё место в общем пуле данных. Чтобы своими глазами увидеть, как всё это работает, давайте создадим тестовое приложение. Оно будет основано на уже знакомой по предыдущим примерам странице регистрации. Прежде всего, необходимо создать базу данных. Для этого в утилите SQL Query Analyzer нужно выполнить следующий код на языке TSQL:
-- Создание БД 'FormAuthUsers' и таблицы 'Users' CREATE DATABASE FormAuthUsers GO USE FormAuthUsers GO CREATETABLE [Users] ( [ID] [int] IDENTITY (1, 1) NOTNULL, [UserName] [nvarchar] (50), [Password] [nvarchar] (50), CONSTRAINT [PK_Users] PRIMARYKEYCLUSTERED ([ID]) ON [PRIMARY] ) ON [PRIMARY] GO --Заполнение таблицы 'Users'INSERTINTO Users (UserName, Password) VALUES('John'</str>, 'one') GO INSERTINTO Users (UserName, Password) VALUES('Mike', 'two') GO INSERTINTO Users (UserName, Password) VALUES('Bill', 'three') GO --Создание процедуры 'FindUser'CREATEPROCEDURE FindUser @Name nvarchar(50), @Password nvarchar(50) ASSELECT COUNT(ID) FROM Users WHERE UserName = @Name AND Password = @Password GO |
В результате выполнения этого кода будет создана БД с именем "FormAuthUsers", содержащая таблицу Users с тремя записями и хранимую процедуру FindUser. Эта процедура возвращает количество имен пользователей, удовлетворяющих запросу, который формируется из передаваемых ей параметров.
Теперь, когда БД готова, можно приступить к созданию страницы, работающей с ней. Возьмите предыдущий пример и замените обработчик события нажатия кнопки на странице login.aspx.
Imports System.Data.SqlClient Imports System.Web.Security PublicClass WebForm1 Inherits System.Web.UI.Page ProtectedWithEvents txtName As System.Web.UI.WebControls.TextBox ProtectedWithEvents txtPassword As System.Web.UI.WebControls.TextBox ProtectedWithEvents lbl As System.Web.UI.WebControls.Label ProtectedWithEvents btnLogin As System.Web.UI.WebControls.Button #Region" Web Form Designer Generated Code "'This call is required by the Web Form Designer. <System.Diagnostics.DebuggerStepThrough()> PrivateSub InitializeComponent() EndSub'NOTE: The following placeholder declaration 'is required by the Web Form Designer.'Do not delete or move it.Private designerPlaceholderDeclaration As System.ObjectPrivateSub Page_Init(ByVal sender As System.Object, _ ByVal e As System.EventArgs) HandlesMyBase.Init 'CODEGEN: This method call is required by the Web Form Designer'Do not modify it using the code editor. InitializeComponent() EndSub #End Region PrivateSub btnLogin_Click(ByVal sender AsObject, ByVal e As EventArgs) _ Handles btnLogin.Click Dim cn AsNew _ SqlConnection("server=localhost;database=FormAuthUsers;uid=sa;pwd=;") Dim cm AsNew SqlCommand("FindUser", cn) Dim n AsInteger' Открываем соединениеTry cn.Open() Catch ex As SqlException Response.Write(ex.Message) ExitSubEndTry' Задаём тип команды 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' Если кого-то нашли, значит, регистрация пройдена успешно FormsAuthentication.RedirectFromLoginPage(txtName.Text, False) Else' если никого нет, значит, ошибка lbl.Visible = TrueEndIf cn.Close() EndSubEndClass |
Убедитесь, что строка подключения соответствует вашей БД. Давайте разберёмся, что тут происходит. Во-первых, создаётся объект соединения SqlConnection, которому в качестве параметра передаётся строка подключения к базе данных:
Dim cn AsNew _ SqlConnection("server=localhost;database=FormAuthUsers;uid=sa;pwd=;") |
После этого создаётся экземпляр объекта SqlCommand, служащий для выполнения команд работы с данными. Следующие строки кода открывают соединение, но при этом учитывается возможность исключений, которые вылавливаются обработчиком исключений try-catch.
' Открываем соединение Try cn.Open() Catch ex As SqlException Response.Write(ex.Message) ExitSubEndTry |
Если при открытии соединения возникают какие-либо сбои, пользователь получает соответствующее сообщение, и операция прерывается.
После этого объект SqlCommand настраивается на выполнение хранимой процедуры, и готовятся параметры для её запуска.
Процедура FindUser – она возвращает количество записей, удовлетворяющих заданному условию:
SELECT COUNT(ID) FROM Users WHERE UserName = @Name AND Password = @Password |
В конце кода, получив количество найденных записей, мы просто анализируем это значение и выполняем соответствующие операции.
ПРИМЕЧАНИЕ Зачем проверять количество пользователей, совершенно непонятно – нужно было бы ввести уникальный ключ и не допускать появления двух учетных записей с одинаковым именем (наличие двух записей с одинаковым именем недопустимая вещь). Тогда достаточно было бы проверить наличие имени пользователя, а затем уже проверять соответствие имени и пароля. Это позволило бы также проверять корректность ввода пароля и реагировать по-разному на отсутствие учетной записи и ошибку при вводе пароля. – прим. ред. |
Предположим, вы работаете в организации XYZ. Вам было поручено создать приложение, управляющее информацией о персонале вашей организации. Вы работаете над этим проектом уже 7 месяцев, и вдруг по соображениям безопасности вам было поручено разместить информацию о пользователях на совсем другом сервере, с которым активно работает другой отдел организации XYZ. Непосредственный доступ к серверу вам не обеспечили, поэтому ваш проект не может напрямую обращаться к расположенной на этом сервере базе данных с пользователями. Для решения этой проблемы было решено дать вам возможность разработать Web-службу, через которую вы могли бы осуществлять контроль над аутентификацией пользователей.
Эта выдуманная история раскрывает ещё один способ, позволяющий проводить аутентификацию – использование Web-служб. Web-службы становятся особенно актуальными, когда у вас или у ваших клиентов нет полноценного доступа к серверу. Кроме того, Web-службы применимы не только к Web-приложениям, но они могут также быть использованы и программными продуктами, работающими на самых разнообразных платформах. Это стало возможно благодаря применению технологии SOAP (Simple Object Access Protocol), которая использует стандартные порты TCP/IP и протокол HTTP.
Для работы с Web-службой её, прежде всего, нужно создать. Для этого начните новый проект типа ASP.NET Web Service (рисунок 4).
Рисунок 4. Создание Web-службы.
Теперь, немного изменив код обработчика события нажатия кнопки регистрации из предыдущего примера, вставьте его в исходный код Web-службы:
Imports System.Web.Services Imports System.Data.SqlClient <System.Web.Services.WebService(Namespace := _ "http://tempuri.org/AuthSrvc/Service1")> PublicClass Service1 Inherits System.Web.Services.WebService #Region" Web Services Designer Generated Code "PublicSubNew() MyBase.New() 'This call is required by the Web Services Designer. InitializeComponent() 'Add your own initialization code after the InitializeComponent() callEndSub'Required by the Web Services DesignerPrivate components As System.ComponentModel.IContainer 'NOTE: The following procedure is required by the Web Services Designer'It can be modified using the Web Services Designer. 'Do not modify it using the code editor. <System.Diagnostics.DebuggerStepThrough()> PrivateSub InitializeComponent() components = New System.ComponentModel.Container() EndSubProtectedOverloadsOverridesSub Dispose(ByVal disposing AsBoolean) 'CODEGEN: This procedure is required by the Web Services Designer'Do not modify it using the code editor.If disposing ThenIfNot (components IsNothing) Then components.Dispose() EndIfEndIfMyBase.Dispose(disposing) EndSub #End Region ' Функция, осуществляющая проверку наличия пользователя с заданным именем и паролем <WebMethod()> PublicFunction Authenticate(ByVal UserName AsString, _ ByVal Password AsString, ByRef ErrMessage AsString) AsBooleanDim cn AsNew _ SqlConnection("server=localhost;database=FormAuthUsers;uid=sa;pwd=;") Dim cm AsNew SqlCommand("FindUser", cn) Dim n AsInteger' Открываем соединениеTry cn.Open() Catch ex As SqlException ' Если есть исключение, то передаём его описание параметру ErrMessage ErrMessage = ex.Message ExitFunctionEndTry' Задаём тип команды cm.CommandType = CommandType.StoredProcedure ' Добавляем параметры имениDim prmName = New SqlParameter("@Name", SqlDbType.NVarChar, 50) prmName.Value = UserName cm.Parameters.Add(prmName) ' Добавляем параметр пароляDim prmPass = New SqlParameter("@Password", SqlDbType.NVarChar, 50) prmPass.Value = Password cm.Parameters.Add(prmPass) ' Выполняем запрос n = cm.ExecuteScalar ' Закрываем соединение cn.Close() ' Анализируем полученный результатIf n > 0 Then' Если кого-то нашли, значит, регистрация пройдена успешноReturnTrueElse' если никого нет, значит, ошибкаReturnFalseEndIfEndFunctionEndClass |
Вы можете тут же проверить работоспособность службы — просто запустите её на выполнение в среде Visual Studio .NET. Если в службе не было ошибок, то вы увидите перед собой экран, содержащий 2 гиперссылки. Одна из них ведёт к описанию Web-службы средствами языка WSDL (Web Service Description Language), а другая (Authenticate) позволяет протестировать службу. Нажмите на вторую гиперссылку и заполните таблицу параметров в появившейся странице (рисунок 5). Если вы введёте в поле UserName строку "John", а в качестве пароля подставите "one", то функция вернёт значение true:
http://localhost/AuthSrvc/AuthSrvc.asmx/Authenticate: <?xml version="1.0" encoding="utf-8" ?> <boolean xmlns="http://tempuri.org/AuthSrvc/Service1">true</boolean> |
Если изменить значение любого из этих полей на недействительное, т. е. на то, которого нет в базе данных, то результат соответственно будет противоположным – False.
Думаю, нет смысла детально разбирать код этой функции, потому что во многом она похожа на свою предшественницу из предыдущего примера. Но, тем не менее, следует обратить особое внимание на обработчик исключений. Если в предыдущем примере он при возникновении каких-либо исключений просто выводил соответствующее сообщение на экран, то в Web-службе сообщение об ошибке возвращается через параметр ErrMessage функции Authenticate.
' Открываем соединение Try cn.Open() Catch ex As SqlException ' Если есть исключение, то передаём его описание параметру ErrMessage ErrMessage = ex.Message ExitFunctionEndTry |
В приложениях, которые будут использовать эту службу, нужно встроить проверку на наличие каких-либо исключений, и при их обнаружении выводить соответствующее сообщение.
Рисунок 5. Проверка работоспособности Web-службы.
Теперь давайте создадим приложение, которое будет использовать эту Web-службу, но только на этот раз для разнообразия создадим приложение Windows. Выполните следующую последовательность действий:
Рис. 6. Примерный вид тестового приложения
Рис. 7. Результаты поиска Web-службы
Теперь можно приступить к написанию программного кода, реализующего этот Web-сервис. Весь необходимый код приведён ниже.
Public Class Form1 Inherits System.Windows.Forms.Form #Region" Windows Form Designer generated code "PublicSubNew() MyBase.New() 'This call is required by the Windows Form Designer. InitializeComponent() 'Add any initialization after the InitializeComponent() callEndSub'Form overrides dispose to clean up the component list.ProtectedOverloadsOverridesSub Dispose(ByVal disposing AsBoolean) If disposing ThenIfNot (components IsNothing) Then components.Dispose() EndIfEndIfMyBase.Dispose(disposing) EndSub'Required by the Windows Form DesignerPrivate components As System.ComponentModel.IContainer 'NOTE: The following procedure is required by the Windows Form Designer'It can be modified using the Windows Form Designer. 'Do not modify it using the code editor.FriendWithEvents Label1 As System.Windows.Forms.Label FriendWithEvents Label2 As System.Windows.Forms.Label FriendWithEvents txtName As System.Windows.Forms.TextBox FriendWithEvents txtPassword As System.Windows.Forms.TextBox FriendWithEvents cmdExit As System.Windows.Forms.Button FriendWithEvents cmdLogin As System.Windows.Forms.Button <System.Diagnostics.DebuggerStepThrough()> PrivateSub InitializeComponent() Me.Label1 = New System.Windows.Forms.Label Me.Label2 = New System.Windows.Forms.Label Me.txtName = New System.Windows.Forms.TextBox Me.txtPassword = New System.Windows.Forms.TextBox Me.cmdExit = New System.Windows.Forms.Button Me.cmdLogin = New System.Windows.Forms.Button Me.SuspendLayout() ''Label1'Me.Label1.Location = New System.Drawing.Point(8, 8) Me.Label1.Name = "Label1"Me.Label1.Size = New System.Drawing.Size(40, 16) Me.Label1.TabIndex = 0 Me.Label1.Text = "Name:"''Label2'Me.Label2.Location = New System.Drawing.Point(8, 40) Me.Label2.Name = "Label2"Me.Label2.Size = New System.Drawing.Size(64, 16) Me.Label2.TabIndex = 1 Me.Label2.Text = "Password:"''txtName'Me.txtName.Location = New System.Drawing.Point(80, 5) Me.txtName.Name = "txtName"Me.txtName.Size = New System.Drawing.Size(216, 20) Me.txtName.TabIndex = 2 Me.txtName.Text = ""''txtPassword'Me.txtPassword.Location = New System.Drawing.Point(80, 37) Me.txtPassword.Name = "txtPassword"Me.txtPassword.PasswordChar = Microsoft.VisualBasic.ChrW(42) Me.txtPassword.Size = New System.Drawing.Size(216, 20) Me.txtPassword.TabIndex = 3 Me.txtPassword.Text = ""''cmdExit'Me.cmdExit.DialogResult = System.Windows.Forms.DialogResult.Cancel Me.cmdExit.Location = New System.Drawing.Point(216, 72) Me.cmdExit.Name = "cmdExit"Me.cmdExit.Size = New System.Drawing.Size(80, 24) Me.cmdExit.TabIndex = 4 Me.cmdExit.Text = "Exit"''cmdLogin'Me.cmdLogin.Location = New System.Drawing.Point(128, 72) Me.cmdLogin.Name = "cmdLogin"Me.cmdLogin.Size = New System.Drawing.Size(80, 24) Me.cmdLogin.TabIndex = 5 Me.cmdLogin.Text = "Login"''Form1'Me.AcceptButton = Me.cmdLogin Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13) Me.CancelButton = Me.cmdExit Me.ClientSize = New System.Drawing.Size(304, 103) Me.Controls.Add(Me.cmdLogin) Me.Controls.Add(Me.cmdExit) Me.Controls.Add(Me.txtPassword) Me.Controls.Add(Me.txtName) Me.Controls.Add(Me.Label2) Me.Controls.Add(Me.Label1) Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog Me.MaximizeBox = FalseMe.MinimizeBox = FalseMe.Name = "Form1"Me.Text = "AuthSrvc Test application"Me.ResumeLayout(False) EndSub #End Region PrivateSub cmdLogin_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles cmdLogin.Click Dim au AsNew localhost.Service1 Dim sErr AsString, bln AsBoolean' Осуществляем аутентификацию Cursor = Cursors.WaitCursor bln = au.Authenticate(txtName.Text, txtPassword.Text, sErr) Cursor = Cursors.Default' - Но сперва учтём возможные исключенияIf sErr <> ""Then MsgBox(sErr) ExitSubEndIf' - А теперь переходим к основной проверкеIf bln = TrueThen MsgBox("Hello " & txtName.Text, MsgBoxStyle.Information) Else MsgBox("Wrong data!", MsgBoxStyle.Exclamation) EndIfEndSubPrivateSub cmdExit_Click(ByVal sender As System.Object, ByVal e _ As System.EventArgs) Handles cmdExit.Click EndEndSubEndClass |
В этом примере можно увидеть очень простой, хотя и длинный код, который содержит обычный вызов функции, выполняющей все необходимые операции.
Как уже говорилось, Web-службы основаны на технологии SOAP и потому могут быть использованы приложениями, работающими совсем на другой платформе. Мы не будем уходить далеко, отрекаясь от Windows, но попробуем вызвать эту же самую Web-службу без применения технологий .NET, т. е. непосредственно через SOAP. Для этого создайте файл сценария на языке VBScript и запустите его на выполнение:
' Создаём экземпляр объекта SoapClient Set sc = CreateObject("MSSOAP.SoapClient") ' Соединяемся с веб-службой и вызываем метод Authenticate sc.mssoapinit "http://localhost/AuthSrvc/AuthSrvc.asmx?WSDL"If sc.Authenticate ("John", "one", s) = TrueThen MsgBox "Hello John", 64 Else MsgBox "Ошибка!", 48 EndIf' Удаляем ссылку на объектSet sc = Nothing |
Как видите, применяя объектную модель SOAP, можно обращаться к Web-службам, используя самые разнообразные языки программирования (даже скрипты!) и платформы.
Но не всё так гладко, как кажется. Под красивой обёрткой Web-сервисов скрываются подводные камни, в первую очередь – это их собственная безопасность. Сами Web-службы являются незащищенным протоколом, передающим информацию в виде XML. Чтобы исключить, а точнее — уменьшить вероятность утечки информации, необходимо обезопасить веб-службы. Для этих целей существует ряд технологий, но наиболее распространены только два из них: Secure Sockets Layer (SSL) и Virtual Private Network (VPN).
При использовании SSL передаваемые данные шифруются, а поступающие данные проходят аутентификацию. При аутентификации используются сертификаты, причем сертификат должен быть как у сервера, так и у клиента. Использование сертификатов, с одной стороны, позволяет удостовериться, что данные попадают в нужные руки, но с другой стороны – нужно быть уверенным, что у получателя есть соответствующий сертификат. Таким образом, для применения SSL нужно:
ПРИМЕЧАНИЕ Несмотря на отсутствие официальной регистрации, триальный сертификат является легальным. |
VPN – это организация виртуальной частной сети на основе глобальной, в частности, Internet’а. Например, пользователь, работающий на удалённой машине, может соединиться с локальной сетью через VPN, используя при этом Internet. С помощью этой технологии вы можете отправлять данные между компьютерами через защищённое соединение, поскольку VPN обладает теми же средствами безопасности, что и локальная сеть. Для обмена данными VPN работает со следующими протоколами: Microsoft Point-to-Point Tunneling Protocol (PPTP), поставляемый с Windows NT 4.0 и Windows 2000, или Layer Two Tunneling Protocol (L2TP), доступный в Windows 2000.
ПРИМЕЧАНИЕ В списке операционных систем приведены лишь те, в которых указанные протоколы стали доступными, т. е. в число этих ОС входят и более поздние версии, например, Windows XP, Windows 2003 Server. |
Web-службы и средства их безопасности – это очень интересные и актуальные темы. С появлением .NET Framework и VS.NET наблюдается значительный рост популярности Web-сервисов. Но мы не станем дальше углубляться в подробности, а вернёмся в русло этой статьи.
Passport – это единая система регистрации, созданная Microsoft, которой можно воспользоваться на любом Web-сайте, являющемся членом этой системы. Важным достоинством данной технологии является то, что пользователю не нужно помнить регистрационные данные для каждого сайта в отдельности. Passport позволяет решить проблему регистрации благодаря тому, что он использует общую базу о пользователях, поэтому на сайтах, поддерживающих Microsoft Passport, вы будете вводить всегда одни и те же регистрационные данные: ваш e-mail и пароль.
Метод Passport-аутентификации использует стандартные технологии Web:
Для использования Passport-аутентификации нужно выполнить следующие действия:
Так же, как и при использовании других типов аутентификации, сначала нужно настроить файл конфигурации проекта. Следующий листинг демонстрирует базовое содержание раздела <authentication> файла настройки:
<authentication mode="Passport"> <passport redirectUrl="login.aspx" /> </authentication> |
Здесь, как и раньше, задаётся тип аутентификации и один-единственный параметр – адрес страницы, на которую будет переадресован пользователь в случае необходимости пройти регистрацию:
<passport redirectUrl="login.aspx" />
|
Ещё одна черта, объединяющая все виды аутентификации, — это интерфейс IIdendity, на основе которого были созданы все классы сведений о пользователе для различных видов аутентификации. Passport-аутентификация не является исключением из этого списка. Средством, реализующим все основные свойства, стал объект System.Web.Security.PassportIdentity инфраструктуры .NET Framework.
Для обозначения ссылки на регистрационную страницу принято использовать специализированный логотип, подгружаемый через Интернет. Из-за того, что этот логотип приходится использовать довольно часто, лучше создать отдельный элемент управления, реализующий компонент аутентификации. Для этого:
Imports System.Web.Security PublicClass passport Inherits System.Web.UI.UserControl #Region" Web Form Designer Generated Code "'This call is required by the Web Form Designer. <System.Diagnostics.DebuggerStepThrough()> PrivateSub InitializeComponent() EndSub'NOTE: The following placeholder declaration 'is required by the Web Form Designer.'Do not delete or move it.Private designerPlaceholderDeclaration As System.ObjectPrivateSub Page_Init(ByVal sender As System.Object, _ ByVal e As System.EventArgs) HandlesMyBase.Init 'CODEGEN: This method call is required by the Web Form Designer'Do not modify it using the code editor. InitializeComponent() EndSub #End Region PrivateSub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) HandlesMyBase.Load Dim id As PassportIdentity ' Получаем данные о текущем пользователе id = CType(context.User.Identity, PassportIdentity) ' Отображаем кнопку регистрации Response.Write(id.LogoTag()) EndSubEndClass |
<%@ Page Language="vb" AutoEventWireup="false" Codebehind="login.aspx.vb" Inherits="PassAuth.WebForm1"%> <%@ Register TagName="passport" TagPrefix="ctl" src="passport.ascx"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title>LogIn</title> <meta name="GENERATOR" content="Microsoft Visual Studio .NET 7.1"> <meta name="CODE_LANGUAGE" content="Visual Basic .NET 7.1"> <meta name="vs_defaultClientScript" content="JavaScript"> <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5"> </head> <body MS_POSITIONING="GridLayout"> <form id="Form1" method="post" runat="server"> <ctl:passport id="pas" runat=server></ctl:passport> </form> </body> </html> |
Все основные действия по аутентификации пользователя берёт на себя объект PassportIdentity, а точнее – служба Microsoft Passport, на которую .NET Framework выходит с помощью этого объекта. Вам остаётся только пожинать плоды, но чтобы знать, что конкретно можно и нужно пожинать, обратитесь к таблице 1, в которой разъясняются все возможные атрибуты, описывающие зарегистрировавшегося пользователя.
Имя атрибута | Описание |
---|---|
Accessibility | Определяет, следует ли использовать опции доступности для данного пользователя на всех узлах, являющихся членами Microsoft Passport. |
BDay_precision | Определяет точность атрибута Birthdate. |
Birthdate | Содержит дату или год рождения пользователя, в зависимости от значения атрибута BDay_precision. |
City | Идентификатор GeoID, хранящий информацию о местоположении пользователя. |
Country | Код страны пользователя по стандарту ISO 3166. |
Directory | Пока не используется. |
Firstname | Имя пользователя. |
Flags | Содержит опции пользовательского профиля. |
Gender | Пол пользователя. |
Lang_Preference | Идентификатор LCID национального языка пользователя. |
Lastname | Фамилия пользователя. |
MemberIDHigh | Верхние 32 бита уникального идентификатора пользователя (PUID). |
MemberIDLow | Нижние 32 бита уникального идентификатора пользователя (PUID). |
MemberName | Содержит имя пользователя и имя домена, разделенные знаком "@". |
Nickname | Дружелюбное обращение к пользователю. |
Occupation | Род занятий. |
PostalCode | Почтовый индекс пользователя в США или в другой стране. |
PreferredEmail | Адрес электронной почты пользователя. |
ProfileVersion | Версия профиля пользователя. |
Region | Идентификатор GeoID, обозначающий место проживания пользователя. |
TimeZone | Часовой пояс, в котором проживает пользователь. |
Для получения доступа ко всем этим атрибутам есть два способа: методом GetProfileObject объекта PassportIdentity и через свойство Item того же объекта. Следующая программа написана на языке C# и демонстрирует оба эти способа.
using System.Web.Security; ... privatevoid Page_Load(object sender, System.EventArgs e) { PassportIdentity id; id = (PassportIdentity)User.Identity; Response.Write(id["Firstname"] + "<br>"); Response.Write(id.GetProfileObject("Lastname") + "<br>"); } |
Теперь давайте вернёмся к проекту с элементом управления passport.ascx и доделаем страницу регистрации . Для этого изменим файлы login.aspx и login.aspx.vb следующим образом:
<%@ Page Language="vb" AutoEventWireup="false" Codebehind="login.aspx.vb" Inherits="PassAuth.WebForm1"%> <%@ Register TagName="passport" TagPrefix="ctl" src="passport.ascx"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title>LogIn</title> <meta name="GENERATOR" content="Microsoft Visual Studio .NET 7.1"> <meta name="CODE_LANGUAGE" content="Visual Basic .NET 7.1"> <meta name="vs_defaultClientScript" content="JavaScript"> <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5"> </head> <body MS_POSITIONING="GridLayout"> <form id="Form1" method="post" runat="server"> <p><asp:Label ID="lblLogin" Runat=server>Please login...</asp:Label> <ctl:passport id="pas" runat=server/></p> <asp:Label ID="lbl" Runat=server Visible=False> <table border> <tr> <th>PUID:</th> <td><asp:Label ID="lblPUID" Runat=server/></td> </tr> <tr> <th>Firstname:</th> <td><asp:Label ID="lblFName" Runat=server/></td> </tr> <tr> <th>Lastname:</th> <td><asp:Label ID="lblLName" Runat=server/></td> </tr> <tr> <th>E-mail:</th> <td><asp:Label ID="lblEmail" Runat=server/></td> </tr> </table> </asp:Label> </form> </body> </html> |
Imports System.Web.Security PublicClass WebForm1 Inherits System.Web.UI.Page ProtectedWithEvents lbl As System.Web.UI.WebControls.Label ProtectedWithEvents lblLogin As System.Web.UI.WebControls.Label ProtectedWithEvents lblPUID As System.Web.UI.WebControls.Label ProtectedWithEvents lblFName As System.Web.UI.WebControls.Label ProtectedWithEvents lblLName As System.Web.UI.WebControls.Label ProtectedWithEvents lblEmail As System.Web.UI.WebControls.Label #Region" Web Form Designer Generated Code "'This call is required by the Web Form Designer. <System.Diagnostics.DebuggerStepThrough()> PrivateSub InitializeComponent() EndSub'NOTE: The following placeholder declaration 'is required by the Web Form Designer.'Do not delete or move it.Private designerPlaceholderDeclaration As System.ObjectPrivateSub Page_Init(ByVal sender As System.Object, _ ByVal e As System.EventArgs) HandlesMyBase.Init 'CODEGEN: This method call is required by the Web Form Designer'Do not modify it using the code editor. InitializeComponent() EndSub #End Region PrivateSub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) HandlesMyBase.Load If User.Identity.IsAuthenticated Then' Если пользователь зарегистрирован, то' создаём объект PassportIdentity и' выводим информацию о нёмDim id As PassportIdentity = CType(User.Identity, PassportIdentity) lbl.Visible = True lblPUID.Text = User.Identity.Name lblFName.Text = id("Firstname") lblLName.Text = id("Lastname") lblEmail.Text = id("PreferredEmail") ' Скрываем сообщение регистрации lblLogin.Visible = FalseElse' Если нет, то предлагаем ему зарегистрироваться' и скрываем таблицу lblLogin.Visible = True lbl.Visible = FalseEndIfEndSubEndClass |
Обратите внимание, что в предыдущем примере одним из полей таблицы было поле PUID, и данные в него мы загружали из свойства User.Identity.Name. Это свойство хранит в себе уникальный идентификатор Passport User ID конкретного пользователя, и, если вам нужно будет получить данные на определённого пользователя, то для его нахождения и обозначения нужно использовать именно это свойство, а не, скажем, атрибут профиля пользователя MemberName. Несмотря на то, что данное свойство Name находится в объекте User.Identity, а не в PassportIdentity, оно содержит нужный вам PUID, поскольку объект User.Identity хранит информацию о текущем зарегистрированном пользователе.
На этом мы завершаем обзор средств аутентификации в среде ASP.NET. В следующей части статьи будет рассматриваться следующую часть системы безопасности ASP.NET – авторизацию.
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() |