AD FS 2.0认证和AJAX

15

我有一个网站正在尝试调用另一个网站上的MVC控制器操作。这些网站都在AD FS 2.0中设置为信赖方信任。当在两个网站之间的浏览器窗口中打开页面时,一切都可以验证并正常工作。但是,当尝试使用jQuery AJAX方法从JavaScript调用控制器操作时,它总是失败。以下是我正在尝试做的代码片段...

$.ajax({
  url: "relyingPartySite/Controller/Action",
  data: { foobar },
  dataType: "json",
  type: "POST",
  async: false,
  cache: false,
  success: function (data) {
    // do something here
  },
  error: function (data, status) {
    alert(status);
  }
});
问题在于AD FS使用JavaScript向受信方发送隐藏的html表单进行发布。当使用Fiddler进行跟踪时,我可以看到它到达AD FS网站并返回此html表单,该表单应该发布并重定向到经过身份验证的控制器操作。问题是,由于ajax请求期望来自控制器操作的json,因此此表单作为ajax请求的结果返回,显然会失败并出现解析器错误。似乎这应该是一个常见的场景,那么正确的方式是如何从AJAX与AD FS通信并处理此重定向呢?

1
如果ajax调用返回的是HTML,显然你不想使用json解析器来解析它。将dataType更改为“html”,并发布返回的HTML示例,这样我就可以向你展示如何编写处理程序以提交返回的表单。 - ironchefpython
问题是我想要返回JSON。AD FS使用一个新的HTML表单进行重定向,以执行所需的握手操作。在浏览器窗口中,这个过程很顺利,但在这里却不行。一旦握手完成,AJAX请求不会发生重定向,我将得到JSON响应。目前,我已经想出了一个解决方法,通过在IFRAME中处理HTML页面的提交,但这并不是理想的解决方案。 - SaaS Developer
我理解您想要获取JSON返回结果,但实际上不会得到JSON。_然而_,如果您想将返回的数据结构视为JSON处理,可以发布一下所返回的HTML示例,我将向您展示如何编写一个处理程序来提交返回的表单而不使用IFRAME - ironchefpython
为什么在我的情况下会出现“没有访问控制Allow-Access-Allow-Origin”错误?这是一个CORS问题,意味着我的浏览器阻止了重定向到我的ADFS服务器,为什么你的请求可以重定向到ADFS服务器? - machinarium
7个回答

4
你有两个选择。 更多信息在此处
第一种选择是在入口应用程序(基于HTML的应用程序)和API解决方案之间共享会话cookie。您需要配置这两个应用程序以使用相同的WIF cookie。仅当这两个应用程序位于相同的根域上时,此方法才有效。请参见上面的帖子或此stackoverflow问题
另一个选择是禁用AJAX请求的passiveRedirect(如Gutek的答案所述)。这将返回401的HTTP状态代码,您可以在JavaScript中处理它。 当检测到401时,您可以在iFrame中加载一个虚拟页面(或“身份验证”对话框,如果需要重新输入凭据,则可以用作登录对话框)。当iFrame完成后,您可以再次尝试调用。这次调用时,会话cookie将存在于调用中,并且应该成功。
//Requires Jquery 1.9+
var webAPIHtmlPage = "http://webapi.somedomain/preauth.html"

function authenticate() {
    return $.Deferred(function (d) {
        //Potentially could make this into a little popup layer 
        //that shows we are authenticating, and allows for re-authentication if needed
        var iFrame = $("<iframe></iframe>");
        iFrame.hide();
        iFrame.appendTo("body");
        iFrame.attr('src', webAPIHtmlPage);
        iFrame.load(function () {
            iFrame.remove();
            d.resolve();
        });
    });
};

function makeCall() {
    return $.getJSON(uri)
                .then(function(data) {
                        return $.Deferred(function(d) { d.resolve(data); });
                    },
                    function(error) {
                        if (error.status == 401) {
                            //Authenticating, 
                            //TODO:should add a check to prevnet infinite loop
                            return authenticate().then(function() {
                                //Making the call again
                                return makeCall();

                            });
                        } else {
                            return $.Deferred(function(d) {
                                d.reject(error);
                            });
                        }
                });
}

iFrame解决方案对我只有在不删除iframe的情况下获取其内容才有效。 iFrame.load(function () { var content = iFrame.contents(); resolve(); }); - GitteTitter
如果我使用Ajax进行GET请求,则第二个选项适用于我。 但是,如果我进行POST,则浏览器不会将iFrame加载的cookie与请求一起发送。 有任何想法为什么? - NLV

