Оптимизация производительности делегации 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-специалиста, который больше всего ценится заказчиками.

Похожие статьи:
Щомісяця ми дивимося, що відбувалося на jobs.dou.ua з вакансіями, відгуками та активністю компаній. Найцікавіше у травні: 5111 вакансій —...
Константин Мирин — CEO IT-компании Postindustria. Шесть лет назад он переехал в город Бар в Черногории и открыл там офис. Константин...
Директор Google Ukraine Дмитро Шоломко пішов із компанії, про це повідомило видання AIN, посилаючись на власні джерела. Шоломко...
Два роки тому ми анонсували оновлений рейтинг роботодавців на jobs.dou.ua. І ось знову запрошуємо ІТ-спеціалістів узяти...
Генеративний ШІ суттєво прискорює розробку ПЗ та автоматизує багато процесів. Це, на думку засновника компанії...
Яндекс.Метрика