Опубликован: 11.05.2007 | Доступ: свободный | Студентов: 1706 / 243 | Оценка: 4.36 / 4.25 | Длительность: 16:06:00
Лекция 9:

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