2013-03-27 117 views
7

我们将ADFS用于我们的内部应用程序 - 用户基本上都会在透明地登录到我们的某个应用程序中。但是,如果用户开了一个网页了一个多小时,然后尝试做网页上的东西(除了导航到另一个页面),他们得到一个错误:ADFS会话过期并导致错误

This page is accessing information that is not under its control. This poses a security risk. Do you want to continue?

好像在网页正试图将该请求重定向到ADFS服务器,并且浏览器正在阻止该请求。

我的问题是这样的:我如何捕获这种情况并让用户到ADFS服务器重新进行身份验证?

我还没有在Google上找到任何有关此问题的运气。

+0

如果它是相关的,点击“是”继续将用户带到行动 - 这不是作为一个GET存在,所以他们然后得到一个404. – zimdanen 2013-03-27 20:28:10

+0

你找到任何整洁的解决方案?在我们的例子中,我们正在考虑(如果我们可以检测到问题发生),在访问相关内部应用程序中新页面的页面上添加一个新的(隐藏的)iframe。将该新页面加载到'iframe'应该会触发ADFS服务器的完全被动流程,最终将在重新认证后加载新页面。此时,新页面将通过“postMessage”通知现有页面循环已完成,并且适当的cookie应该再次可用。 – 2013-08-29 12:30:14

+1

@Damien_The_Unbeliever:由于时间问题和其他优先事项,我们没有继续深入探讨。 iframe解决方案对我来说很不好,但是,如果它是解决问题的唯一方法,那就是它。让我知道它是如何解决你的。 – zimdanen 2013-08-29 13:02:33

回答

1

