Почему OnUserAuthenticate дважды вызывается на REST-сервере DataSnap?

Я создал веб-службу REST, используя DataSnap в Delphi XE. Я вызываю методы сервера, используя объект JavaScript XMLHttpRequest. Я передаю имя пользователя и пароль для аутентификации в четвертом и пятом необязательных параметрах метода Open XMLHttpRequest.

Когда я загружаю свой сервер DataSnap в Delphi и подключаю отладчик к IIS (я использую IIS 7.5), я вижу, что первый раз, когда я вызываю один из серверных методов, приводит к тому, что событие OnUserAuthenticate DSAuthenticationManager вызывается дважды.

При первом вызове параметры пользователя и пароля обработчика события OnUserAuthenticate представляют собой пустые строки. При втором вызове в этих параметрах появляются имя пользователя и пароль, которые я передал в методе XMLHttpRequest Open.

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

Я проверил исходный код ряда классов DataSnap и не нашел кода, который вызывал бы этот эффект, что наводит меня на мысль, что такое поведение может быть вызвано браузером или IIS.

Почему OnUserAuthenticate вызывается дважды при первом вызове метода сервера и что вызывает этот эффект?


В ответ на ответ Мэта ДеЛонга я показываю одну из общих функций, которые я использую для вызова методов своего сервера. Этот конкретный метод выполняет синхронный вызов. Параметр baseURL обычно представляет собой строку, аналогичную http://mydomain.com/myservice/myservice.dll/datasnap/rest/tservermethods1, а метод может быть myservermethod. Дополнительные параметры используются для передачи параметров серверному методу:

function getJSONSync(baseURL, method) {
  var request = new XMLHttpRequest();
  var url = baseURL + method;
  for (var i= 2; i < arguments.length; i++) {
    url += "/" + arguments[i];
  }
  request.open("GET", encodeURI(url), false);
  request.send(null);
  if (request.status != 200) {
    throw new Error(request.statusText);
  } 
  return jQuery.parseJSON(request.responseText);
}

Редактировать: Лекс Ли, похоже, прав, что IIS использует обычную аутентификацию только в случае сбоя при первом запросе. Кроме того, Мэт прав в том, что OnUserAuthenticate следует вызывать только один раз за сеанс. После изучения предоставленной им ссылки стало очевидно, что мне не удалось захватить и вернуть идентификатор сеанса. Вот пример кода, который работает, на этот раз асинхронно. Он захватывает идентификатор сеанса и сохраняет его в скрытом поле. Затем он передает этот идентификатор сеанса обратно при каждом последующем вызове в заголовке Pragma.

function getJSONAsync(callback, baseURL, method) {
  var request = new XMLHttpRequest();
  var timedout = false;
  request.onreadystatechange = function() {
     if (request.readyState !== 4) { return }
    if (timedout) { return }
    if (request.status >= 200 && request.status < 300)
    {
      callback(jQuery.parseJSON(request.responseText));
      //store the sessionid in a hidden field
      $("#sessionid").val(request.getResponseHeader('Pragma').split(',')[0].split("=")[1]);
    }
  }
  var url = baseURL + method;
  for (var i= 3; i < arguments.length; i++) {
    url += "/" + arguments[i];
  }
  request.open("GET", encodeURI(url), true);
  //pass the sessionid in the Pragma header, if available
  if (! $("#sessionid").val() == "") {
    request.setRequestHeader("Pragma", "dssession="+$("#sessionid").val());
  }
  //time out if request does not complete in 10 seconds
  var timer = setTimeout(function() { timedout = true; request.abort(); }, 10000);
  request.send(null);
}

