【问题标题】:SSRS: Why do SKA-cookies build up until "HTTP 400 Bad Request - Request Too Long" occurs?SSRS:为什么在“HTTP 400 Bad Request - Request Too Long”出现之前,SKA-cookies 会一直累积?
【发布时间】:2013-08-31 17:20:45
【问题描述】:

我已将 SQL-Server Reporting Services 2012 (SSRS 2012) 切换为表单身份验证,以便我们可以通过 Internet 使用它。

我无法在任何地方找到适用于 SSRS 2012 的表单身份验证示例,因此我不得不采用 SSRS 2008R2,并将其调整为 2012 年的单点登录 (SSO)。

那时一切似乎都按预期进行;我什至设法让 SSO 跨域工作。

但是现在我有一个问题:

我正在使用 Google Chrome 测试所有报告(超过 200 个),因为我必须插入一些 JavaScript 来更改 td 边框大小,以便 HTML 在非 IE5-QuirksMode 下正确显示。大约在第 50 次报告之后,我突然得到:

“HTTP 400 错误请求 - 请求太长”

在那之后,我无法查看任何其他报告,甚至那些之前确实有效的报告。

问题似乎是由于 cookie 过多造成的,实际上,当我删除了一些“*_SKA”(Session Keep Alive?)cookie 后,它又开始工作了。

我现在的问题是我不知道是什么导致了这个“cookie 溢出”。 我也不知道这是 Chrome 中的 bug,vanilla SSRS 中的 bug 还是新表单身份验证引起的 bug。

我在新表单中所做的所有与 cookie 有关的身份验证是这样的:

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


    }


}

因为这段代码创建了在底部看到的“sqlAuthCookie”。只有一个“sqlAuthCookie”,所以我认为这不可能是表单身份验证错误。

问题似乎在于 SKA cookie,AFAIK 与表单身份验证无关,而与 Vanilla SSRS 无关。

我能看到的唯一其他原因是我在 web.config 文件的 forms-authentication 部分中输入的 forms-authentication-cookie 超时更改为 720 分钟。

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

有人知道我可以做些什么来防止被 Session Keep-Alive cookie 淹没(手动删除这些 cookie 除外)吗?

这对我本身来说没有问题,除了它非常烦人之外,但这将是一个问题,因为用户可能不会很理解这一点......