2
在我目前工作的项目中,我们遇到了与SAML令牌在客户端过期并导致ajax调用出现问题的相同问题。在我们特定的情况下,我们需要在遇到第一个401后将所有请求排队,并且在成功验证后可以重新发送所有请求。身份验证使用Adam Mills建议的iframe解决方案,但还进一步处理,以防需要输入用户凭据,这是通过显示对话框来完成的,通知用户登录外部视图(因为ADFS不允许在iframe中显示登录页面,至少不是默认配置),在此期间等待请求正在等待完成,但用户需要从外部页面登录。如果用户选择取消,则等待请求也可以被拒绝,在这些情况下,每个请求都将调用jquery错误。以下是包含示例代码的gist链接:

https://gist.github.com/kavhad/bb0d8e4a446496a6c05a

我的代码基于使用jQuery处理所有ajax请求。如果您的ajax请求是由原生JavaScript、其他库或框架处理的,那么您可以在这个示例中找到一些灵感。使用jQuery UI 仅因为对话框而使用,只占了代码的一小部分,可以很容易地替换掉。
更新:抱歉,我更改了我的Github账户名称,这就是链接没有起作用的原因。现在应该可以正常工作了。

请问您能再次分享您的代码吗?GitHub的链接无法使用。另外,您能否提供更多细节?我和您有相同的情况,正在使用ADFS和SAML令牌,并为我的站点启用了跨域。当我向另一个站点发出ajax请求“get”时,它会显示iframe。对于我来说,我不应该使用iframe,我必须获取令牌并使用它。有没有不需要身份验证和iframe的解决方案来进行get请求?注意:用户已经登录。 - Imen
@kaveh-hadjari 你能再分享一遍代码链接吗? - Jay

2
如果您不想接收包含链接的HTML,则可以在Ajax调用上处理WSFederationAuthenticationModule上的AuthorizationFailed,并将RedirectToIdentityProvider设置为false
例如:
FederatedAuthentication.WSFederationAuthenticationModule.AuthorizationFailed += (sender, e) =>
{
    if (Context.Request.RequestContext.HttpContext.Request.IsAjaxRequest())
    {
        e.RedirectToIdentityProvider = false;
    }
};

使用Authorize属性会返回状态码401,如果你想要不同的结果,那么可以实现自己的Authorize属性并在Ajax请求中编写特殊代码。


处理JS中的403错误,并在安全区域内加载一个包含HTML页面的iframe,所有糟糕的ADFS重定向都将发生,然后您可以重试调用并获取您的cookies。 - Adam Mills
理论上,这应该在Global.asax.csApplication_Start()方法中正常工作 - 但实际上不会。然而,在Application_BeginRequest()中它可以正常工作。 - Jeremy McGee
1
请查看MSDN文章,了解在ASP.NET应用程序中设置AuthorizationFailed事件的推荐方法 https://msdn.microsoft.com/zh-cn/library/system.identitymodel.services.wsfederationauthenticationmodule.authorizationfailed(v=vs.110).aspx - Kaveh Hadjari

0

你只能使用这种数据类型

"xml": Treat the response as an XML document that can be processed via jQuery. 

"html": Treat the response as HTML (plain text); included script tags are evaluated. 

"script": Evaluates the response as JavaScript and evaluates it. 

"json": Evaluates the response as JSON and sends a JavaScript Object to the success callback. 

如果您在 Fiddler 中看到返回的只是 HTML,则将数据类型更改为 HTML,或者如果只是脚本代码,则可以使用脚本。

0
首先你说你试图通过 Ajax 调用另一个网站,那么你的调用是否符合 Web 浏览器的同源策略?如果是的话,那么你期望从服务器获得 HTML 作为响应,将 ajax 调用的datatype更改为dataType:"html",然后将表单插入到你的 DOM 中。

0

也许这个系列的前两篇文章可以帮到你。它们涉及ADFS和AJAX请求。

我认为我会尝试找出为什么身份验证cookie不能通过ajax传输,并找到一种方法将它们与我的请求一起发送。或者将ajax调用包装在一个函数中,通过检索HTML表单进行预身份验证,将其隐藏附加到DOM中,提交它(它将有望设置好的cookie),然后发送您最初想要发送的适当请求。


当握手操作发生时,cookie会被传输。问题在于,如果是第一次发送AJAX请求,则握手操作尝试发生。 - SaaS Developer

-1
你应该创建一个名为json.php的文件,然后将连接放到relayparty网站上,这样就可以工作了。 $.ajax({ url: "json.php", data: { foobar }, dataType: "json", type: "POST", async: false, cache: false, success: function (data) { // 在这里做一些事情 }, error: function (data, status) { alert(status); } });


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接