Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SSRS: Why do SKA-cookies build up until "HTTP 400 Bad Request - Request Too Long" occurs?

I have switched SQL-Server Reporting Services 2012 (SSRS 2012) to forms authentication so we can use it over internet.

I could not find a forms-authentication sample for SSRS 2012 anywhere, so I had to take the SSRS 2008R2 one, and adapt it for 2012, for Single-Sign-On (SSO).

At that point everything seemed to be working as expected; I even managed to get SSO working across domains.

But now I have a problem:

I was testing all reports (more than 200) with Google Chrome, because I had to insert a little JavaScript that alters td border-size for that the HTML displays right in non-IE5-QuirksMode. After about the 50th report, I suddenly got:

"HTTP 400 Bad Request - Request Too Long"

After that, I could not view any other report, not even those that did work previously.

The problem seems to be caused by too many cookies, and indeed, when I deleted a few "*_SKA" (Session Keep Alive?) cookies, it began working again.

SSRS Sucks

My problem now is that I don't know what causes this "cookie overflow". I also don't know, if this a bug in Chrome, a bug in vanilla SSRS or a bug caused by the new forms authentication.

All I do in the new forms-authentication that has something to do with cookies is this:

using System;
using System.Collections.Generic;
using System.Text;


namespace FormsAuthentication_RS2012
{


    internal class FormsAuthenticationWorkaround
    {

        public static void RedirectFromLoginPage(string strUser, bool createPersistentCookie)
        {
            //string url = System.Web.Security.FormsAuthentication.GetRedirectUrl(strUser, true);
            string url = GetRedirectUrlWithoutFailingOnColon(strUser, createPersistentCookie);
            SQL.Log("User: '" + strUser + "' ReturnUrl", url);

            if (System.Web.HttpContext.Current != null && System.Web.HttpContext.Current.Response != null)
                System.Web.HttpContext.Current.Response.Redirect(url);
        }


        // https://github.com/mono/mono/blob/master/mcs/class/System.Web/System.Web.Security/FormsAuthentication.cs
        // @MSFT: WTF are u guys smoking ?
        public static string GetRedirectUrlWithoutFailingOnColon(string userName, bool createPersistentCookie)
        {
            if (userName == null)
                return null;

            System.Web.Security.FormsAuthentication.SetAuthCookie(userName, true, "/");

            string returnUrl = null;

            if (System.Web.HttpContext.Current != null && System.Web.HttpContext.Current.Request != null)
                returnUrl = System.Web.HttpContext.Current.Request.QueryString["ReturnUrl"];

            if (returnUrl != null)
                return returnUrl;

            returnUrl = System.Web.Security.FormsAuthentication.DefaultUrl;
            return returnUrl;
        }


    }


}

And as this code creates the "sqlAuthCookie" that one sees at the bottom. There is only one "sqlAuthCookie" so I don't think this can possibly be a forms-authentication bug.

The problem seem to be the SKA cookies, that AFAIK have nothing to do with forms-authentication and everything to do with Vanilla SSRS.

The only other thing that I could see as a reason for this is the change in forms-authentication-cookie timeout to 720 minutes that I entered in the forms-authentication section in the web.config file.

  <authentication mode="Forms">
    <forms loginUrl="logon.aspx" name="sqlAuthCookie" timeout="720" path="/">
    </forms>
  </authentication>

Does anybody know what I can do to prevent getting flooded by Session Keep-Alive cookies (except for deleting those cookies manually)?

It's no problem for me per se, apart from it being highly annoying, but it's going to be a problem because the users probably won't be very understanding of that...

like image 349
Stefan Steiger Avatar asked Aug 28 '13 14:08

Stefan Steiger


3 Answers

Issue listed as fixed in SQL Server 2012 SP1 CU7. (see comments from Microsoft in the connect issue)
But still present in SQL-Server 2014.


The later section applies, if you can't install SQL Server 2012 SP1 CU7:

OK, got the answer myself.

The keep-alive cookie is issued every time one opens a report.
Now, that becomes a problem when one opens (or refreshs, or changes to another page), say, more than 110 - 120 reports, without closing the browser.

So we safeguard by deleting the excess cookies, and set a safe boundary at appx. 1/2 of the assumed maximum of 120 cookies.

The cookies are HttpOnly, and expire when one closes the browser (session cookies).
They are non-secure HttpOnly cookies, which is why I failed in my attempt to delete them via JavaScript.
So it becomes necessary to delete them on the server side. Since we can't modify ReportServer, we have to use inline-scripting.

<body style="margin: 0px; overflow: auto">


<script type="text/C#" runat="server">
protected string ClearSessionKeepAliveCookiesToPreventHttp400HeaderTooLong()
{
    if(Request == null || Request.Cookies == null)
        return "";

    if(Request.Cookies.Count < 60)
        return "";

    // System.Web.HttpContext.Current.Response.Write("<h1>"+Request.Cookies.Count.ToString()+"</h1>");
    for(int i = 0; i < Request.Cookies.Count; ++i)
    {
        if(StringComparer.OrdinalIgnoreCase.Equals(Request.Cookies[i].Name, System.Web.Security.FormsAuthentication.FormsCookieName))
            continue;

        if(!Request.Cookies[i].Name.EndsWith("_SKA", System.StringComparison.OrdinalIgnoreCase))
            continue;

        if(i > 60)
            break;

        //System.Web.HttpContext.Current.Response.Write("<h1>"+Request.Cookies[i].Name+"</h1>");

        System.Web.HttpCookie c = new System.Web.HttpCookie( Request.Cookies[i].Name );
        //c.Expires = System.DateTime.Now.AddDays( -1 );
        c.Expires = new System.DateTime(1970, 1 ,1);
        c.Path = Request.ApplicationPath + "/Pages";
        c.Secure = false;
        c.HttpOnly = true;

        // http://stackoverflow.com/questions/5517273/httpcookiecollection-add-vs-httpcookiecollection-set-does-the-request-cookies
        //Response.Cookies[Request.Cookies[i].Name] = c;
        //Response.Cookies.Add(c);
        Response.Cookies.Set(c);
    }

    return "";
}