Изменить: когда я впервые опубликовал эти два примера кода, я включил примеры имен пользователей и паролей в четвертый и пятый параметры метода открытия XMLHttpRequest. Я передавал эти значения явно во время тестирования. Передавать такие параметры не рекомендуется, поэтому я удалил эти параметры в этом редактировании. Если вы опустите эти пароли и не будете передавать их с помощью других методов (например, в заголовке вашего первого вызова метода REST-сервера), браузер отобразит диалоговое окно, запрашивающее у пользователя эту информацию. Как только имя пользователя и пароль будут предоставлены, браузер запомнит эту информацию до конца сеанса. Если вы закроете, а затем снова откроете браузер и вызовете другой метод сервера REST, вам снова будет предложено ввести имя пользователя и пароль. Это предполагает, конечно, что вы отклоняете недействительных пользователей с помощью обработчика событий OnUserAuthenticate.


person Cary Jensen    schedule 05.08.2011    source источник


Ответы (2)


Что ж, это типичное поведение IIS для других технологий, таких как ASP.NET.

Первоначальный запрос не содержит ваших учетных данных пользователя и вызывает ответ 401. Затем второй запрос отправляет ваши учетные данные пользователя, после чего IIS может вернуть 200.

Не уверен, что это относится к DataSnap, но если вы захватываете сетевые пакеты, вы можете это обнаружить.

http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.preauthenticate.aspx

person Lex Li    schedule 06.08.2011
comment
Это кажется правильным. Я проверил пакеты с помощью wireshark. Первый запрос не включает основную информацию заголовка проверки подлинности, а второй запрос включает. Спасибо! - person Cary Jensen; 06.08.2011

Я не могу точно сказать, почему вы видите такое поведение, не видя кода вашего сервера и точного JavaScript, который вы используете с XHR. OnUserAuthenticate предназначен для вызова один раз для сеанса.

Протокол XE DataSnap REST можно посмотреть здесь: http://docwiki.embarcadero.com/RADStudio/en/DataSnap_REST_Messaging_Protocol#Session_Information

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

Если вы видите, что OnUserAuthenticate вызывается дважды, это, скорее всего, ошибка, если только это не что-то странное в конфигурации XHR или IIS.

Если бы вы могли предоставить немного больше деталей, я был бы рад рассмотреть поближе.

person Mat DeLong    schedule 05.08.2011
comment
Мат: Спасибо за ответ. Я вызываю сервер DataSnap как чистый сервер REST без кода, сгенерированного прокси. Например, я использую XMLHttpRequest напрямую (я не использую сгенерированную прокси функцию serverMethods()). При таком использовании OnUserAuthenticate вызывается один раз для каждого вызова метода сервера (за исключением двойного вызова при первом вызове из сеанса браузера). Я отредактировал свой вопрос и добавил вызов XMLHttpRequest. - person Cary Jensen; 05.08.2011
comment
Мэт: У меня сложилось впечатление, что я должен читать идентификатор сеанса из заголовка ответа и возвращать ему заголовки последующих запросов. В настоящее время я этим не занимаюсь и буду изучать. - person Cary Jensen; 06.08.2011
comment
Мэт: Еще раз спасибо за вклад и ссылку. Я изменил свой вопрос, включив в него асинхронный вызов, который фиксирует идентификатор сеанса и возвращает его при последующих вызовах на сервер DataSnap. Конечно, этот код не проверяет значение прагмы dssessionexpires, а просто фиксирует новый идентификатор сеанса, если выдается новый. Теперь, хотя есть два вызова OnUserAuthenticate (первый без имени пользователя или пароля, а второй — без), никакие последующие вызовы серверных методов не вызывают OnUserAuthenticate. - person Cary Jensen; 06.08.2011
comment
Я вижу, что у вас здесь правильный ответ, но я хотел упомянуть только одну вещь. Сервер не выдаст вам новый идентификатор сеанса, пока вы не сделаете запрос без указания идентификатора сеанса. Если вы продолжите указывать недопустимый идентификатор сеанса, он будет продолжать возвращать ошибку с истекшим сроком действия сеанса и никогда не создаст для вас новый сеанс. - person Mat DeLong; 10.08.2011