ГлавнаяРегистрацияВход Приветствую Вас Гость | RSS
   
Меню сайта
Разделы новостей
mp3player
Главная » 2007 » Июль » 8 » Система аутентификации на базе протокола HTTP Basic. Создание своего .NET HttpModule.
Система аутентификации на базе протокола HTTP Basic. Создание своего .NET HttpModule.
21:01
Автор: Anatoly Lubarsky  (anatolylubarsky@hotmail.com)
Источник: www.aspnetmania.com

Большинство Web разработчиков используют обычные HTML-формы для обеспечения информации о правах доступа к страницам приложения, потому что эти формы всем знакомы, могут предоставить дополнительную информацию, запросить дополнительную информацию, кроме имени пользовател и пароля.

Как только юзер получает начальный доступ, приложение начинает отслеживать session, чтобы предоставить пользователю доступ к другим страницам.

Но у этого подхода есть и недостатки:

  1. Большие затраты со стороны разработчика.
  2. Прозрачность приложения.
    • например, часто, посмотрев Source страницы с login/password легко понять, куда происходит post и с какими параметрами
  3. Открытость для атак:
    • кросс-сайтных атак через сценарий:
      • http://www.cert.org/advisories/CA-2000-02.html
      • http://www.microsoft.com/technet/security/crssite.asp
    • атак через различные Http Clients
HTTP "Basic"

В данной статье я приведу другой вариант предоставления права на доступ для анонимного юзера. Система основана на базе протокола HTTP, "basic". Сразу оговорюсь, что в "basic", login и пароль передаются через заголовок HTTP, но как clear text. Этот недостаток сведён на нет в системе HTTP "digest", но "digest" это тема для другой статьи.

Всмотритесь в это окно:

В форумах иногда можно увидеть вопросы типа: "А как сделать чтобы для анонимного пользователя вылетала стандартна форма в браузере: логин, пароль, домен ?"

Как правило такие вопросы остаются либо без ответа, либо ответ такой: "Для этого существуют специальные ISAPI фильтры."

К слову самый известный коммерческий фильтр этого типа это Authentix. Теоретически можно написать и самому, но это во-первых задача не из лёгких, а во-вторых если хостинг чужой, то далеко не каждый хостер согласится поставить ISAPI фильтр на HTTP Server.

Алгоритм аутентификации на базе HTTP "basic":

Он такой:

Этап 1

  1. Проверяем существует ли заголовок HTTP "Authorization", если отсутствует переходим к этапу 2.
  2. Если существует, то мы получаем то, что юзер заполнил в модальном окне, (заголовок "Authorization") строкой следующего вида: "basic abrakadabrasdfsdfsdabrakadabra="
  3. Пропускаем слово "basic", и декодируем оставшуюся часть, закодированную base64. Результатом будет строка username:password
  4. Сравниваем с тем, что хранится у нас в базе данных. Если всё нормально, возвращаем страницу. Если нет, переходим к этапу 2.

В мини проекте, который прилагается к данной статье, я построил базу данных сходно с тем, как описывается в статье на ASPNETMANIA "Создание системы авторизации, основанной на ролях в ASP.NET приложении." (небольшие отличия) Чтобы пример был полнокровный - юзер с ролями, страница с ролями(см. статью).

Этап 2

Возвращаем код состояния HTTP 401 (Unauthorized), и задаём заголовок следующего вида:

WWW-Authenticate: BASIC realm="some-name"

Этот ответ заставляет браузер вывести наше модальное окно, чтобы юзер ввёл логин и пароль для some-name, и повторно установить соединение с логином и паролем, собранным в одну строчку, закодированную base64 (здесь кодирование происходит автоматически).

Алгоритм base64 широко известен, поэтому безопасность не является целью кодирования. Кодируется скорее всего для того, чтобы сделать заголовок непрозрачным.

Реализация аутентификации HTTP "basic" на основе ролей в .NET:

Я имел в своё время нелёгкий опыт реализации на чистом ASP - ещё один способ помимо ISAPI:

Декодирование из base64 выглядело примерно так:

'*** Декодировка из base64 - VBScript
Function Base64Decode(ByVal base64String)

 Const Base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
 Dim dataLength, sOut, groupBegin

 'remove white spaces, If any
 base64String = Replace(base64String, vbCrLf, "")
 base64String = Replace(base64String, vbTab, "")
 base64String = Replace(base64String, " ", "")

 'The source must consists from groups with Len of 4 chars
 dataLength = Len(base64String)
 If dataLength Mod 4 <> 0 Then
 Err.Raise 1, "Base64Decode", "Bad Base64 string."
 Exit Function
 End If


 ' Now decode each group:
 For groupBegin = 1 To dataLength Step 4
 Dim numDataBytes, CharCounter, thisChar, thisData, nGroup, pOut
 ' Each data group encodes up To 3 actual bytes.
 numDataBytes = 3
 nGroup = 0

 For CharCounter = 0 To 3
 ' Convert each character into 6 bits of data, And add it To
 ' an integer For temporary storage. If a character is a '=', there
 ' is one fewer data byte. (There can only be a maximum of 2 '=' In
 ' the whole string.)

 thisChar = Mid(base64String, groupBegin + CharCounter, 1)

 If thisChar = "=" Then