您可以在global.asax中手动检查并重新发出安全令牌,并使用它创建滑动会话。通过滑动会话,您可以选择推迟重新验证,直到它变为“安全”(这样做时(由于ADFS重定向而不再丢失数据)。

在SessionSecurityTokenReceived事件中,您可以评估令牌和请求。如果令牌已过期并且请求会经历重定向导致的数据丢失,则可以重新发布新的“临时”令牌。新的令牌应该有一个非常短的寿命,只要足够长的时间,以便您可以安全地完成当前的请求。令牌将过期并在下一次请求时再次评估。

protected void SessionAuthenticationModule_SessionSecurityTokenReceived(object sender, SessionSecurityTokenReceivedEventArgs e) 
{ 
    var now = DateTime.UtcNow; 
    SessionSecurityToken token = e.SessionToken; 
    var httpContext = new HttpContextWrapper(this.Context); 

    if (now > token.ValidTo 
     && (httpContext.Request.IsAjaxRequest() || httpContext.Request.HttpMethod == "POST")) 
    { 
     var sessionAuthModule = (SessionAuthenticationModule)sender; 
     e.SessionToken = sessionAuthModule.CreateSessionSecurityToken(token.ClaimsPrincipal, 
                    token.Context, 
                    now, 
                    now.AddMinutes(2), 
                    token.IsPersistent); 
     e.ReissueCookie = true; 
    } 
} 

ADFS会话将继续推迟重新认证,直到下一个GET请求。然后重定向最终会发生,并且用户将被发出正常寿命的正确标记。

+0

我在最初问题的相同情况,我正在尝试此解决方案。我必须安装哪个nuget包?此处理程序永远不会被解雇 – 2017-01-20 10:47:46

+0

这使用Microsoft令牌验证扩展包(System.IdentityModel.Tokens.ValidatingIssuerNameRegistry) – friggle 2017-01-20 19:49:19

3

更新:以下解决方案取决于iframe。 ADFS 3.0的X-Frame-Options默认为DENY,无法更改设置。因此,此解决方案仅适用于早期的ADFS 2.1 &。

在你的global.asax.cs中,你会想要捕获任何mid-AJAX 302s并将它们变成401 Unauthorized。这将阻止进行调用(并弹出该消息),并将发送给我们$(document).ajaxError()。

protected void Application_EndRequest() 
    { 
     var context = new HttpContextWrapper(this.Context); 
     if (context.Response.StatusCode == 302 && context.Request.IsAjaxRequest()) 
     { 
      context.Response.Clear(); 
      context.Response.StatusCode = 401; 
     } 
    } 

然后,在那里,拦截任何401s,然后继续进行其余的错误处理。我选择向用户显示一条消息。您可以在这里执行下一步,但为了便于阅读,我将ajaxSettings对象发送给另一个函数。返回true,以便它不会继续进行其他错误处理。

如果要重新检查这是ADFS,event.target.referrer将具有尝试重定向的URL。

$(document).ajaxError(function (event, jqXHR, ajaxSettings, thrownError) { 
    if (xhr.status == 401) { 
     alert("Your session has timed out. Click OK to reauthorize and extend your session."); 

     TriggerReauthenticationRefresher(ajaxSettings); 
     return true; 
    } 
…the rest of the error handling code…    
}); 

我在我的页面的空div只是针对这种情况,与“refresherBox”的ID,但你可以做到这一点DOM中的任何元素。把一个iframe放到你的域中的某个虚拟页面上。就我而言,ADFSRefresher.cshtml的内容都只是

<div><input type="hidden" value="@DateTime.Now.ToString()" /></div> 

而不是使用全局变量,我使用。数据()存储ajaxSettings。我们还需要跟踪iframe重新加载的次数,因此我们还要存储loadcount。将iframe插入到DOM中,它会启动。

function TriggerReauthenticationRefresher(ajaxSettings) { 
    var refreshframe = '<iframe src="@Url.Action("ADFSRefresher", "Debug")" style="display:none" onload="TrackFrameReloads()" />'; 

    $('#refresherBox').data('loadcount', 0); 
    $('#refresherBox').data('originalRequestSettings', ajaxSettings); 

    $('#refresherBox').html(refreshframe); 
} 

每当iframe加载完成时,TrackFrameReloads都会触发。由于我们知道即将发生的ADFS重定向,它将会启动两次。第一次是重定向,第二次是到它的src url。所以第一次启动时,我们只是增加loadcount。

第二次触发,我们知道我们已经成功重新认证。检索ajaxSettings,清除存储的数据,然后可以重新使用原始设置发送AJAX调用!它将通过,不重定向,并运行其原始成功&完整的功能。

function TrackFrameReloads() { 
    var i = $('#refresherBox').data('loadcount'); 
    if (i == 1) { 
     alert('Your session has been extended.'); 

     var ajaxSettings = $('#refresherBox').data('originalRequestSettings'); 

     $('#refresherBox').removeData(); 

     $.ajax(ajaxSettings); 

    } else { 
     $('#refresherBox').data("loadcount", 1); 
    } 
} 

请注意,如果您定义了它们,则错误和完整函数已经被触发。

如果您愿意,您可以跳过两条警报消息给用户。根据您的ADFS设置,这应该只需要1秒钟,并且用户不必被通知任何这发生!

+0

这有效,但我觉得在请求中早些时候知道该会话已过期,然后执行某些操作,而不是分解所有302响应会更好。 – BigOmega 2014-01-31 16:17:52

+0

ADFS将不允许我加载iFrame: 拒绝在框架中显示,因为它将'X-Frame-Options'设置为'DENY'。 任何方法? – severin 2015-02-05 13:29:11

+1

你知道它是哪个版本的ADFS吗?显然,ADFS 3将该设置默认为DENY,并且无法更改它。我会更新我的答案,注意此解决方案仅适用于ADFS 2及更低版本。我实际上已经不再使用发布的解决方案,而是开始使用滑动会话。这篇文章对于了解这一点非常有帮助:http://www.cloudidentity.com/blog/2010/06/16/warning-sliding-sessions-are-closer-than-they-appear/ – friggle 2015-02-05 20:06:16