</script>

<%=ClearSessionKeepAliveCookiesToPreventHttp400HeaderTooLong()%>

    <form style="width:100%;height:100%" runat="server" ID="ReportViewerForm">
like image 87
Stefan Steiger Avatar answered Nov 15 '22 05:11

Stefan Steiger


You can set KeepSessionAlive to false on the ReportViewer Control http://msdn.microsoft.com/en-us/library/microsoft.reporting.webforms.reportviewer.keepsessionalive(v=vs.100).aspx

like image 27
graham mendick Avatar answered Nov 15 '22 04:11

graham mendick


I was having a lot of difficulty implementing different solutions to this problem because of our site's architecture - for whatever reason, my coworkers had originally decided to use iframes with links to the reports instead of a ReportViewer control, and I was loathe to try and change this so late in the development process due to a simple cookie issue.

Solutions I tried which did not work:

  1. Implementing Stefan's code-behind fix - The server code on my page could not access cookies being set in the embedded iframe document
  2. Changing cookies from the parent document in javascript - For understandable security reasons, I could not access cookies in the iframe from client-side code either
  3. Tried passing parameters into the report URL to tell it not to keep the session alive - Tried appending "&rs:KeepSessionAlive=False", which did not cause an error, but did not work
  4. *Toyed* with the idea of injecting javascript into the reports themselves - Considering this would involve changing some 50-odd reports and screwing up the exported/saved reports feature, this was not an option

Finally, after poking around the server, I realized that the Report Server "Pages" folder (C:\Program Files\Microsoft SQL Server\MSRS11.SQLEXPRESS\Reporting Services\ReportServer\Pages) contained a "ReportViewer.aspx" document.

And what do you know? It's just a simple ASP.NET page with a header where you can add your own javascript!?

So, here's what DID work for me:

I just added the client-side cookie-setting code I had found elsewhere below to delete all cookies on the ReportViewer page, and everything suddenly worked! Only one keep-alive cookie at a time!

<%@ Register TagPrefix="RS" Namespace="Microsoft.ReportingServices.WebServer" Assembly="ReportingServicesWebServer" %>
<%@ Page Language="C#" AutoEventWireup="true" Inherits="Microsoft.ReportingServices.WebServer.ReportViewerPage" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
 <head id="headID" runat="server">
  <title><%= GetPageTitle() %></title>
 </head>
 <body style="margin: 0px; overflow: auto">
  <form style="width:100%;height:100%" runat="server" ID="ReportViewerForm">
   <asp:ScriptManager ID="AjaxScriptManager" AsyncPostBackTimeout="0" runat="server" />
   <RS:ReportViewerHost ID="ReportViewerControl" runat="server" />
  </form>
  <script language="javascript" type="text/javascript">
      // Beginning of inserted cookies management code
function createCookie(name, value, days) {
    if (days) {
        var date = new Date();
        date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
	var expires = "; expires=" + date.toUTCString();
    }
    else var expires = "";

    document.cookie = name + "=" + value + expires;
}

function readCookie(name) {
    var nameEQ = name + "=";
    var ca = document.cookie.split(';');
    for (var i = 0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) == ' ') c = c.substring(1, c.length);
        if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
    }
    return null;
}

function eraseCookie(name) {
    createCookie(name, "", -1);
}

var getCookies = function () {
    var pairs = document.cookie.split(";");
    var cookies = {};
    for (var i = 0; i < pairs.length; i++) {
        var pair = pairs[i].split("=");
        cookies[pair[0]] = unescape(pair[1]);
    }
    return cookies;
}

var pairs = document.cookie.split(";");
var cookies = {};
for (var i = 0; i < pairs.length; i++) {
    var pair = pairs[i].split("=");
    cookies[pair[0]] = unescape(pair[1]);
}
var keys = [];
for (var key in cookies) {
    if (cookies.hasOwnProperty(key)) {
        keys.push(key);
    }
}
for (index = 0; index < keys.length; ++index) {
    eraseCookie(keys[index]);
}

      // End of inserted cookies management logic

      //Beginning of pre-existing code
Sys.WebForms.PageRequestManager.prototype._destroyTree = function(element) {
    var allnodes = element.getElementsByTagName('*'),
        length = allnodes.length;
    var nodes = new Array(length);
    for (var k = 0; k < length; k++) {
        nodes[k] = allnodes[k];
    }
    for (var j = 0, l = nodes.length; j < l; j++) {
        var node = nodes[j];
        if (node.nodeType === 1) {
            if (node.dispose && typeof (node.dispose) === "function") {
                node.dispose();
            }
            else if (node.control && typeof (node.control.dispose) === "function") {
                node.control.dispose();
            }
            var behaviors = node._behaviors;
            if (behaviors) {
                behaviors = Array.apply(null, behaviors);
                for (var k = behaviors.length - 1; k >= 0; k--) {
                    behaviors[k].dispose();
                }
            }
        }
    }
}
  </script>
 </body>
</html>

Please note that there was some pre-existing code in the page which I did not replace.

Hope this helps somebody else, as I struggled with this one for a while!

NOTE: Please note that, in my case, the Session Keep Alive (SKA) cookies were not HTTP-only, so I was able to access them from the client-side, albeit only the client-side within the Report Server itself. enter image description here

like image 4
DicreetAndDiscrete Avatar answered Nov 15 '22 06:11

DicreetAndDiscrete