numDataBytes = numDataBytes - 1
thisData = 0
 Else
thisData = InStr(Base64, thisChar) - 1
 End If

 If thisData = -1 Then
Err.Raise 2, "Base64Decode", "Bad character In Base64 string."
Exit Function
 End If

 nGroup = 64 * nGroup + thisData
 Next

 'Hex splits the long To 6 groups with 4 bits
 nGroup = Hex(nGroup)

 'Add leading zeros
 nGroup = String(6 - Len(nGroup), "0") & nGroup

 'Convert the 3 byte hex integer (6 chars) To 3 characters
 pOut = Chr(CByte("&H" & Mid(nGroup, 1, 2))) + _
 Chr(CByte("&H" & Mid(nGroup, 3, 2))) + _
 Chr(CByte("&H" & Mid(nGroup, 5, 2)))

 'add numDataBytes characters To out string
 sOut = sOut & Left(pOut, numDataBytes)
 Next

 Base64Decode = sOut
End Function

К счастью в .NET это делается в 2 строчки:

 byte[] tempConverted = Convert.FromBase64String(strEncoded);
 string userInfo = new ASCIIEncoding().GetString(tempConverted);

Ещё в добром старом ASP приходилось тяжело манипулировать реквестами и респонсами (смотрите алгоритм).

Чтобы легко выполнять HTTP "basic" алгоритм необходимо нечто, некий компонент, через который будут происходить все реквесты для данной аппликации.

В ASP.NET специально для таких задач предусмотрен инструмент, HttpModule.

наш HttpModule

Мы напишем собственный HttpModule, в котором реализуем "basic" алгоритм. Чтобы класс был зарегистрирован как HttpModule, достаточно реализовать в нём интерфейс System.Web.IHttpModule, имеющий 2 метода: OnAuthenticateRequest и OnEndRequest, определяющие этап 1 и этап 2 "basic" алгоритма соответственно.

Ну что ж, к делу:

/*
################################################################################
#Components/AuthBasic.cs #
# #
# #
################################################################################
*/

using System;
using System.Collections;
using System.Configuration;
using System.Security.Principal;
using System.Text;
using System.Web;


namespace HTTPAuth.Components
{
 // реализация интерфейса IHttpModule
 // класс будет называться AuthBasic

 public class AuthBasic : IHttpModule
 {
 public AuthBasic() { }
 public void Dispose() { }


 public void Init(HttpApplication application)
 {
 application.AuthenticateRequest += new EventHandler(this.OnAuthenticateRequest);
 application.EndRequest += new EventHandler(this.OnEndRequest);
 }



 // первый метод OnAuthenticateRequest, реализующий этап 1 алгоритма

 /*
 ##########################################################################
 # OnAuthenticateRequest
 # <summary>
 #
 # </summary>
 ##########################################################################
 */
 public void OnAuthenticateRequest(object source, EventArgs eventArgs)
 {
 HttpApplication app = (HttpApplication) source;

 // дальше идём по алгоритму
 // достаём заголовок Authorization
 // проверяем, существует ли он

 string authorization = app.Request.Headers["Authorization"];
 if ((authorization == null) || (authorization.Length == 0))
 {
AccessDenied(app);
return;
 }

 // проверяем, удостоверяемся,
 // что заголовок вида basic

 authorization = authorization.Trim();
 if (authorization.IndexOf("Basic", 0) != 0)
 {
AccessDenied(app);
return;
 }

 // отсекаем слово basic и декодируем из base64
 // получаем "username:password"

 byte[] tempConverted = Convert.FromBase64String(authorization.Substring(6));
 string userInfo = new ASCIIEncoding().GetString(tempConverted);

 // получаем "username"
 // получаем "password"

 string[] usernamePassword = userInfo.Split(new char[] {':'});
 string username = usernamePassword[0];
 string password = usernamePassword[1];

 // сравниваем username, password против базы данных
 // если всё нормально, получаем список групп юзера из базы данных
 // и создаём экземпляр GenericPrincipal

 string[] groups;
 if (AuthenticateAgent(app, username, password, out groups))
 {
app.Context.User = new GenericPrincipal(new GenericIdentity(username, "HTTPAuth.Components.AuthBasic"), groups);
 }

 // если нет, AccessDenied

 else
 {
AccessDenied(app);
return;
 }
 }



