Разработка распределенных приложений в Microsoft.NET Framework

         

Менеджер пользовательских записей


В WSE уже входят расширения, позволяющие использовать в политике доступа к веб службе несколько видов аутентификации пользователей, а именно:

  • аутентификация Kerberos (в пределах домена Active Directory);
  • аутентификация на основе сертификатов X.509;
  • аутентификация на основе имени пользователя и пароля, используемая вместе с серверным сертификатом (для шифрования трафика) или без него (в этом случае рекомендуется шифрование передаваемой информации на уровне транспортного протокола TCP).

Тот или иной способ аутентификации можно установить в политике при помощи утилиты WseConfigEditor3.exe или прямым редактированием файла политики WSE.

По умолчанию WSE использует для проверки имени и пароля список пользователей Windows, что не всегда может быть удобно. Для ведения нестандартного списка пользователей веб службы можно установить свой менеджер пользовательских записей. Для реализации нестандартной проверки подлинности в WSE имеется механизм так называемых поставщиков токенов безопасности (Microsoft.Web.Services3.Design.TokenProvider). Такие поставщики не являются сами по себе расширениями и не имеют доступа к пакету SOAP, а используется другими расширениями WSE. К последним относятся, в частности, стандартные расширения UsernameOverTransportAssertion и UsernameForCertificateAssertion, использующие менеджер пользовательских записей в момент проверки подлинности имени и пароля пользователя.

Менеджеры описываются в файле web.config в разделе <microsoft.web.services3><security><securityTokenManager>. Каждый менеджер связывается с каким либо именем токена, указанным в атрибуте localName, например как указано ниже.

<microsoft.web.services3> <security> <securityTokenManager> <add localName="UsernameToken" type="Seva.WS.Users.UsersListManager, Seva.WS.UsersManager, Version=1.0.0.0, Culture=neutral, PublicKeyToken=..." namespace="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" > <users file="C:\Inetpub\users.config"/> </add> </securityTokenManager> </security> <policy fileName="wse3policyCache.config" /> </microsoft.web.services3>

При обнаружении использующим аутентификацию расширением записи о токене в заголовке пакета SOAP (в разделе <wsse:Security>) оно использует связанный с данным токеном менеджер, вызывая его метод VerifyToken.
По умолчанию установлен менеджер UsernameTokenManager, связанный с записью в заголовке SOAP вида <wsse:UsernameToken>. Нестандартный менеджер токенов может быть либо унаследован от класса UsernameTokenManager, изменив его функциональность, либо реализован с чистого листа наследованием класса SecurityTokenManager.

К сожалению, при создании наследника класса UsernameTokenManager следует учитывать, что метод AuthenticateToken создаваемого менеджера должен возвращать тот же пароль, который передается в пакете SOAP. Однако хранение самих паролей в базе пользователей – решение, с точки зрения безопасности, неверное. Вместо самого пароля в базе следует хранить его образ (хеш), по которому невозможно восстановить сам пароль. Однако такая реализация метода AuthenticateToken имеет свои основания. Проблема в том, что пароль в заголовках пакете SOAP может передаваться как в открытом виде (при этом требуется шифрование пакета на основе, например, сертификатов X.509) , так и в виде своего образа (digest). Образ вычисляется как хеш от конкатенации некоторой случайной строки, указанной в пакете (nonce), времени создания пакета и самого пароля:

Password_Digest = Base64(SHA1( nonce + created + password)) где SHA1 – широко применяемая криптографическая функция вычисления хеша. Она обеспечивает разный результата для разных аргументов с высокой вероятностью и невозможность вычисления аргумента по результату хеш преобразования. Видно, что для проверки пароля пользователя в данном случае необходимо иметь доступ к самому паролю, а не к его образу. Наилучшим решением данной проблемы будет отказ от передачи образа пароля в пакете и использования защиты всего сообщения на основе сертификатов или защита канала передачи данных на основе SSL.

Предлагаемый далее менеджер паролей работает в обеих случаях – при использовании открытых паролей в сообщении он предполагает, что в базе пользователей хранятся хеши паролей (метод VerifyPlainTextPassword). Этот вариант будет задействован, в частности, при использовании стандартных расширений.


При использовании же хешированных паролей в открытом сообщении предполагается, что в базе пользовательских записей хранятся сами пароли. Этот вариант будет задействован при добавлении клиентом в пакет SOAP элемента <wsse:UsernameToken>, создаваемого классом UsernameToken с параметром PasswordOption.SendHashed. Вариант такого расширения для клиента веб службы будет описан далее.



// UsersManager.cs using System; using System.IO; using System.Reflection; using System.Text; using System.Xml; using System.Xml.Schema; using System.Xml.Serialization; using System.Collections.Generic; using System.Security.Cryptography; using System.Security; using Microsoft.Web.Services3; using Microsoft.Web.Services3.Design; using Microsoft.Web.Services3.Security; using Microsoft.Web.Services3.Security.Tokens; Поскольку для использования в политике WSE сборку удобнее зарегистрировать в GAC, то следует указать номер версии сборки.

