Защита ViewState от вмешательства | ||||||||||||||||
Простое средство защиты от подобных фальсификаций — использование машинного контроля аутентификации (machine authentication check) или MAC. Машинный контроль аутентификации разработан для обеспечения гарантии того, что полученные компьютером данные являются именно теми данными, которые были переданы, в частности, что они не были фальсифицированы. Это именно то, что нам требовалось для состояния отображения. Для состояния отображения ASP.NET LosFormatter осуществляет MAC путем хэширования сериализованных данных состояния отображения и добавления этого хэша в конец состояния отображения. (Хэш — это быстро вычисляемый дайджест, который обычно используется в сценариях симметричной безопасности для обеспечения целостности сообщения.) При возврате Web-страницы LosFormatter проводит проверку, чтобы гарантировать, что прикрепленный хэш совпадает с хэшированным значением десериализованного состояния отображения. Если он не совпадает, состояние отображения было изменено по дороге. По умолчанию класс LosFormatter применяет MAC. Однако вы можете настроить то, проводить MAC или нет, через свойство EnableViewStateMac класса Page. По умолчанию оно имеет значение True, которое указывает, что MAC будет иметь место; значение False показывает, что MAC не должен проводиться. Вы можете далее настроить, какой алгоритм хэширования должен применяться при MAC. В файле machine.config найдите атрибут validation элемента <machineKey>. По умолчанию применяется алгоритм хэширования SHA1, но вы по желанию можете заменить его алгоритмом MD5. (Более подробная информация по SHA1 представлена в RFC 3174; более подробная информация по MD5 представлена в RFC 1321.) | ||||||||||||||||
Запись ViewState в файл | ||||||||||||||||
public class PersistViewStateToFileSystem : Page { protected override void SavePageStateToPersistenceMedium(object viewState) { // сериализуем состояние отображения в строку, кодированную по основанию base-64 LosFormatter los = new LosFormatter(); StringWriter writer = new StringWriter(); los.Serialize(writer, viewState); // сохраняем строку на диск StreamWriter sw = File.CreateText(ViewStateFilePath); sw.Write(writer.ToString()); sw.Close(); } protected override object LoadPageStateFromPersistenceMedium() { // определяем, к какому файлу организовывать доступ if (!File.Exists(ViewStateFilePath)) return null; else { // открываем файл StreamReader sr = File.OpenText(ViewStateFilePath); string viewStateString = sr.ReadToEnd(); sr.Close(); // десериализуем строку LosFormatter los = new LosFormatter(); return los.Deserialize(viewStateString); } } public string ViewStateFilePath { get { string folderName = Path.Combine(Request.PhysicalApplicationPath, "PersistedViewState"); string fileName = Session.SessionID + "-" + Path.GetFileNameWithoutExtension(Request.Path).Replace("/", "-") + ".vs"; return Path.Combine(folderName, fileName); } } } | ||||||||||||||||
Программный разбор состояния отображения | ||||||||||||||||
protected virtual void
ParseViewStateGraph( object node, int depth, string label) { tw.Write(System.Environment.NewLine); if (node == null) { tw.Write(String.Concat(Indent(depth), label, "NODE IS NULL")); } else if (node is Triplet) { tw.Write(String.Concat(Indent(depth), label, "TRIPLET")); ParseViewStateGraph( ((Triplet) node).First, depth+1, "First: "); ParseViewStateGraph( ((Triplet) node).Second, depth+1, "Second: "); ParseViewStateGraph( ((Triplet) node).Third, depth+1, "Third: "); } else if (node is Pair) { tw.Write(String.Concat(Indent(depth), label, "PAIR")); ParseViewStateGraph(((Pair) node).First, depth+1, "First: "); ParseViewStateGraph(((Pair) node).Second, depth+1, "Second: "); } else if (node is ArrayList) { tw.Write(String.Concat(Indent(depth), label, "ARRAYLIST")); // display array values for (int i = 0; i < ((ArrayList) node).Count; i++) ParseViewStateGraph( ((ArrayList) node)[i], depth+1, String.Format("({0}) ", i)); } else if (node.GetType().IsArray) { tw.Write(String.Concat(Indent(depth), label, "ARRAY ")); tw.Write(String.Concat("(", node.GetType().ToString(), ")")); IEnumerator e = ((Array) node).GetEnumerator(); int count = 0; while (e.MoveNext()) ParseViewStateGraph( e.Current, depth+1, String.Format("({0}) ", count++)); } else if (node.GetType().IsPrimitive || node is string) { tw.Write(String.Concat(Indent(depth), label)); tw.Write(node.ToString() + " (" + node.GetType().ToString() + ")"); } else { tw.Write(String.Concat(Indent(depth), label, "OTHER - ")); tw.Write(node.GetType().ToString()); } } | ||||||||||||||||
ViewState | ||||||||||||||||
Существует большое количество хороших ресурсов, из которых вы можете многое узнать о состоянии отображения ASP.NET. Пол Вилсон (Paul Wilson) предлагает такие источники как View State: All You Wanted to Know и Page View State Parser. Дино Еспосито (Dino Esposito) написал статью для MSDN Magazine в феврале 2003 под названием The ASP.NET View State, которая обсуждает методы сохранения состояния отображения в файловой системе Web-сервера. Статья Taking a Bite Out of ASP.NET View State, написанная Сьзен Воррен (Susan Warren), обеспечивает хороший высокоуровневый обзор состояния отображения, включая обсуждение его кодирования. На форуме Скотта Гэлловея (Scott Galloway) тоже есть несколько хороших сообщений о том, как работать с состоянием отображения ASP.NET. | ||||||||||||||||
Кэш файлов | ||||||||||||||||
автоматически сгенерированный класс вместе с откомпилированным экземпляром класса хранятся в папке: WINDOWS\Microsoft.NET\Framework\version\Temporary ASP.NET Files | ||||||||||||||||
Machine.config | ||||||||||||||||
WINDOWS\Microsoft.NET\Framework\version\CONFIG\Machine.config | ||||||||||||||||
Configuration | ||||||||||||||||
MSDN
<configuration> <system.web> ... </system.web> <appSettings> <add key="ConnectionString" value="server=localhost;datasource=DemoBase;login=sa"> </add> </appSettings> </configuration> public static SqlConnection Connection { get { return new SqlConnection(System.Configuration.ConfigurationSettings.AppSettings["ConnectionString"]); } } | ||||||||||||||||
<configuration> <configSections> <sectionGroup name="z_setup"> <section name="z_Url2DBmapping" type="System.Configuration.NameValueSectionHandler,System,Version=1.0.3300.0,Culture=neutral,PublicKeyToken=b77a5c561934e089" /> <section name="z_UserRegions" type="System.Configuration.NameValueSectionHandler,System,Version=1.0.3300.0,Culture=neutral,PublicKeyToken=b77a5c561934e089" /> </sectionGroup> </configSections> <system.web> ... </system.web> <z_setup> <z_UserRegions> <add key="rus" value="id=ru;encoding=windows-1251;dateformat=DMY.:" /> <add key="eng" value="id=en;encoding=windows-1251;dateformat=MDY/:" /> </z_UserRegions> </z_setup> </configuration> NameValueCollection vc = (NameValueCollection) System.Configuration.ConfigurationSettings.GetConfig("z_setup/z_UserRegions"); | ||||||||||||||||
<?xml version="1.0" encoding="Windows-1252"?> <configuration> <configSections> <section name="FavoriteUrls" type="System.Configuration.NameValueSectionHandler" /> <section name="htmleditor" type="HtmlEditor.config,HtmlEditor" /> </configSections> <appSettings> ... </appSettings> <FavoriteUrls> <add key="Microsoft" value="http://www.microsoft.com/" /> <add key="MSD2D" value="http://www.msd2d.com/" /> </FavoriteUrls> <htmleditor> </htmleditor> </configuration> NameValueCollection section = (NameValueCollection)ConfigurationSettings.GetConfig("FavoriteUrls"); config cfg = (config)System.Configuration.ConfigurationSettings.GetConfig("htmleditor"); public class config : System.Configuration.IConfigurationSectionHandler { public object Create(object parent, object configContext, System.Xml.XmlNode section) { ... return this; } } | ||||||||||||||||
Аутентфикация | ||||||||||||||||
Аутентификация формой с использованием файла конфигурации <configuration> <location path="articles"> <system.web> <authorization> <deny users="?" /> </authorization> </system.web> </location> <system.web> <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> </system.web> </configuration>Как видно из вышеприведённого листинга, тэг credentials содержит один единственный атрибут – passwordFormat. Этот параметр определяет способ хранения пароля и принимает следующие значения:
Private Sub btnLogin_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnLogin.Click If FormsAuthentication.Authenticate(txtName.Text, txtPassword.Text) Then ' Если пользователь найден в разделе сертификатов, значит, регистрация проведена ‘ успешно FormsAuthentication.RedirectFromLoginPage(txtName.Text, False) Else ' Иначе – выводим сообщение об ошибке lbl.Visible = True End If End Sub Аутентификация формой с использованием собственных методов System.Web.Security.FormsAuthentication.RedirectFromLoginPage("User", False) | ||||||||||||||||
Аутентификация | ||||||||||||||||
Аутентификация с помощью учетных записей Windows Если вы планируете аутентифицировать пользователей по учетным записям, хранящимся на контроллере домена Microsoft Windows NT или в службе каталогов Microsoft Windows® 2000 Active Directory™, то должны использовать аутентификацию IIS в сочетании с провайдером Windows Authentication для ASP.NET, как показано на рис. 2. Тогда вам не придется писать никакого специфического кода для аутентификации. Когда аутентификация осуществляется этим способом, ASP.NET создает объект Windows Principal и связывает его с контекстом приложения для аутентифицированного пользователя. В результате ASP.NET-поток выполняется от имени аутентифицированного пользователя и может получить членство в его группе. В некоторых случаях нужно обходить аутентификацию IIS и реализовать собственный способ. В ASP.NET это допустимо. Например, вы можете создать свой ISAPI-фильтр, проверяющий удостоверения пользователя в Active Directory, и самостоятельно создавать объект Windows Principal. Существуют и другие способы, но тогда придется писать больше кода, чем при прямом использовании .NET-провайдера. | ||||||||||||||||
Аутентификация без применения учетных записей Windows Если вы планируете аутентифицировать пользователей на уровне приложения и при этом у пользователей нет учетных записей в Windows, то скорее всего вы настроите IIS на анонимную аутентификацию. В этом случае рассмотрите следующие аутентификационные модули .NET.
| ||||||||||||||||
Олицетворение и делегирование За счет олицетворения ASP.NET-приложения могут при необходимости выполняться под идентификацией клиента, от имени которого они действуют. Обычно олицетворение реализуется для управления доступом к ресурсам. Тщательно взвесьте, нужно ли оно в вашем случае, так как олицетворение расходуются дополнительные ресурсы сервера. Делегирование - более мощная форма олицетворения, позволяющая серверному процессу обращаться к удаленным ресурсам от имени клиента. При включенном олицетворении ASP.NET получает соответствующий маркер (token) от IIS. ASP.NET-механизм олицетворения по сравнению с аналогичным механизмом в традиционном ASP позволяет добиться в Web-приложениях более тонкого контроля за олицетворением. Для этого в файле Web.config приложения поддерживается специальный параметр. Этот параметр может принимать три значения.
Когда приложение размещено на сетевом UNC-ресурсе, ASP.NET всегда подменяет (олицетворяет) UNC-маркер IIS для доступа к этому ресурсу, если только не используется специфически сконфигурированная учетная запись. В последнем случае ASP.NET использует ее вместо UNC-маркера IIS. В табл. 1 показан маркер потока, используемый ASP.NET для выполнения запроса в зависимости от трех вариантов настройки Web.config. Заметьте, что учетная запись IUSR_SERVER соответствует учетной записи, настроенной для анонимного доступа по текущему URL, и что такая запись не обязательно должна быть типа IUSR_. Учетная запись процесса - это учетная запись, под которой выполняется рабочий процесс приложения (по умолчанию - System Account). Табл. 1. Маркер ASP-потока для конфигураций ASP и IIS
| ||||||||||||||||
Выгрузка файла | ||||||||||||||||
private void Page_Load(object sender, System.EventArgs e) { roaming.classes.page pg = new roaming.classes.page(Page); string sId = Page.Request.QueryString["id"]; if( sId != null ) { if( ((roaming.classes.user)pg.User).ID == 0 ) { string s = Server.HtmlEncode("/roaming/login.aspx?return=/roaming/document.aspx?id="+sId); Response.Redirect(s,true); } unisite.util.fileitem fl = new unisite.util.fileitem(); System.Data.SqlClient.SqlDataReader rs = null; System.Data.SqlClient.SqlCommand cmd = null; try { cmd = new System.Data.SqlClient.SqlCommand(); cmd.Connection= pg.Connection; cmd.CommandType= System.Data.CommandType.Text; cmd.CommandText= "select id, fl_name, fl_type, fl_length, fl_data from ut_file where id=@id"; cmd.Parameters.Add("@id",sId); rs= cmd.ExecuteReader(); while( rs.Read() ) { fl.ID = rs[0]; fl.FileName = rs.GetString(1); fl.ContentType = rs.GetString(2); fl.ContentLength = rs.GetInt32(3); byte[] buf = new Byte[fl.ContentLength]; rs.GetBytes(4,0,buf,0,fl.ContentLength); Response.Clear(); Response.ContentType = fl.ContentType; Response.AddHeader("content-disposition","filename="+fl.FileName); Response.BinaryWrite(buf); } } catch( System.Exception ) { ; } finally { if( rs != null ) rs.Close(); cmd.Dispose(); } } pg.Dispose(); } | ||||||||||||||||
Web сервисы ASP .NET | ||||||||||||||||
Server Controls Overview (MSDN) | ||||||||||||||||
Data Binding Overview | ||||||||||||||||
Подключение паспорта | ||||||||||||||||
Мультиязыковая поддержка в ASP.NET | ||||||||||||||||
private void Page_Load(object sender, System.EventArgs e) { string _sCulture = "ru-RU" if (this.Request.QueryString["Culture"] != null) _sCulture = this.Request.QueryString["Culture"]; // Задаём культуру для текущего потока запроса пользователя Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(_sCulture); // Задаём культуру, используемую ResourceManager для поиска // локализованных ресурсов Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(_sCulture); // Задаём кодировку контента ответа на запрос пользователя this.Response.ContentEncoding = Encoding.GetEncoding(Thread.CurrentThread.CurrentCulture.TextInfo.ANSICodePage); // Создаём экземпляр класса ResourceManager для данного класса и данной сборки ResourceManager _resourceManager = new ResourceManager(this.GetType().BaseType.FullName, this.GetType().BaseType.Assembly); // Получаем локализованное значение из локализованной сборки ресурсов this.headerLabel.Text = _resourceManager.GetString("headerLabel"); } | ||||||||||||||||
Caching Dynamic Pages with ASP.NET | ||||||||||||||||
Темы и скины в ASP.NET2 | ||||||||||||||||
Создание таблиц со сложной шапкой | ||||||||||||||||
Отображение номера ряда в DataGrid | ||||||||||||||||
The ItemDataBound of the DataGrid occurs when data is bound to a item in a DataGrid control. This article explains how to use this event and display serial numbers for rows in a DataGrid. The first step is to create an empty TemplateColumn in the DataGrid control as follows: <Asp:DataGrid runat="Server" id ="DGCategory"> <columns> <Asp:TemplateColumn> </Asp:TemplateColumn> ................. </columns> </Asp:DataGrid> The next step is to specify an event handler for the DataGrid's ItemDataBound using the following event signature. void eventhandlername(object Sender, DataGridItemEventArgs E) { ......................... } This event handler should be mapped to the OnItemDataBound property of the DataGrid control as follows: <Asp:DataGrid runat="Server" id ="DGCategory" OnItemDataBound="eventhandler"> The DataGridItemEventArgs has a property called Item which is of type DataGridItem and gets the referenced item in the DataGrid when the event happens. DataGridItem has a property called DataSetIndex which gives the index number of the DataGridItem. We will have to display this number within the empty template column as follows: E.Item.Cells[0].Text= E.Item.DataSetIndex + 1; Since the DataSetIndex starts with zero one is added to it for displaying the serial number. Also we will have to check the ItemType and display serial number only if the item type is not a Header or a Footer. if(E.Item.Itemtype != ListItemType.Header && E.Item.Itemtype != ListItemType.Footer) { E.Item.Cells[0].Text= E.Item.DataSetIndex + 1; } | ||||||||||||||||
Разработка серверных элементов управления ASP.NET | ||||||||||||||||
| ||||||||||||||||
Submit на другую страницу | ||||||||||||||||
<!-- UserForm.aspx --> Context.Items("UserName") = UserName.text Context.Items("UserPhone") = UserPhone.text Server.Transfer("Result.aspx") <!-- Result.aspx --> Name= Context.Items("UserName") Phone= Context.Items("UserPhone") | ||||||||||||||||
404.aspx | ||||||||||||||||
<customErrors mode="Off"> <error statusCode="404" redirect="/404.aspx" /> <error statusCode="403" redirect="/403.aspx" /> </customErrors> | ||||||||||||||||
POST на другую страницу | ||||||||||||||||
<script language="javascript"> function noPostBack(sNewFormAction) { document.forms[0].action = sNewFormAction; document.forms[0].__VIEWSTATE.name = 'NOVIEWSTATE'; } </script> | ||||||||||||||||
private void Page_Load(object sender, System.EventArgs e) { Submit.Attributes.Add("onclick", "noPostBack('secondform.aspx');"); } | ||||||||||||||||
Обработчик ошибок | ||||||||||||||||
Step 1 - Updating the global.asax
protected void Application_Error(Object sender, EventArgs e) { // Get the exception Exception LastException = Server.GetLastError(); // Redirect to custom error page Response.Redirect(String.Format("~/error.aspx?msg={0}", Server.UrlEncode(LastException.Message))); } | ||||||||||||||||
To fix IIS mappings for ASP.NET | ||||||||||||||||
"%windir%\Microsoft.NET\Framework\version\aspnet_regiis.exe" -i regsvr32 %windir%\Microsoft.NET\Framework\version\aspnet_isapi.dll | ||||||||||||||||
HttpContext.RewritePath | ||||||||||||||||
файл Global.asax void Application_BeginRequest (Object sender, EventArgs e) { // TODO: превращение пути вида // .../quotes/page1.aspx в путь вида // .../rewritepath.aspx?id=1 // HttpContext context = HttpContext.Current; string oldpath = context.Request.Path.ToLower (); string token = "/quotes/page"; int i = oldpath.IndexOf (token); int len = token.Length; if (i != -1) { int j = oldpath.IndexOf (".aspx"); if (j != -1) { string id = oldpath.Substring (i + len, j - (i + len)); string newpath = oldpath.Replace (token + id + ".aspx", "/rewritepath.aspx?id=" + id); context.RewritePath (newpath); } } } | ||||||||||||||||
Control State | ||||||||||||||||
Свойство ControlState класса PageStatePersister является аналогом свойств ViewState. Но, в отличии от последнего, не может быть отключено. Можно использовать это свойство для назначения своего обработчика в контроле. Здесь приведен пример. public class SampleControl : Control { int _idx = 0; protected override void OnInit(EventArgs e) { Page.RegisterRequiresControlState(this); base.OnInit(e); } protected override void LoadControlState( object savedState) { object[] rgState = (object[])savedState; base.LoadControlState(rgState[0]); _ idx = (int)rgState[1]; } protected override object SaveControlState() { object[] rgState = new object[2]; rgState[0] = base.SaveControlState(); rgState[1] = _idx; return rgState; } // ... } | ||||||||||||||||
Последовательность событий страницы | ||||||||||||||||
| ||||||||||||||||
- | ||||||||||||||||
- |