为什么OnUserAuthenticate在DataSnap REST服务器上被调用两次?

Why is OnUserAuthenticate being called twice on a DataSnap REST server?

本文关键字:两次 调用 OnUserAuthenticate DataSnap REST 服务器 为什么      更新时间:2023-09-26

我在Delphi XE中使用DataSnap创建了一个REST Web服务。我正在使用XMLHttpRequest JavaScript对象调用服务器方法。我在XMLHttpRequest Open方法的第四个和第五个可选参数中传递用于身份验证的用户名和密码。

当我在Delphi中加载我的DataSnap服务器并将调试器连接到IIS(我使用IIS 7.5)时,我可以看到我第一次调用其中一个服务器方法导致DSAuthenticationManager的OnUserAuthenticate事件被调用两次。

在第一个调用中,OnUserAuthenticate事件处理程序的用户和密码参数是空字符串。在第二次调用时,我在XMLHttpRequest Open方法中传递的用户名和密码出现在这些参数中。

一旦用户通过身份验证,使用XMLHttpRequest对任何服务器方法的任何后续调用都会导致对OnUserAuthenticate事件处理程序的单个调用,用户的用户名和密码分别出现在用户和密码参数中。

我检查了一些DataSnap类的源代码,并没有发现会导致这种效果的代码,这使我认为这种行为可能源于浏览器或IIS。

为什么OnUserAuthenticate在第一个服务器方法调用时被调用两次,是什么产生了这种效果?


作为对Mat DeLong的回答的回应,我正在展示我用来调用服务器方法的通用函数之一。这个特定的方法进行同步调用。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);
}

编辑:Lex Li似乎是正确的,IIS只在第一次请求失败时使用基本身份验证。另外,每个会话只应该调用OnUserAuthenticate一次,Mat是正确的。在检查了他提供的链接之后,很明显我没有捕获并返回会话id。下面是一个可以工作的代码示例,这次是异步的。它捕获会话id并将其存储在一个隐藏字段中。然后在Pragma报头的每个后续调用中传递该会话id。
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事件处理程序拒绝无效用户。

对于其他技术,例如ASP.NET,这是典型的IIS行为。

初始请求不包含您的用户凭据,并触发401响应。然后第二个请求发送您的用户凭据,然后IIS可以返回200。

不确定这是否适用于DataSnap,但如果您捕获网络数据包,您可能会发现它。

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

在没有看到服务器代码和XHR使用的JavaScript的情况下,我不能确定为什么会看到这种行为。OnUserAuthenticate在一个会话中只被调用一次。

您可以在这里看到XE DataSnap REST协议:http://docwiki.embarcadero.com/RADStudio/en/DataSnap_REST_Messaging_Protocol#Session_Information

第一次调用返回一个会话ID,以后的所有调用都应该指定它。如果这样做,除了第一个调用之外,所有调用都使用给定的会话ID来跳过身份验证过程,并且只去OnUserAuthorize事件(如果分配了一个)。

如果你看到OnUserAuthenticate被调用两次,那很可能是一个bug,除非它是XHR或IIS配置中的一些奇怪的东西。

如果你能提供更多的细节,我很乐意仔细看看。