Аутентификация, авторизация (+имперсонализация), персонализация в ASP2
В принципе возможно строить сайты вообще без какой-либо аутентификации. Просто вписываю юзеру в Куку например номер заказа или какую-либо персонифицированную настройку. Соответсвенно, когда браузер в обьекте Request приносит Куку, можно сразу позиционировать клиента на его заказы и настройки. Это достаточно редко применяемый подход, однако мне удалось реализовать именно такой подход в реально работающем электронном магазине.
Но для того чтобы работать с поддержкой аутентификации и персонализации, зашитой в ASP2, надо выполнить ряд предварительных настроек:
- Установить нормальный MachineConfig, в котором сразу удалить ссылку на ./SQLEXPRESS.
- После этого перезапустить IIS (или перезагрузиться) и посмотреть, что коннект с именнем LocalSqlServer добавляется IIS к каждому ASP2 сайту.
- После этого проганяется утилита aspnet_regsql, которая и создает в указанной базе структуру для аутентификации и персонализации
- Дальше надо дать права на учетные записи для доступа к этой базе. Здесь есть непонятка. По идее процесс аутентификаций происходит под учетной записью процесса aspnet_wp.exe - обычно ASPNET. По идее ключом <processModel> в Web-конфиг можно переопределить учетную запись. Однако, по моим наблюдениям необходимо также давать права на запись 'NT AUTHORITY\NETWORK SERVICE'. В общем для исключения возможных глюков я даю права на обе записи. Заданные на рисунке права - это макстимальный предел, в реальной эксплуатации эти права, конечно, следует последовательными шагами уменьшать и уменьшать.
- Наконец, надо правильно сконфигурить Web-конфиг приложения. По моим наблюдениям, пока стандартную запись коннекта LocalSqlServer, определенную в MashineConfig, не удалишь и не определишь заново - наблюдаются сплошные глюки. Поэтому я поступаю так.
- Теперь можно вызвать утилиту конфигурирования Web-конфига проекта, которая будет работать прямо под девелоперским сервером студии. (Вообще все что мы пока делали не подразумевало еще публикации сайта в IIS).
- Дальше следует убедится в правильности выполнения всех предыдущих шагов
- Наконец, мы заходим на вкладку SECURITY и добавляем там юзеров в нашу базу, например, мастером, после чего они появляются в нашей базе и Web-конфиге приложения.
- В пункте Manage access rules даем всем юзерам доступ к папке с рисунками, иначе даже MasterPage для Login-странички отображаться не будет. В итоге работающий Web-конфиг проекта может выглядеть вот так.
- И вот теперь, если добавить страничку с именем по умолчанию Login.aspx и на нее бросить контрол LOGIN - то вся предыдущая работа окупится сторицей. Никакого конкретно кода программирования для этого простейшего варианта защиты сайта не требуется. Контрол сам выполнил примерно вот такой запрос в базу и если имя/пароль прошло, то выполнил редирект на указанную ему страничку. Теперь свойство User каждой странички (или HttpContext.Current) будет иметь имя юзера и признак IsAuthenticated.
- Для того, чтобы чуть глубже понять, как работают контролы раздела Login на ToolBox'е студии, сделаем еще один простой эксперимент. Изменим определение Forms-аутентификации вот так и создадим вот такую простейшую страничку.
У нас получилось не что иное как тот самый контрол LOGIN, только сделанный самостоятельно. И здесь мы видим еще два важных класса в системе аутентификации ASP2 из пространства имен System.Web.Security. Класс FormsAuthentication представляет собой фактически программное представление тега FORMS из конфигурации и имеет метод для входа собственно в сайт. А класс Membership - это тот самый класс манипулирования юзерами в базе (к которому мы обращались через Web Site Administration Tool). В том числе, обращаясь к алгоритмам SHA1, этот класс проводит проверку пароля. На самом деле - класс Membership - это оболочка, обращающаяся к классу MembershipProvider. Взглянув на Web Site Administration Tool мы увидим все три встроенных провайдера. Свой провайдер создается с помощью переопределения базовых методов стандартного провайдера. И хотя это довольно распространеная техника, здесь я ее рассматривать не буду.
Итак мы познакомились со всеми базовыми элементами аутентификации/авторизации ASP2. В этой короткой заметке я упомяну лишь еще пару моментов ASP2-техники.
Любопытно, что хост-процесс девелоперского сервера студии всегда возвращает имперсонализированный контекст (в отличии от контекста IIS, рассмотренного выше), именно поэтому мы обычно наблюдаем при первом запуске приложения под IIS такую картинку.
В заключение я опишу способ разрешения одной небольшой проблемки, возникающей при использовании стандартных конфигурационных баз ASP2. Дело в том, что при проэктировании сайта ВЕСЬМА УДОБНО работать на основании ролей. Например, проверять принадлежность к той или иной роли в CMS. Или, как было показано - менять меню сайта в зависимости от роли, к которой принадлежит юзер. Но...
Но как заказчику назначить в пустой базе (где еще нету юзеров вообще, а только сконфигурены роли) того самого первого админа в базе, из под которого админу заказчика удасться залогинится и назначить принадлежность всем прочим юзерам к различным заведомо сконфигуренным группам? Скопировать WSAT на хостинг? Или переопределить на машине заказчика MashineConfig - чтобы его LocalSqlServer указывал на сервер хостинга? Но для этого заказчику придется у себя еще и VS2005 поставить. В общем, проблема неразрешима без отдельного сайта, производящего ПЕРВИЧНУЮ ИНИЦИАЛИЗАЦИЮ базы на хостинге. Причем силами самого заказчика. Его админа, который впишет туда свой первичный пароль, из под которого позже залогинится сам и станет раздавать всем прочим права. Причем этот пароль будет недоступен девелоперу.
В общем, если вы сообразили, для чего все это надо - то можете читать дальше. В противном случае (если таких жестких требований не предъявляется) можно сделать гораздо проще.
Этот простой сайт будет состоять из ОДНОЙ странички и разворачивается админом заказчика в любой из подкаталогов CMS. У него очень простой CONFIG, который надо будет приконнектить к конфигурируемой базе:
00001: <?xml version="1.0"?> 00002: <configuration> 00003: <appSettings/> 00004: <connectionStrings> 00005: <remove name="LocalSqlServer"/> 00006: <add name="LocalSqlServer" connectionString="server=XXXXXXXXXX;Initial Catalog=YYYYYYYY;User ID=ZZZZZZZZ;Password=WWWWWWWWW" providerName="System.Data.SqlClient"/> 00007: <add name="ConnStr" connectionString="server=XXXXXXXXXX;Initial Catalog=YYYYYYYY;User ID=ZZZZZZZZ;Password=WWWWWWWWW" providerName="System.Data.SqlClient"/> 00008: </connectionStrings> 00009: <system.web> 00010: <authentication mode="Windows"/> 00011: <roleManager enabled="true" /> 00012: <compilation debug="true" strict="true" explicit="true"/> 00013: <pages> 00014: <namespaces> 00015: <clear/> 00016: <add namespace="System"/> 00017: <add namespace="System.Collections"/> 00018: <add namespace="System.Collections.Specialized"/> 00019: <add namespace="System.Configuration"/> 00020: <add namespace="System.Text"/> 00021: <add namespace="System.Text.RegularExpressions"/> 00022: <add namespace="System.Web"/> 00023: <add namespace="System.Web.Caching"/> 00024: <add namespace="System.Web.SessionState"/> 00025: <add namespace="System.Web.Security"/> 00026: <add namespace="System.Web.Profile"/> 00027: <add namespace="System.Web.UI"/> 00028: <add namespace="System.Web.UI.WebControls"/> 00029: <add namespace="System.Web.UI.WebControls.WebParts"/> 00030: <add namespace="System.Web.UI.HtmlControls"/> 00031: </namespaces> 00032: </pages> 00033: </system.web> 00034: </configuration>
И такая же простая, но важная страничка:
00001: <%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="Configure_Default" 00002: Theme="SkinFile" %> 00003: 00004: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 00005: <html xmlns="http://www.w3.org/1999/xhtml"> 00006: <head id="Head1" runat="server"> 00007: <title>ASP_Configure</title> 00008: </head> 00009: <body> 00010: <form id="form1" runat="server"> 00011: <asp:Label ID="Label8" runat="server" Text="Вы вошли как : "></asp:Label> 00012: <asp:Label ID="L0" runat="server"></asp:Label><br /> 00013: <asp:MultiView ID="MV1" runat="server" ActiveViewIndex="0"> 00014: <asp:View ID="Anon" runat="server"> 00015: <asp:Label ID="Label9" runat="server" Text="Сорри, на эту страничку непозволительно заходить без Windows-аутентификации"></asp:Label> 00016: <br /> 00017: <asp:Label ID="Label10" runat="server" Text="Для входа на эту страничку сконфигурируйте эту директорию как отдельный сайт и запретите вход сюда анонимам."></asp:Label> 00018: <br /> 00019: <asp:Image ID="Image1" runat="server" ImageUrl="~/Configure.gif" /> 00020: <br /> 00021: <asp:Label runat="server" ID="Label12" Text="Проверьте в конфигурации этого файла наличие строки:"></asp:Label> 00022: <br /> 00023: <asp:Image ID="Image2" runat="server" ImageUrl="Configure1.gif" /> 00024: <br /> 00025: <asp:Label ID="Label11" runat="server" Text="Это обязательные условия безопасности для запуска этой странички."></asp:Label> 00026: </asp:View> 00027: <asp:View ID="NotAnon" runat="server"> 00028: <div> 00029: <asp:Label ID="Label1" runat="server" Text="На этой страничке производится конфигурирование администраторов сайта и занесение учетных данных администраторов в базу."></asp:Label> 00030: <br /><br /><br /> 00031: <table> 00032: <tr> 00033: <td> 00034: <asp:Label ID="Label2" runat="server" Text="Логин администратора сайта"></asp:Label> 00035: </td> 00036: <td> 00037: <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox></td> 00038: <td> 00039: <asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ErrorMessage="*" 00040: ControlToValidate="TextBox1"></asp:RequiredFieldValidator> 00041: </td> 00042: </tr> 00043: <tr> 00044: <td> 00045: <asp:Label ID="Label3" runat="server" Text="Пароль администратора сайта"></asp:Label></td> 00046: <td> 00047: <asp:TextBox ID="TextBox2" runat="server" TextMode="Password"></asp:TextBox></td> 00048: <td> 00049: <asp:RequiredFieldValidator ID="RequiredFieldValidator2" runat="server" ErrorMessage="*" 00050: ControlToValidate="TextBox2"></asp:RequiredFieldValidator> 00051: </td> 00052: </tr> 00053: <tr> 00054: <td> 00055: <asp:Label ID="Label4" runat="server" Text="Пароль еще раз"></asp:Label> 00056: <asp:CompareValidator ID="CompareValidator1" runat="server" ControlToCompare="TextBox2" 00057: ControlToValidate="TextBox3" ErrorMessage="*"></asp:CompareValidator></td> 00058: <td> 00059: <asp:TextBox ID="TextBox3" runat="server" TextMode="Password"></asp:TextBox></td> 00060: <td> 00061: <asp:RequiredFieldValidator ID="RequiredFieldValidator3" runat="server" ErrorMessage="*" 00062: ControlToValidate="TextBox3"></asp:RequiredFieldValidator> 00063: </td> 00064: </tr> 00065: <tr> 00066: <td> 00067: <asp:Label ID="Label5" runat="server" Text="Email администратора сайта"></asp:Label> 00068: </td> 00069: <td> 00070: <asp:TextBox ID="TextBox4" runat="server"></asp:TextBox></td> 00071: <td> 00072: <asp:RequiredFieldValidator ID="RequiredFieldValidator4" runat="server" ErrorMessage="*" 00073: ControlToValidate="TextBox4"></asp:RequiredFieldValidator> 00074: </td> 00075: </tr> 00076: <tr> 00077: <td> 00078: <asp:Label ID="Label7" runat="server" Text="Секретный вопрос админу для восстановления забытого пароля"></asp:Label> 00079: </td> 00080: <td> 00081: <asp:TextBox ID="TextBox5" runat="server"></asp:TextBox></td> 00082: <td> 00083: <asp:RequiredFieldValidator ID="RequiredFieldValidator5" runat="server" ErrorMessage="*" 00084: ControlToValidate="TextBox5"></asp:RequiredFieldValidator> 00085: </td> 00086: </tr> 00087: <tr> 00088: <td> 00089: <asp:Label ID="Label6" runat="server" Text="Ответ"></asp:Label> 00090: </td> 00091: <td> 00092: <asp:TextBox ID="TextBox6" runat="server"></asp:TextBox></td> 00093: <td> 00094: <asp:RequiredFieldValidator ID="RequiredFieldValidator6" runat="server" ErrorMessage="*" 00095: ControlToValidate="TextBox6"></asp:RequiredFieldValidator> 00096: </td> 00097: </tr> 00098: <tr> 00099: <td> 00100: <asp:Label ID="Label13" runat="server" Text="Администратор является членом предварительно сконфигурированной роли"></asp:Label> 00101: </td> 00102: <td align="center"> 00103: <asp:DropDownList ID="DropDownList1" runat="server" DataMember="DefaultView" DataSourceID="SqlDataSource1" 00104: DataTextField="RoleName" DataValueField="RoleName" Width="100%"> 00105: </asp:DropDownList></td> 00106: <td> 00107: </td> 00108: </tr> 00109: <tr> 00110: <td> 00111: <asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:ConnStr %>" 00112: SelectCommand="exec dbo.aspnet_Roles_GetAllRoles @ApplicationName=N'/'"> 00113: <SelectParameters> 00114: <asp:Parameter Name="ApplicationName" DefaultValue="/" /> 00115: </SelectParameters> 00116: </asp:SqlDataSource> 00117: </td> 00118: <td align="center"> 00119: <asp:Button ID="Button1" runat="server" Text="OK" Width="100px" /></td> 00120: <td> 00121: </td> 00122: </tr> 00123: <tr> 00124: <td> 00125: <asp:Label ID="Err" runat="server" ForeColor="Red"></asp:Label></td> 00126: <td align="center"> 00127: </td> 00128: <td> 00129: </td> 00130: </tr> 00131: </table> 00132: </div> 00133: </asp:View> 00134: </asp:MultiView> 00135: </form> 00136: </body> 00137: </html> 00001: Partial Class Configure_Default 00002: Inherits System.Web.UI.Page 00003: 00004: Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load 00005: If Not IsPostBack Then 00006: If System.Security.Principal.WindowsIdentity.GetCurrent.IsAuthenticated Then 00007: L0.Text = System.Security.Principal.WindowsIdentity.GetCurrent.Name 00008: If System.Security.Principal.WindowsIdentity.GetCurrent.IsAnonymous Then 00009: MV1.SetActiveView(Anon) 00010: Else 00011: If System.Security.Principal.WindowsIdentity.GetCurrent.Name = "NT AUTHORITY\NETWORK SERVICE" Then 00012: MV1.SetActiveView(Anon) 00013: Else 00014: MV1.SetActiveView(NotAnon) 00015: Me.DataBind() 00016: End If 00017: End If 00018: Else 00019: MV1.SetActiveView(Anon) 00020: End If 00021: End If 00022: End Sub 00023: 00024: Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click 00025: Dim Result As MembershipCreateStatus 00026: Try 00027: Dim Admin As MembershipUser = Membership.CreateUser(TextBox1.Text, TextBox2.Text, TextBox4.Text, TextBox5.Text, TextBox6.Text, True, Result) 00028: If Admin Is Nothing Then 00029: Err.Text = Result.ToString 00030: Else 00031: Roles.AddUserToRole(TextBox1.Text, DropDownList1.Text) 00032: Err.Text = "Новый администратор создан." 00033: End If 00034: Catch ex As Exception 00035: Err.Text = ex.Message 00036: End Try 00037: 00038: End Sub 00039: End Class
В заключение хотелось бы отметить, что аутентификационные базы и базы профилей сделаны неплохо и весьма полезны. Как хранить в них произвольные бинарные файлы - я рассказал здесь.
|