我们将 ADFS 用于我们的内部应用程序 - 用户基本上可以在每次访问我们的应用程序之一时透明地登录。但是,如果用户将页面打开超过一个小时然后尝试在该页面上执行某些操作(而不是导航到另一个页面),他们会收到错误消息:
此页面正在访问不受其控制的信息。这会带来安全风险。你要继续吗?
该页面似乎正在尝试将该请求重定向到 ADFS 服务器,而浏览器正在阻止该请求。
因此,我的问题是:如何捕捉这种情况并让用户到 ADFS 服务器重新进行身份验证?
我没有任何运气在谷歌上找到任何关于此的内容。
更新:以下解决方案取决于 iframe。ADFS 3.0 的 X-Frame-Options 默认为 DENY,没有更改设置的选项。因此,此解决方案仅适用于 ADFS 2.1 及更早版本。
在您的 global.asax.cs 中,您将想要捕获任何中间 AJAX 302 并将它们变成 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;
}
}
然后,在那里,拦截任何 401,然后再进行其余的错误处理。我选择向用户显示一条消息。您可以在此处执行下一步,但为了便于阅读,我将 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,其 id 为“refresherBox”,但您可以在 DOM 中的任何元素上执行此操作。将一个 iframe 放在一起,该 iframe 会转到您域中的某个虚拟页面。就我而言, ADFSRefresher.cshtml 的内容只是
<div><input type="hidden" value="@DateTime.Now.ToString()" /></div>
我没有使用全局变量,而是使用 .data() 存储 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);
}
}
请注意,如果您定义了它们,则 error 和 complete 函数将已经触发。
如果愿意,您可以跳过向用户发送的两条警报消息。根据您的 ADFS 设置,这应该只需要 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 请求。然后重定向最终将发生,并且将向用户发出正常寿命的适当令牌。