Почему мои клиенты .NET службы REST отправляют каждый запрос без заголовков проверки подлинности, а затем повторяют его с заголовком проверки подлинности?

Мы запускаем веб-службу REST с API, требуя, чтобы клиенты использовали обычную проверку подлинности. Мы создали набор опрятных образцов на разных языках, демонстрируя, как взаимодействовать с нашим сервисом. Теперь я просматриваю журналы IIS службы и вижу, что следующий шаблон происходит довольно часто:

  • приходит запрос, отклоняется с кодом HTTP 401
  • тот же запрос повторно отправлен и успешно завершен

который выглядит так, как первый запрос отправляется без заголовков авторизации, а затем второй отправляется с правильными заголовками и выполняется успешно. В большинстве случаев запись журнала содержит "пользовательский агент", который является той же строкой, которую мы установили в нашем примере .NET.

Поэтому я предполагаю, что проблема связана только с программами .NET. Проблема не воспроизводится с помощью нашего образца кода, поэтому я предполагаю, что пользователи каким-то образом модифицировали код или написали свой собственный с нуля.

Мы попытались связаться с пользователями, но, по-видимому, они не хотят вкладывать время в исследования. Поэтому было бы неплохо найти наиболее вероятный сценарий, который приводит к такому поведению программ .NET.

Зачем им это делать? Почему они не присоединяли заголовки с первой попытки?

Ответ 1

Это поведение по умолчанию классов HttpClient и HttpWebRequest, которое отображается следующим образом.

Примечание. Ниже текст объясняет субоптимальное поведение, вызывающее проблему, описанную в вопросе. Скорее всего, вы не должны писать свой код, как это. Вместо этого прокрутите ниже скорректированный код

В обоих случаях создайте экземпляр объекта NetworkCredenatial и укажите там имя пользователя и пароль.

var credentials = new NetworkCredential( username, password );

Если вы используете HttpWebRequest - установить .Credentials свойство:

webRequest.Credentials = credentials;

Если вы используете HttpClient - передайте объект учетных данных в HttpClientHandler (измененный код из здесь):

var client = new HttpClient(new HttpClientHandler() { Credentials = credentials })

Затем запустите Fiddler и запустите запрос. Вы увидите следующее:

  • запрос отправляется без заголовка авторизации
  • служба отвечает HTTP 401 и WWW-аутентификацией: Basic realm = "UrRealmHere"
  • запрос отправляется с соответствующим заголовком авторизации (и успешно)

Это объясняется здесь - клиент не знает заранее, что услуге требуется Basic и пытается согласовать протокол аутентификации (а если услуге требуется дайджест отправка основных заголовков в открытом виде бесполезна и может поставить под угрозу клиента).

Примечание: здесь субоптимальное объяснение поведения заканчивается, и объясняется лучший подход. Скорее всего, вы должны использовать код снизу вместо кода сверху.

В тех случаях, когда известно, что услуге требуется Basic, дополнительный запрос может быть устранен следующим образом:

Не устанавливайте .Credentials, вместо этого добавляйте заголовки вручную, используя код здесь. Кодируйте имя пользователя и пароль:

var encoded = Convert.ToBase64String( Encoding.ASCII.GetBytes(
    String.Format( "{0}:{1}", username, password ) ) );

При использовании HttpWebRequest добавьте его в заголовки:

request.Headers.Add( "Authorization", "Basic " + encoded );

и при использовании HttpClient добавьте его в заголовки по умолчанию:

client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue( "Basic", encoded );

Когда вы делаете это, запрос отправляется с правом заголовком авторизации каждый раз. Обратите внимание, что вы не должны устанавливать .Credentials, иначе, если имя пользователя или пароль неверны, один и тот же запрос будет отправляться дважды в оба раза с неправильными учетными данными и оба раза, конечно, уступая HTTP 401.