Промежуточная среда веб служб 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.