AD FS 2.0 身份验证和 AJAX

Posted

技术标签:

【中文标题】AD FS 2.0 身份验证和 AJAX【英文标题】:AD FS 2.0 Authentication and AJAX 【发布时间】:2011-12-06 11:15:40 【问题描述】:

我有一个网站试图在另一个网站上调用 MVC 控制器操作。这些站点都设置为 AD FS 2.0 中的信赖方信任。在两个站点之间的浏览器窗口中打开页面时,一切都经过身份验证并且工作正常。但是,当尝试使用 jQuery AJAX 方法从 javascript 调用控制器操作时,它总是会失败。这是我正在尝试做的代码 sn-p...

$.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 通信并处理此重定向的正确方法是什么?

【问题讨论】:

如果 ajax 调用正在返回 HTML,显然您不想使用 json 解析器对其进行解析。将 dataType 更改为“html”,并发布一个返回的 html 示例,这样我就可以向您展示如何编写一个将提交返回的表单的处理程序。 问题是我想取回 JSON。 AD FS 使用它要发布的新 HTML 表单进行重定向,以执行所需的握手。这在浏览器窗口中可以正常工作,但在这里不行。一旦握手发生,AJAX 请求就不会重定向,我会返回 JSON。我现在想出了一个解决方法来处理 IFRAME 中的 html 页面帖子,但这并不理想。 我知道您想取回 JSON,但您不会取回 JSON。 但是,如果您希望能够将返回的数据结构视为 JSON,请发布返回的 HTML 示例,我将向您展示如何编写一个处理程序,该处理程序将不使用使用 IFRAME 提交返回的表单。 为什么在我的情况下它给了我错误“没有访问控制允许访问允许来源”?这是一个 CORS 问题,这意味着我的浏览器阻止了重定向到我的 ADFS 服务器,为什么您的请求可以重定向到 ADFS 服务器? 【参考方案1】:

你有两个选择。 更多信息here。

第一个是在入口应用程序(基于 HTML 的应用程序)和您的 API 解决方案之间共享会话 cookie。您将两个应用程序配置为使用相同的 WIF cookie。这仅在两个应用程序位于同一根域上时才有效。 请参阅上面的帖子或*** question。

另一个选项是禁用 AJAX 请求的被动重定向(如Gutek's answer)。这将返回一个 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(); ); 如果我使用 Ajax 执行 GET,第二个选项对我有用。但是,如果我执行 POST,则浏览器不会将 iFrame 加载的 cookie 与请求一起发送。知道为什么吗?【参考方案2】:

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

这是一个带有示例代码的要点的链接:

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

请注意,我的代码基于使用 jquery 处理所有 ajax 请求。如果您的 ajax 请求正在由 vanilla javascript、其他库或框架处理,那么您也许可以在这个示例中找到一些灵感。使用 jquery ui 只是因为对话框,它代表一小部分代码,可以很容易地换出。

【讨论】:

你能再分享一下你的代码吗,GitHub的链接失效了,你能不能详细点,我和你的情况一样,正在使用ADFS和SAML Token,我启用了我的网站的跨域。当我向另一个站点执行 ajax 请求“获取”时,它会显示 iframe,对我来说,我不应该执行 iframe,我必须获取令牌并使用,任何解决方案都可以在没有身份验证和 iframe 的情况下进行获取,注意:用户已经登录 @kaveh-hadjari 能否再次分享代码链接。【参考方案3】:

如果您不想接收带有链接的 HTML,您可以在 WSFederationAuthenticationModule 上处理 AuthorizationFailed,并仅在 Ajax 调用上将 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 重定向都会发生,你会得到你的 cookie...然后你可以重试调用 这应该(理论上)在Application_Start()Global.asax.cs 方法中可以正常工作——但不会。但是,它在Application_BeginRequest() 中工作得很好。 查看 MSDN 文章关于在 ASP.NET 应用程序中设置 AuthorizationFailed 事件的推荐方法msdn.microsoft.com/en-us/library/…【参考方案4】:

您应该创建一个任何名称的文件,例如 json.php,然后连接到中继方网站,这应该可以 $.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); );

【讨论】:

【参考方案5】:

你只能做这种数据类型

"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,或者如果只有脚本代码,那么您可以使用脚本。

【讨论】:

【参考方案6】:

也许this serie 的前 2 篇帖子会对你有所帮助。他们考虑 ADFS 和 AJAX 请求

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

【讨论】:

Cookie 确实会在握手发生时被传输。这个问题是第一次尝试在 AJAX 请求上发生握手。【参考方案7】:

首先你说你正在尝试对另一个网站进行ajax调用,你的调用符合same origin policy的网络浏览器吗?如果是这样,那么您期望 html 作为服务器的响应,请将 ajax 调用的datatype 更改为dataType: "html",然后将表单插入到您的 DOM 中。

【讨论】:

以上是关于AD FS 2.0 身份验证和 AJAX的主要内容,如果未能解决你的问题,请参考以下文章

使用 Azure AD 承载令牌时身份验证失败,返回容器列表 [Azure Blob] [Azure AD OAuth 2.0] [REST API]

如何在 ASP.NET Core 3 上同时使用 Azure AD 身份验证和身份?

使用 kerberos 和 AD 的 Oracle 数据库身份验证

是否可以在单个应用程序中拥有 SAML 2.0 身份验证和 JWT 身份验证?

WCF 和 AD LDS 以及基本身份验证

如何使用身份服务器 3 和 microsoft 团队应用程序使用 Azure AD 令牌进行身份验证