ADFS session expires and causes error

We use ADFS for our internal applications - users are basically logged in transparently anytime they go to one of our apps. However, if a user leaves a page open for over an hour then tries to do something on that page (other than navigate to another page), they get an error:

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

It seems like the page is trying to redirect that request to the ADFS server and that is being prevented by the browser.

My question is thus: How do I catch this situation and get the user to the ADFS server to reauthenticate?

I haven't had any luck finding anything on Google regarding this.


You can inspect and re-issue security tokens manually in global.asax, and use this to create sliding sessions. With sliding sessions, you can choose to postpone the re-authentication until it becomes "safe" to do so (when data will no longer be lost due to the ADFS redirect).

Inside the SessionSecurityTokenReceived event, you can evaluate the token and the request. If the token has expired and the request is one that will experience data loss from a redirect, you can re-issue a new "temporary" token. The new token should have a very short life, just long enough so you can safely complete the current request. The token will then expire and be evaluated again on the next request.

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;
   }
}

The ADFS session will continue to postpone reauthentication until the next GET request. Then the redirect will finally occur, and the user will be issued a proper token of normal lifespan.


Update: The below solution depends on iframes. ADFS 3.0 has X-Frame-Options defaulted to DENY, with no option to change the setting. So this solution will only work on ADFS 2.1 & earlier.

In your global.asax.cs, you're going to want to catch any mid-AJAX 302s and turn them into a 401 Unauthorized. This will prevent the call from proceeding (and popping that message), and will send us to $(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;
        }
    }

Then, in there, intercept any 401s before they proceed to the rest of your error handling. I chose to show a message to the users. You can do the next step right here, but for readability, I'm sending the ajaxSettings object to another function. Return true so it won't proceed into the rest of your error handling.

If you want to doublecheck that this is ADFS, event.target.referrer will have the URL of the attempted redirect.

$(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…            
});

I have an empty div in my page just for this situation, with an id of 'refresherBox', but you can do this on any element in your DOM. Put together an iframe that goes to some dummy page in your domain. In my case, the contents of ADFSRefresher.cshtml are just

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

Instead of using global variables, I'm storing the ajaxSettings using .data(). We also need to keep track of how many times the iframe reloads, so we're also storing loadcount. Insert the iframe into the DOM, and it will kick off.

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);
}

TrackFrameReloads will fire every time the iframe finishes loading. Since we know there is an impending ADFS redirect, it will fire twice. The first time will be the redirect, and the second time will be to its src url. So the first time it fires, we just increment loadcount.

The second time it fires, we know we have been successfully reauthenticated. Retrieve the ajaxSettings, clear the stored data, and you can then re-use your original settings to send the AJAX call! It will go through, un-redirected, and run its original success & complete functions.

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);
    }
}

Be aware that if you defined them, the error and complete functions will have already fired.

You can skip the two alert messages to the users if you like. Depending on your ADFS setup, this should only take 1 second, and the user doesn't have to be informed that any of this happened at all!

链接地址: http://www.djcxy.com/p/70260.html

上一篇: 如何在WPF中懒洋洋地创建UI元素?

下一篇: ADFS会话过期并导致错误