 // второй метод OnEndRequest, реализующий этап 2 алгоритма

 /*
 ##########################################################################
 # OnEndRequest
 # <summary>
 #
 # </summary>
 ##########################################################################
 */
 public void OnEndRequest(object source, EventArgs eventArgs)
 {
 HttpApplication app = (HttpApplication) source;
 if (app.Response.StatusCode == 401)
 {

// Поднимаем модальное окно, realm хранится в web.config

string realm = String.Format("Basic Realm=\"{0}\"", 
 ConfigurationSettings.AppSettings["HTTPAuth.Components.AuthBasic_Realm"]);
app.Response.AppendHeader("WWW-Authenticate", realm);
 }
 }


 // Вход воспрещён - Unauthorized

 /*
 ##########################################################################
 # AccessDenied
 # 401 - Access Denied
 # <summary>
 # app in ; HttpApplication
 # </summary>
 ##########################################################################
 */
 private void AccessDenied(HttpApplication app)
 {
 app.Response.StatusCode = 401;
 app.Response.StatusDescription = "Access Denied";

 // Пишем в браузер

 app.Response.Write("401 Access Denied");
 app.CompleteRequest();
 }



 // следующий метод реализует проверку против базы данных
 // на основе ролей
 // если всё нормально, возвращает true и список групп пользовател
 // который нужен для создания экземпляра GenericPrincipal

 /*
 ##########################################################################
 # AuthenticateAgent
 #
 # <summary>
 # Authenticates Agent, returns true/false
 # app in ; HttpApplication
 # User in ; username
 # Password in ; password
 # groups out; agent groups to create GenericPrincipal
 # </summary>
 ##########################################################################
 */
 protected virtual bool AuthenticateAgent(HttpApplication app, string username, string password, out string[] groups)
 {
 groups = null;
 int lagentID = 0;
 string lpageURL = "";

 // экземпляр класса, который осуществляет работу с базой
 // код прилагаетс

 SqlDataProvider dataProvider = new SqlDataProvider();


 // проверим есть ли вообще такой юзер

 // get agent if exists
 lagentID = dataProvider.getAgentByUsernamePassword(username, password);
 if (lagentID == 0)
return false;


 // проверим есть ли вообще у него группы

 // get agent groups
 ArrayList arrAgentsGroups = new ArrayList();
 arrAgentsGroups = dataProvider.getGroupsByAgentID(lagentID);
 if (arrAgentsGroups.Count == 0)
return false;


 // проверим есть ли группы у запрашиваемой страницы

 // get pages groups
 lpageURL = app.Request.Path;
 ArrayList arrPagesGroups = new ArrayList();
 arrPagesGroups = dataProvider.getGroupsByPageURL(lpageURL);
 if (arrPagesGroups.Count == 0)
return false;


 // проверим если хотя бы одна группа юзера
 // находится в списке групп запрашиваемой страницы
 // если да - возвращаем true

 // check if at least one agent group is in Page Groups List
 string[] pagegroups = (String[]) arrPagesGroups.ToArray(typeof(String));
 groups = (string[]) arrAgentsGroups.ToArray(typeof(string));

 foreach (String groupagentID in groups)
 {
foreach (String grouppageID in pagegroups)
{
 if (groupagentID == grouppageID)
 return true;
}
 }
 return false;
 }
 }
}

Всё. HttpModule готов. Подключим его к веб приложению: В файле web.config под sytem.web

 <httpModules>
 <add name="BasicAuthenticationModule" type="HTTPAuth.Components.AuthBasic, HTTPAuth" />
 </httpModules>

формат такой:

 <httpModules>
 <add name="имя" type="полное имя класса, имя сборки" />
 </httpModules>

отменяем встроенную аутентификацию

 <authentication mode="None" />

 <authorization>
 <deny users="?" />
 </authorization>

а в appSettings мы сохраним realm string

 <appSettings>
 <add key="HTTPAuth.Components.AuthBasic_Realm" value="Protected System" />
 </appSettings>

Вот и всё.

К коду прилагаются database scripts и упрощённый класс для работы с базой.

Категория: ASP.Net | Просмотров: 576 | Добавил: VVS | Рейтинг: 0.0/0 |
Всего комментариев: 0
Имя *:
Email *:
Код *:
Поиск
Форма входа
Наш опрос
Чего Вам не хватает на сайте?
Всего ответов: 21
Друзья сайта
Статистика
Возраст