我有一个网站试图调用另一个网站上的 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 请求的结果返回并且显然会因解析器错误而失败,因为 ajax 请求需要来自控制器操作的 json。这似乎是一种常见的情况,那么从 AJAX 与 AD FS 通信并处理这种重定向的正确方法是什么?
你有两个选择。 更多信息这里.
第一个是在入口应用程序(基于 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);
});
}
});
}
如果您不想接收带有链接的 HTML,您可以在
AuthorizationFailed
上处理 WSFederationAuthenticationModule
并仅在 Ajax 调用中将 RedirectToIdentityProvider
设置为 false
。
例如:
FederatedAuthentication.WSFederationAuthenticationModule.AuthorizationFailed += (sender, e) =>
{
if (Context.Request.RequestContext.HttpContext.Request.IsAjaxRequest())
{
e.RedirectToIdentityProvider = false;
}
};
带有
Authorize
属性的这个将返回状态代码 401
如果你想要不同的东西,那么你可以实现自己的 Authorize
属性并在 Ajax 请求上编写特殊代码。
在我目前使用的项目中,我们遇到了与客户端 SAML 令牌过期相同的问题,并导致 ajax 调用出现问题。在我们的特殊情况下,我们需要在遇到第一个 401 之后将所有请求排入队列,并且在成功验证后可以重新发送所有请求。身份验证使用 Adam Mills 建议的 iframe 解决方案,但在需要输入用户凭据的情况下也更进一步,这是通过显示一个对话框通知用户在外部视图上登录来完成的(因为 ADFS 不允许显示登录信息iframe 中的页面至少不是默认配置)在此期间等待请求正在等待完成,但用户需要从外部页面登录。如果用户选择取消,也可以拒绝等待的请求,在这种情况下,将为每个请求调用 jquery 错误。
这是一个带有示例代码的要点链接:
https://gist.github.com/kavhad/bb0d8e4a446496a6c05a
注意我的代码基于使用 jquery 来处理所有 ajax 请求。如果您的 ajax 请求由 vanilla javascript、其他库或框架处理,那么您或许可以在此示例中找到一些灵感。 jquery ui 的使用只是因为对话框,代表一小部分代码,可以很容易地换掉。
更新 抱歉,我更改了我的 github 帐户名,这就是链接不起作用的原因。它现在应该可以工作了。
首先,您说您正在尝试对 another 网站进行 ajax 调用,您的调用是否符合 Web 浏览器的 same origin policy?如果是这样,那么您期望 html 作为服务器的响应,将 ajax 调用的
datatype
更改为dataType: "html"
,然后将表单插入到您的 DOM 中。
也许this serie的前两篇文章会对你有所帮助。他们考虑 ADFS 和 AJAX 请求
我想我会尝试做的是查看为什么不通过 ajax 传输身份验证 cookie,并找到一种方法将它们与我的请求一起发送。或者将 ajax 调用包装在一个函数中,该函数通过检索 html 表单进行预验证,将其隐藏到 DOM,提交它(它有望设置好的 cookie),然后发送您最初想要发送的适当请求
只能做这种数据类型
"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.
如果您在提琴手中看到仅返回 html,则将您的数据类型更改为 html,或者如果只有脚本代码,则您可以使用脚本。
您应该创建一个任意名称的文件,如 json.php,然后将连接连接到 relayparty 网站,这应该可以工作
$.ajax({
url: "json.php",
data: { foobar },
dataType: "json",
type: "POST",
async: false,
cache: false,
success: function (data) {
// do something here
},
error: function (data, status) {
alert(status);
}
});