Asp.net session never expires when using SignalR a

2020-06-30 09:17发布

问题:

We have a web application that uses SignalR for its notification mechanism.The problem is when we are browsing our web application using IE ,SignalR uses Long Polling as its transport type thus sends back requests to our web server therefore Session never expires no matter how long the browser is idle.

We were thinking that maybe we could catch the requests in Global.asax and see if they were from SingalR and set the session timeout to the remaining time (Which I don't think it's a straightforward solution).

Is there any other solution the we are missing ?

回答1:

The workaround I am currently using is an IHttpModule to check if the request is a Signalr request, if so remove the authentication cookie, this will prevent the ASP.net session timeout from being reset, so if your Session Timeout is 20min and the only requests are Signalr the users session will still timeout and the user will have to login again.

    public class SignalRCookieBypassModule : IHttpModule
    {
        public void Init(HttpApplication application)
        {
            application.PreSendRequestHeaders += OnPreSendRequestHeaders;
        }

        private bool IsSignalrRequest(string path)
        {
            return path.IndexOf("/signalr/", StringComparison.OrdinalIgnoreCase) > -1;
        }

        protected void OnPreSendRequestHeaders(object sender, EventArgs e)
        {
            var httpContext = ((HttpApplication)sender).Context;
            if (IsSignalrRequest(httpContext.Request.Path))
            {
                // Remove auth cooke to avoid sliding expiration renew
                httpContext.Response.Cookies.Remove(DefaultAuthenticationTypes.ApplicationCookie);
            }
        }

        public void Dispose()
        {
        }
    }

I feel this is a real hack solution so would love so other ideas to prevent session timeout renew when data is pushed to the client from the server, or a when javascript client polls an endpoint for data.



回答2:

If you take a look at the description of the SignalR protocol I wrote a while ago you will find this:

» ping – pings the server ... Remarks: The ping request is not really a “connection management request”. The sole purpose of this request is to keep the ASP.NET session alive. It is only sent by the the JavaScript client.

So, I guess the ping request is doing its job.



回答3:

I here post @Simon Mourier's commented solution, with his approval, as a CW answer, as I find the suggested approach the most appropriate and less intrusive, as it just disables the Session for SignalR requests.

A positive side effect is that the request will be processed faster as the Session object doesn't need to be initiated and loaded.

It still uses a IHttpModule for the work, and the preferable place is likely the AcquireRequestState event (not personally tested yet though), or at an event raised earlier, before making use of the Session object.

Do note using this approach that one might need to test that the Session object is available before access any of its members or stored objects.

public class SignalRSessionBypassModule : IHttpModule
{
    public void Init(HttpApplication application)
    {
        application.AcquireRequestState += OnAcquireRequestState;
    }

    private bool IsSignalrRequest(string path)
    {
        return path.IndexOf("/signalr/", StringComparison.OrdinalIgnoreCase) > -1;
    }

    protected void AcquireRequestState(object sender, EventArgs e)
    {
        var httpContext = ((HttpApplication)sender).Context;
        if (IsSignalrRequest(httpContext.Request.Path))
        {
            // Run request with Session disabled
            httpContext.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Disabled);
        }
    }

    public void Dispose()
    {
    }
}

Here is another completely different approach, simple, yet quite efficient.

Instead of relying on Session/Auth cookies to decide whether a user has timed out, use the Cache object. This have more or less no side effects and work just like if the user simply logged out.

By simply add this small snippet somewhere in the beginning of your web app code, where of course SignalR don't go, you will be able to check if the cache item is there and reinitiate it (with the same expiration time as the Session timeout is set), and if not, just do a logout and remove cookies/session variables.

if (Request.IsAuthenticated) {

  if (Cache[Context.User.Identity.Name] == null) {

    // Call you logout method here...,
    // or just:
    // - Sign out from auth;
    // - Delete auth cookie
    // - Remove all session vars

  } else {

    // Reinitiate the cache item
    Cache.Insert(Context.User.Identity.Name,
                 "a to you usable value",
                 null,
                 DateTime.Now.AddMinutes(Session.Timeout),
                 Cache.NoSlidingExpiration,
                 CacheItemPriority.Default,
                 null
                );
}

And within your user login method, you just add this, to create the cache item for the first time

// Insert the cache item
Cache.Insert(Context.User.Identity.Name,
             "a to you usable value",
             null,
             DateTime.Now.AddMinutes(Session.Timeout),
             Cache.NoSlidingExpiration,
             CacheItemPriority.Default,
             null
            );


回答4:

It's more stable and maintainable -in my view- to have your own "session like timeout" . Set your .NET session timeout to infinity since you'll not be using it and then create a global JavaScript counter (in your layout or master page) to track the time passing while the browser is idle (obviously setTimeout or setInterval every few seconds would do the trick). Make sure to have the counter reset on every web request (that should happen automatically since all JavaScript variables would reset). In case you have pages that depend on web services or Web API, make sure to reset your global JavaScript counter on every call. If the counter reaches your desired timeout without being reset, that means that the session is expired and you can logout the user. With this approach you'll have full control over the session lifetime which enables you to create a logout timer popup to warn the user that the session is about to expire. SignalR would perfectly fit with this approach since the JavaScript timer would keep ticking.