Оптимизация производительности делегации Kerberos при обращении к устаревшей системе

Любой, кто хоть раз имел дело с делегированием Kerberos, знает, как непросто все правильно настроить. Тонкости и сложности выдачи прав и грамотное конфигурирование SPN кого угодно заведут в тупик. Но иногда и этим дело не ограничивается — особенно, когда в конечном итоге вам нужно обращаться к устаревшей ERP системе. Об этом я и хочу сегодня рассказать.

Начнем с известного факта, что аутентификация Kerberos основана на запросах. То есть каждый запрос к удаленной системе должен содержать действующий мандат Kerberos в HTTP-заголовке Authorization. Вроде бы это не такая уж и большая проблема: мы живем в 2017 году, и пару лишних килобайт данных (да, мандаты Kerberos не столь маленькие, как кажется, и могут достигать 16 Кб) большой роли не сыграют. На самом деле проблема не столько в размере передаваемых данных, сколько в необходимости проверять входящий мандат Kerberos для каждого запроса — что, конечно же, не очень хорошо масштабируется. На всякий случай имейте в виду, что размер мандата и сам по себе может приводить к проблемам, поскольку http.sys может не принимать HTTP-запросы больше 16 килобайт.

Поэтому, чтобы избежать снижения производительности, специалисты из Майкрософт добавили в IIS специальную настройку, которая называется authPersistNonNTLM и позволяет проводить аутентификацию Kerberos только один раз для каждого TCP-соединения. Любопытного читателя может заинтересовать прекрасный пост в блоге, в котором описываются все детали этой настройки. Кто-то скажет: эй, так давайте же просто включим эту настройку и пойдём пить пиво? Не так быстро, дорогой друг, не так быстро — у нас здесь устаревшая система! :) Если точнее, «устаревшая» в данном случае означает систему, которая поддерживает только HTTP 1.0. А это значит — никакой поддержки HTTP keep-alive, что, в свою очередь, приводит к невозможности использовать одно TCP-соединение для нескольких HTTP-запросов. Следовательно, увы и ах — настройка authPersistNonNTLM становится совершенно бесполезной.

Возникает вопрос: а можно ли все-таки что-то оптимизировать при таких печальных обстоятельствах? И, как оказалось, ответ положительный: мы можем добиться повышения производительности, используя метод под названием «пре-аутентификация». Основная идея этого метода крайне проста: вместо дополнительного обращения к серверу после получения ответа 401 Unauthorized, мы заранее добавляем действительный мандат Kerberos к каждому исходящему запросу, избавляясь, таким образом, от лишних обращений к серверу по сети. И все было бы просто, если бы WCF поддерживал пре-аутентификацию по умолчанию. К сожалению, по какой-то странной причине, такой поддержки в WCF «из коробки» нет, и мы остаемся один на один с задачей получения действующего мандата Kerberos и добавления его в качестве правильно сформированного заголовка Authorize. Желательно, не обращаясь к низкоуровневым SSPI API — потому что, вы же знаете, там обитают драконы :)

После множества проб и ошибок мы пришли к следующему:

public dynamic CallSoapMethod<TService>(Func<TService, Task<dynamic>> func, string endPointName)
{
    dynamic service = Activator.CreateInstance(typeof(TService), endPointName);
    
    using (new OperationContextScope(service.InnerChannel))
    {
        HttpRequestMessageProperty requestMessage = new HttpRequestMessageProperty();
        var contextChannel = (IContextChannel)service.InnerChannel;
        var remoteAddressUri = contextChannel.RemoteAddress.Uri;
          
        ICredentials credentials = CredentialCache.DefaultCredentials;
        NetworkCredential credential = credentials.GetCredential(remoteAddressUri, "Kerberos");

        KerberosSecurityTokenProvider tokenProvider = new KerberosSecurityTokenProvider(
            "HTTP/your-service-SPN",
            System.Security.Principal.TokenImpersonationLevel.Impersonation,
            credential);

        KerberosRequestorSecurityToken securityToken = tokenProvider.GetToken(
            TimeSpan.FromMinutes(1)) as KerberosRequestorSecurityToken;

        var securityTokenRequest = securityToken.GetRequest();
        string serviceToken = Convert.ToBase64String(securityTokenRequest);
        
        requestMessage.Headers["Authorization"] = "Negotiate " + serviceToken;
        OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = requestMessage;

        return func.Invoke(service);
    }
}

ВАЖНО: В web.config веб-сайта / веб-сервиса, который обращается к устаревшей системе, должно быть следующее:

<system.web>
    <authentication mode="Windows" />
    <identity impersonate="true" />
</system.web>

иначе делегация идентификатора Kerberos работать не будет.


Что же можно вынести из нашего опыта — ведь о том, что работа с устаревшими системами грозит кучей проблем, вы наверняка знали и до нас? :) Думаем, итог можно подвести такой — именно в ситуациях, когда не подходят стандартные решения, и приходится изобретать и выкручиваться, проявляется истинный профессионализм IT-специалиста, который больше всего ценится заказчиками.

Похожие статьи:
В рубриці DOU Проектор всі бажаючі можуть презентувати свій продукт (як стартап, так і ламповий pet-проект). Якщо вам є про...
В Україні ІТ-спеціалісти без досвіду викладання відтепер можуть пройти інтернатуру в загальноосвітній школі під...
Доброго дня! У даній статті мені би хотілося познайомити читачів із тим, як ми у Wire організували процес...
Протягом усієї війни ми висвітлюємо, як ІТ-індустрія реагує, допомагає та працює. У новому випуску...
Харьковчанин Евгений Мамут — специалист по кинематографическим спецэффектам, стоял у истоков...
Яндекс.Метрика