Промежуточная среда веб служб ASP.NET
7.5. Менеджер пользовательских записей
В 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.