【问题讨论】:

    标签: asp.net cookies reporting-services forms-authentication ssrs-2012


    【解决方案1】:

    在 SQL Server 2012 SP1 CU7 中列为已修复的问题(请参阅 connect issue 中来自 Microsoft 的 cmets)
    但仍然存在于 SQL-Server 2014 中。


    如果您无法安装 SQL Server 2012 SP1 CU7,则后面的部分适用:

    好的,我自己得到答案。

    每次打开报告时都会发出保持活动 cookie。
    现在,当打开(或刷新或更改到另一个页面)(例如,超过 110 到 120 个报告)而不关闭浏览器时,这会成为一个问题。

    所以我们通过删除多余的 cookie 来保护,并在 appx 设置安全边界。假设最多 120 个 cookie 的 1/2。

    cookies 是 HttpOnly,并在关闭浏览器时过期(会话 cookie)。
    它们是不安全的 HttpOnly cookie,这就是我尝试通过 JavaScript 删除它们失败的原因。
    因此有必要在服务器端删除它们。 由于我们不能修改 ReportServer,我们必须使用 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">
    

    【讨论】:

    • 关闭浏览器对我来说不适用于 chrome。我不得不手动删除 cookie。我会尽快尝试您的服务器端解决方案。谢谢!
    • 这对我不起作用,直到我将 c.Path = Request.ApplicationPath + "/Pages"; 更改为 c.Path = Request.Cookies[i].Path;
    • @masty:有趣,Request.Cookies[i].Path 对我不起作用。 c.Path = Request.Cookies[i].Path + "/Pages";您是否安装了 ServicePack 1 + 最新的累积更新?
    • 我已经验证,该问题在 SQL Server 2012 SP1 CU7 中确实得到了解决。
    • @Paul Karlin:是的,它也存在于 SQL-Server 2014 中,但并未修复。第一个服务包已经发布,但我们的管理员还没有安装它——所以我不能说服务包是否解决了这个问题。
    【解决方案2】:

    【讨论】:

    • 不错的发现。我会试试的。
    • 仅供参考,如果您使用的是“标准”ReportViewer,可以在 Reporting Services\ReportServer\Pages\ReportViewer.aspx 中更改此设置,方法是将 KeepSessionAlive="false" 添加到 RS:ReportViewerHost 标记。跨度>
    • @graham mendick:另一方面,如果您这样做,如果您在 5 到 10 分钟不活动后单击导出,则会收到会话过期错误消息。
    【解决方案3】:

    由于我们网站的架构,我在为这个问题实施不同的解决方案时遇到了很多困难 - 无论出于何种原因,我的同事最初决定使用带有指向报告的链接的 iframe,而不是 ReportViewer 控件,我很讨厌由于一个简单的 cookie 问题,请在开发过程的后期尝试更改它。

    我尝试过的不起作用的解决方案:

    1. 实施 Stefan 的代码隐藏修复 - 我页面上的服务器代码无法访问嵌入 iframe 文档中设置的 cookie
    2. 在 javascript 中从父文档更改 cookie - 出于可以理解的安全原因,我也无法从客户端代码访问 iframe 中的 cookie
    3. 尝试将参数传递到报告 URL 以告诉它不要让会话保持活动状态 - 尝试附加“&rs:KeepSessionAlive=False”,这没有导致错误,但没有奏效
    4. *玩弄*注入javascript into the reports themselves - 考虑到这将涉及更改一些 50 多个报告并搞砸导出/保存的报告功能,这不是一个选项

    最后,在浏览了服务器之后,我意识到Report Server“Pages”文件夹(C:\Program Files\Microsoft SQL Server\MSRS11.SQLEXPRESS\Reporting Services\ReportServer\Pages)包含 “ReportViewer.aspx” 文档。

    你知道什么? 这只是一个带有标题的简单 ASP.NET 页面,您可以在其中添加自己的 javascript!?

    所以,以下是 对我有用的东西:

    我刚刚在下面添加了client-side cookie-setting code I had found elsewhere 以删除ReportViewer 页面上的所有cookie,然后一切都突然起作用了!一次只能使用一个保活 cookie!

    <%@ 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>

    请注意,页面中有一些我没有替换的预先存在的代码。

    希望这对其他人有所帮助,因为我在这个问题上挣扎了一段时间!

    注意: 请注意,在我的情况下,会话保持活动 (SKA) cookie 不是仅 HTTP,因此我能够从客户端访问它们,尽管只能从报表服务器本身的客户端访问。

    【讨论】:

    • 您可能使用较新的 SQL 服务器版本。我不得不使用仅 HTTP cookie,这是一种您无法使用 JavaScript 设置的 cookie。我实际上首先尝试了这个,但它失败了。他们可能已经改变了他们的实现。我的代码适用于 SQL-Server 2012 ReportingServices,版本 11.0.5343.0。
    • 是的,我只采用了这个解决方案,因为 SKA cookie 似乎不是仅限 HTTP 的。我有一个自定义身份验证 cookie,我从托管站点传递到 SSRS,它是仅限 HTTP 的,所以我很惊讶地看到保持活动状态不同。我也在使用 SSRS 2012,版本 11.0.2100.60。不确定差异是由于版本还是配置不同。
    • 您对哪个页面进行了这些更改?例如,位于 C:\Program Files\Microsoft SQL Server\MSRS13.MSSQLSERVER\Reporting Services\ReportServer\Pages 下的 ReportViewer.aspx?
    • @Vivek Patel :是的,就是这样,除非您还有另一个命名的 SSRS 实例。
    猜你喜欢
    • 1970-01-01
    • 2018-02-06
    • 2018-12-23
    • 1970-01-01
    • 1970-01-01
    • 2013-11-09
    • 1970-01-01
    相关资源
    最近更新 更多