Как отключить предварительный запрос к серверу MarkLogic из C# WebApi

Технический стек следующим образом

  • Ядро .Net WebApi (C#)
  • МаркЛогик 9.0.8.2

Мы разработали MarkLogic API с базовой аутентификацией, и при вызове его из C# WebApi мы передаем учетные данные в заголовке.

Мы заметили, что в журналах доступа MarkLogic есть 2 записи, т.е.

  • xx.xxx.xxx.xx - - [18/May/2020:06:05:35 +0000] "GET /getcontent?query=test&offset=1&size=10&format=Extended&sortOrder=ASC&transform=&IncludeFacet=true HTTP/1.1 " 401 209 - -
  • xx.xxx.xxx.xx - MLUser [18/May/2020:06:05:35 +0000] "GET /getcontent?query=test&offset=1&size=10&format=Extended&sortOrder=ASC&transform=&IncludeFacet= правда HTTP/1.1" 200 44229 - -

Тот же запрос идет с 401, т.е. неавторизованным, а затем сразу с 200, т.е. успешным.

Мы поняли, что вызов конечной точки API из C# WebAPI — это предварительный запрос с ОПЦИЯМИ перед вызовом фактического запроса.

Мы обнаружили, что для решения этой проблемы есть возможность отправить тип контента как «текстовый/обычный», но мы не можем этого сделать, поскольку у нас есть объект JSON, а затем нам нужно сделать больше, чтобы проанализировать текст в объект JSON.

Когда мы звоним из PostMan, мы сталкиваемся с той же проблемой.

Из-за этого, когда у нас есть множество запросов к API, ML API возвращает ошибку 401 на брандмауэре, а затем FW блокирует все запросы, считая это атакой грубой силы.

Есть ли способ отключить предварительный вызов запроса?

MarkLogicHttpClient _client;

var queryParams = new Dictionary<string, string>
{
    { "query", searchRequest.Query },
    { "offset", searchRequest.Offset.ToString() },
    { "size", searchRequest.Size.ToString() },
    { "format", searchRequest.Format },
    { "sortOrder", searchRequest.SortOrder.ToString().ToUpper() },
    { "transform", transform }
};

var searchUri = QueryHelpers.AddQueryString("getcontent", queryParams);

var response = await _client.GetAsync(searchUri);

//Startup.cs

services.AddHttpClient<IMarkLogicClient, MarkLogicHttpClient>(client =>
{
    client.BaseAddress = MarkLogicEndpoint;
})
.ConfigurePrimaryHttpMessageHandler(() =>
    new HttpClientHandler
    {
        Credentials = new NetworkCredential
        {
            UserName = UserName,
            Password = Password
        }
    }
);

person Manish Joisar    schedule 25.05.2020    source источник
comment
Как вы добавляете учетные данные в заголовок? Можете ли вы предоставить код?   -  person Brent Ellingson    schedule 25.05.2020
comment
Привет Брент, Спасибо за ваш ответ. Я добавил код в сам вопрос.   -  person Manish Joisar    schedule 25.05.2020


Ответы (1)


HttpClient отправляет заголовок Authorization только после первого получения ответа 401 с заголовком WWW-Authenticate от сервера.

По умолчанию HttpClient ожидает ответа 401 перед авторизацией каждого запроса.

Есть два способа уменьшить количество ответов 401.

Установите свойство HttpClientHandler.PreAuthenticate

services.AddHttpClient<IMarkLogicClient, MarkLogicHttpClient>(client =>
{
    client.BaseAddress = MarkLogicEndpoint;
})
.ConfigurePrimaryHttpMessageHandler(() =>
    new HttpClientHandler
    {
        // cache the WWW-Authenticate response and send Authorize header
        // on all subsequent requests
        PreAuthenticate = true,
        Credentials = new NetworkCredential
        {
            UserName = UserName,
            Password = Password
        }
    });

Или вручную добавьте заголовок авторизации в HttpClient.DefaultRequestHeaders

services.AddHttpClient<IMarkLogicClient, MarkLogicHttpClient>(client =>
{
    // construct HTTP Basic Authorization header and send on every request
    // Do not add credentials to HttpClientHandler
    var authorization= "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(Username + ":" + Password));
    client.BaseAddress = MarkLogicEndpoint;
    client.DefaultRequestHeaders.Add("Authorization", authorization);
});
person Brent Ellingson    schedule 25.05.2020
comment
Привет Брент, Спасибо за ваш ответ с примером. Поэтому, когда я реализовал PreAuthenticate, он все равно выдавал ошибку 401 в первый раз, а затем все 200 запросов, но затем, если на сервере не было запросов в течение определенного времени (я видел, если нет запросов в течение 2 минут), я снова видел ошибку 401. при втором подходе мы никогда не получим ошибку 401? Я спрашиваю, потому что для тестирования мне нужно следовать долгому процессу. Я имею в виду, что на самом деле я хочу отключить предварительный запрос. - person Manish Joisar; 26.05.2020
comment
При втором подходе вы не должны ожидать никакой предварительной проверки и 401 - person Brent Ellingson; 26.05.2020
comment
Привет, Брент, спасибо. То есть вы имеете в виду, что это был httphandler, который вызывал предварительный запрос? - person Manish Joisar; 26.05.2020
comment
Да, способ, которым HttpClient и HttpHandler реализуют базовую проверку подлинности HTTP, приводит к спорадическим предварительным запросам проверки подлинности даже при включенной предварительной проверке подлинности. Жесткое кодирование заголовка Authorize, как во втором примере, никогда не выполняет предварительную проверку подлинности. - person Brent Ellingson; 26.05.2020
comment
Спасибо, Брент, за ответ. Теперь частота 401 очень низкая, но все же я вижу ошибку 401 в файлах журнала доступа, а затем тот же запрос с 200. Я также перезапустил службу, чтобы убедиться, что она ничего не берет из кеша. И они не были в начале 1-го запроса, они были между ними. Жесткое кодирование тоже делает что-то вроде кэширования? Похоже, что предварительный запрос все еще выполняется и при втором подходе, хотя число 401 минимально. - person Manish Joisar; 27.05.2020
comment
Второй подход решил проблему, спасибо, Брент, за помощь. - person Manish Joisar; 03.06.2020