[assembly:AssemblyVersionAttribute("1.0.0.0")] namespace Seva.WS.Users { public class UsersListManager: UsernameTokenManager { private UsersList users; public UsersListManager() { users = new UsersList(); } Основной конструктор класса UsersListManager должен загрузить список пользователей из указанного в конфигурации файла. На практике в случае большого числа пользователей следует использовать СУБД и запрос к базе данных пользователей непосредственно в методе AuthenticateToken.

public UsersListManager(XmlNodeList configData): base(configData) { string fileName = configData[0].Attributes["file"].Value; users = UsersList.Load(fileName); } protected override string AuthenticateToken(UsernameToken token) { if (!users.Users.ContainsKey(token.Username)) return null; return users.Users[token.Username]; } Метод VerifyPlainTextPassword модифицирован для работы с образами паролей.

protected override void VerifyPlainTextPassword(UsernameToken token, string authenticatedPassword) { if (token==null) throw new ArgumentNullException("token is null"); String hashed = Utils.HashedPassword(token.Password); if (authenticatedPassword==null || authenticatedPassword=="" || hashed!=authenticatedPassword) throw new Exception("Passwords does not match "); } } Список пользователей хранится в объекте UsersList.


Поскольку обычные классы словарей не могут быть использованы классом форматирования XmlSerialiser, то список пользователей реализует интерфейс IXmlSerializable.

[XmlRoot("users")] public class UsersList: IXmlSerializable { private Dictionary<string, string> usersField; public Dictionary<string, string> Users { get {return usersField;} } public UsersList() { usersField = new Dictionary<string, string>(); } public void ReadXml(XmlReader reader) { reader.Read(); while (reader.NodeType != XmlNodeType.EndElement) { Users.Add(reader.GetAttribute("username"), reader.GetAttribute("password")); reader.Read(); } } public void WriteXml(XmlWriter writer) { foreach (string user in Users.Keys) { writer.WriteStartElement("user"); writer.WriteAttributeString("username", user); writer.WriteAttributeString("password", this.Users[user]); writer.WriteEndElement(); } } public XmlSchema GetSchema() { return null; } Листинг 7.1. Метод NewUser используется для добавления нового пользователя.

public void NewUser(string userName, string password, bool hashed) { if (Users.ContainsKey(userName)) { throw new ApplicationException("Duplicate username."); } if (hashed) { password = Utils.HashedPassword(password); } Users.Add(userName, password); } Методы Load и Save записывают список пользователей и их паролей (или их образов) в файл.

public static UsersList Load(string fileName) { XmlSerializer serializer = new XmlSerializer(typeof(UsersList)); using (StreamReader reader = new StreamReader(fileName)) { return (UsersList) serializer.Deserialize(reader); }; } public void Save(string fileName) { XmlSerializer serializer = new XmlSerializer(typeof(UsersList)); using (StreamWriter writer = new StreamWriter(fileName)) { serializer.Serialize(writer, this); }; } } Статический класс Utils облегчает вызов функции хеширования и преобразует ее результат в строку.

static class Utils { public static string HashedPassword(string password) { SHA1CryptoServiceProvider crypto = new SHA1CryptoServiceProvider(); byte[] inputBuffer = Encoding.Unicode.GetBytes(password); return Convert.ToBase64String(crypto.ComputeHash(inputBuffer)); } } Следующий make файл создает сборку расширения c менеджером пользователей и регистрирует ее в GAC после команды nmake &amp;&amp; nmake install.



all: Seva.WS.UsersManager.dll Seva.WS.UsersManager.dll: UsersManager.cs UsersManager.key csc /t:library /out:Seva.WS.UsersManager.dll /keyfile:TimeAssertion.key /r:Microsoft.Web.Services3.dll UsersManager.cs UsersManager.key: sn -k UsersManager.key install: gacutil -nologo -i Seva.WS.UsersManager.dll На стороне сервера в файле конфигурации (web.config) следует добавить информацию о менеджере учетных записей в элемент <microsoft.web.services3> <security>.

<?xml version="1.0" encoding="utf-8"?> <configuration> ... <microsoft.web.services3> <security> <securityTokenManager> <add type="Seva.WS.Users.UsersListManager, Seva.WS.UsersManager, Version=1.0.0.0, Culture=neutral, PublicKeyToken=..." namespace= "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" localName="UsernameToken"> <users file="C:\Inetpub\users.config"/> </add> </securityTokenManager> </security> <policy fileName="wse3policyCache.config" /> </microsoft.web.services3> </configuration> Кроме процедуры установления идентичности пользователя, может существовать необходимость ограничить доступ к службе тех или иных пользователей. Для этого в разделе <policy><authorization> политики веб службы можно организовать список пользователей, используя элементы <allow> и <deny>.

Для посылки клиентом хеша пароля может использоваться описанное далее расширение UsernameClientAssertion. Оно может быть использовано для не представляющей критической важности веб службы, не использующей сертификаты X.509. Использование выданных или "самодельных" серверных сертификатов X.509 и открытых паролей в зашифрованном сообщении позволяет обеспечить гораздо большую степень безопасности.

[XmlRoot("user")] public struct UserCredential { [XmlAttribute("username")] public string Username; [XmlAttribute("password")] public string Password; } public class UsernameClientAssertion: SecurityPolicyAssertion { private UserCredential credential; public UsernameClientAssertion() { } Метод ReadXml считывает из файла параметры политики, состоящие из имени файла с именем пользователя и паролем.


Хранение паролей в файле должно как минимум сочетаться с защитой данного файла средствами операционной системы. Однако задание пароля в программном коде (например, после ввода его пользователем с клавиатуры) невозможно при использовании веб службы любой не интерактивной программной компонентой.

public override void ReadXml(XmlReader reader, IDictionary<string, Type> extensions) { string fileName = reader.GetAttribute("file"); if (fileName == null) throw new Exception("Attribute 'file' not found in policy"); // Обязательная операция - переход к следующему тегу в файле политики reader.Read(); using (StreamReader stream = new StreamReader(fileName)) { XmlSerializer serializer = new XmlSerializer(typeof(UserCredential)); using (XmlTextReader xmlReader = new XmlTextReader(stream)) { credential = (UserCredential) serializer.Deserialize(xmlReader); } } } Метод CreateClientOutputFilter данного расширения создает фильтр SOAP, который будет добавлять информацию о пользователе в заголовок сообщения.

public override SoapFilter CreateClientOutputFilter( FilterCreationContext context) { return new UsernameClientFilter(this, credential, context); } public override SoapFilter CreateClientInputFilter( FilterCreationContext context) { return null; } public override SoapFilter CreateServiceInputFilter( FilterCreationContext context) { return null; } public override SoapFilter CreateServiceOutputFilter( FilterCreationContext context) { return null; } } Фильтр наследован от класса SendSecurityFilter и добавляет в сообщение токен с паролем в форме хеша в своем методе SecureMessage.

class UsernameClientFilter : SendSecurityFilter { private UserCredential credential; public UsernameClientFilter(UsernameClientAssertion assertion, UserCredential credential, FilterCreationContext filterContext) : base(assertion.ServiceActor, true, assertion.ClientActor) { this.credential = credential; } public override void SecureMessage(SoapEnvelope envelope, Security security) { UsernameToken userToken = new UsernameToken(credential.Username, credential.Password, PasswordOption.SendHashed); security.Tokens.Add(userToken); } } } Для составления файла с пользователями можно использовать следующую вспомогательную программу.



// AddUser.cs using System; using System.IO; using Seva.WS.Users; class MainApp { public static int Main(string[] Args) { if (Args.Length < 3) { Console.WriteLine( "usage: adduser <users' file> <username> <password> [--hashed]"); return 1; } UsersList users = new UsersList(); string fileName = Args[0]; if (File.Exists(fileName)) { users = UsersList.Load(fileName); } bool hashed = false; if (Args.Length==4) { hashed = Args[3]=="--hashed"; } users.NewUser(Args[1], Args[2], hashed); users.Save(fileName); return 0; } } Листинг 7.2. На стороне сервера можно воспользоваться стандартным расширением WSE UsernameOverTransportAssertion без шифрования сообщения, которое будет использовать описанный ранее специальный менеджер пользовательских записей, простейший вариант файла политики wse3policyCache.config указан ниже.

<policies xmlns="http://schemas.microsoft.com/wse/2005/06/policy"> <extensions> <extension name="usernameOverTransportSecurity" type="Microsoft.Web.Services3.Design.UsernameOverTransportAssertion, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> <extension name="requireActionHeader" type="Microsoft.Web.Services3.Design.RequireActionHeaderAssertion, Microsoft.Web.Services3, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> </extensions> <policy name="ServicePolicy"> <usernameOverTransportSecurity /> <requireActionHeader /> </policy> </policies> Файл политики клиента при использовании расширения UsernameClientAssertion имеет следующий вид.

<policies xmlns="http://schemas.microsoft.com/wse/2005/06/policy"> <extensions> <extension name="simpleUserName" type="Seva.WS.Users.UsernameClientAssertion, Seva.WS.UsersManager, Version=1.0.0.0, Culture=neutral, PublicKeyToken=..."/> </extensions> <policy name="ClientPolicy"> <simpleUserName file="user.xml"/> </policy> </policies> Файл user.xml содержит имя пользователя и пароль клиента веб службы.

<user username="user2" password="222" /> При использовании шифрования сообщений, например, на основе сертификатов X.509 и расширения UsernameForCertificateAssertion использовать описанное расширение клиента UsernameClientAssertion не следует, поскольку при шифровании всего сообщения нет необходимости посылать образ пароля вместо него самого. Поэтому можно использовать расширение стандартное UsernameForCertificateAssertion для клиента и сервера, которое будет использовать созданный менеджер пользователей. Наиболее простой способ конфигурирования политик в таком случае заключается в использование утилиты WseConfigEditor3.exe.


Содержание раздела







Forekc.ru
Рефераты, дипломы, курсовые, выпускные и квалификационные работы, диссертации, учебники, учебные пособия, лекции, методические пособия и рекомендации, программы и курсы обучения, публикации из профильных изданий