ASP.NET:为无 cookie 会话实现 ISessionIDManager?

Posted

技术标签:

【中文标题】ASP.NET:为无 cookie 会话实现 ISessionIDManager?【英文标题】:ASP.NET: Implementing ISessionIDManager for cookieless sessions? 【发布时间】:2011-11-29 05:20:18 【问题描述】:

问题:

我正在编写一个自定义会话提供程序。 到目前为止,它运行良好。 我决定添加一个自定义的 ISessionIDManager 来控制会话 ID。

它已经适用于 cookie 会话。 但是当我切换到 cookieless 时,就像这样:

<sessionState mode="Custom" customProvider="custom_provider" cookieless="true" timeout="1"
                sessionIDManagerType="Samples.AspNet.Session.MySessionIDManager"
                sqlConnectionString="Data Source=localhost;Initial Catalog=TestDB;User Id=SomeUser;Password=SomePassword;" 
                sqlCommandTimeout="10" 
                >
    <!-- timeout in minutes-->
    <providers>
      <add name="custom_provider" type="Test.WebSession.CustomSessionStoreProvider" />
    </providers>
  </sessionState>

然后它重定向到:http://localhost:52897/(77bb065f-d2e9-4cfc-8117-8b89a40e00d8)/default.aspx 这会引发 HTTP 404。 我明白为什么,因为没有这样的文件夹。 但是,当您使用默认会话管理器(asp.net 附带的)并切换到无 cookie 时,URL 看起来像这样:http://localhost:52897/(S(sq2abm453wnasg45pvboee45))/DisplaySessionValues.aspx 并且没有 HTTP 404... 我尝试将 (S and ) 添加到我的会话 ID 中,放在 url 的括号中,但这没有帮助。

我错过了什么?

using System;
using System.Configuration;
using System.Web.Configuration;
using System.Web;
using System.Web.SessionState;

// http://allantech.blogspot.com/2011/04/cookieless-session-state-in-aspnet.html
// http://forums.asp.net/t/1082784.aspx/1

// http://***.com/questions/4612310/implementing-a-custom-sessionidmanager
// http://msdn.microsoft.com/en-us/library/system.web.sessionstate.isessionidmanager.aspx
// http://msdn.microsoft.com/en-us/library/system.web.sessionstate.isessionidmanager(v=vs.80).aspx

namespace Samples.AspNet.Session


    // Samples.AspNet.Session.MySessionIDManager
    public class MySessionIDManager : IHttpModule, ISessionIDManager
    

        protected SessionStateSection pConfig = null;
        internal const string HeaderName = "AspFilterSessionId";


        protected void InitializeModule()
        
            // Obtain session-state configuration settings.
            if (pConfig == null)
            
                Configuration cfg =
                  WebConfigurationManager.OpenWebConfiguration(System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath);
                pConfig = (SessionStateSection)cfg.GetSection("system.web/sessionState");
             // End if (pConfig == null)   
        

        //
        // IHttpModule Members
        //


        //
        // IHttpModule.Init
        //
        public void Init(HttpApplication app)
        
            //InitializeModule();
         // End Sub Init


        //
        // IHttpModule.Dispose
        //
        public void Dispose()
        
         // End Sub Dispose




        //
        // ISessionIDManager Members
        //




        //
        // ISessionIDManager.Initialize
        //
        public void Initialize()
        
            InitializeModule();
         // End Sub Initialize


        //
        // ISessionIDManager.InitializeRequest
        //
        public bool InitializeRequest(
            HttpContext context,
            bool suppressAutoDetectRedirect,
            out bool supportSessionIDReissue
        )
        

            if (pConfig.Cookieless == HttpCookieMode.UseCookies)
            
                supportSessionIDReissue = false;
                return false;
            
            else
            
                supportSessionIDReissue = true;
                return context.Response.IsRequestBeingRedirected;
            

         // End Function InitializeRequest





        //
        // ISessionIDManager.GetSessionID
        //
        public string GetSessionID(HttpContext context)
        
            string id = null;

            if (pConfig.Cookieless == HttpCookieMode.UseUri)
            
                string tmp = context.Request.Headers[HeaderName];
                if (tmp != null)
                    id = HttpUtility.UrlDecode(id);

                // Retrieve the SessionID from the URI.
            
            else
            
                if (context.Request.Cookies.Count > 0)
                
                    id = context.Request.Cookies[pConfig.CookieName].Value;
                    id = HttpUtility.UrlDecode(id);
                
            

            // Verify that the retrieved SessionID is valid. If not, return null.

            if (!Validate(id))
                id = null;

            return id;
         // End Function GetSessionID


        //
        // ISessionIDManager.CreateSessionID
        //
        public string CreateSessionID(HttpContext context)
        
            return System.Guid.NewGuid().ToString();
         // End Function CreateSessionID


        //
        // ISessionIDManager.RemoveSessionID
        //
        public void RemoveSessionID(HttpContext context)
        
            context.Response.Cookies.Remove(pConfig.CookieName);
         // End Sub RemoveSessionID



        public static string InsertSessionId(string id, string path)
        
            string dir = GetDirectory(path);
            if (!dir.EndsWith("/"))
                dir += "/";

            string appvpath = HttpRuntime.AppDomainAppVirtualPath;
            if (!appvpath.EndsWith("/"))
                appvpath += "/";

            if (path.StartsWith(appvpath))
                path = path.Substring(appvpath.Length);

            if (path[0] == '/')
                path = path.Length > 1 ? path.Substring(1) : "";

            // //http://localhost:52897/(S(sq2abm453wnasg45pvboee45))/DisplaySessionValues.aspx
            return Canonic(appvpath + "(" + id + ")/" + path);
            //return Canonic(appvpath + "(S(" + id + "))/" + path);
        


        public static bool IsRooted(string path)
        
            if (path == null || path.Length == 0)
                return true;

            char c = path[0];
            if (c == '/' || c == '\\')
                return true;

            return false;
        


        public static string Canonic(string path)
        
            char[] path_sep =  '\\', '/' ;


            bool isRooted = IsRooted(path);
            bool endsWithSlash = path.EndsWith("/");
            string[] parts = path.Split(path_sep);
            int end = parts.Length;

            int dest = 0;

            for (int i = 0; i < end; i++)
            
                string current = parts[i];

                if (current.Length == 0)
                    continue;

                if (current == ".")
                    continue;

                if (current == "..")
                
                    dest--;
                    continue;
                
                if (dest < 0)
                    if (!isRooted)
                        throw new HttpException("Invalid path.");
                    else
                        dest = 0;

                parts[dest++] = current;
            
            if (dest < 0)
                throw new HttpException("Invalid path.");

            if (dest == 0)
                return "/";

            string str = String.Join("/", parts, 0, dest);

            str = RemoveDoubleSlashes(str);

            if (isRooted)
                str = "/" + str;
            if (endsWithSlash)
                str = str + "/";

            return str;
        




        public static string GetDirectory(string url)
        
            url = url.Replace('\\', '/');
            int last = url.LastIndexOf('/');

            if (last > 0)
            
                if (last < url.Length)
                    last++;

                return RemoveDoubleSlashes(url.Substring(0, last));
            

            return "/";
        


        public static string RemoveDoubleSlashes (string input)
        
          // MS VirtualPathUtility removes duplicate '/'

          int index = -1;
          for (int i = 1; i < input.Length; i++)
            if (input [i] == '/' && input [i - 1] == '/') 
              index = i - 1;
              break;
            

          if (index == -1) // common case optimization
            return input;

          System.Text.StringBuilder sb = new System.Text.StringBuilder(input.Length);
          sb.Append (input, 0, index);

          for (int i = index; i < input.Length; i++) 
            if (input [i] == '/') 
              int next = i + 1;
              if (next < input.Length && input [next] == '/')
                continue;
              sb.Append ('/');
            
            else 
              sb.Append (input [i]);
            
          

          return sb.ToString ();
        



        // http://www.dotnetfunda.com/articles/article1531-how-to-add-custom-headers-into-readonly-httprequest-object-using-httpmodule-.aspx
        public void SetHeader(string strHeaderName, string strValue)
        
            //get a reference 
            System.Collections.Specialized.NameValueCollection headers = HttpContext.Current.Request.Headers;
            //get a type 
            Type t = headers.GetType();
            //get the property 
            System.Reflection.PropertyInfo prop = t.GetProperty(
                  "IsReadOnly",
                  System.Reflection.BindingFlags.Instance
                | System.Reflection.BindingFlags.IgnoreCase
                | System.Reflection.BindingFlags.NonPublic
                | System.Reflection.BindingFlags.FlattenHierarchy
                | System.Reflection.BindingFlags.NonPublic
                | System.Reflection.BindingFlags.Public
                | System.Reflection.BindingFlags.FlattenHierarchy
            );
            //unset readonly 

            prop.SetValue(headers, false, null); // Set Read-Only to false

            //add a header 
            //HttpContext.Current.Request.Headers.Add(strHeaderName, strValue);
            //headers.Add(strHeaderName, strValue);

            t.InvokeMember("BaseAdd", 
                  System.Reflection.BindingFlags.InvokeMethod 
                | System.Reflection.BindingFlags.NonPublic 
                | System.Reflection.BindingFlags.Instance, 
                  null, 
                  headers,
                  new object[]  strHeaderName, new System.Collections.ArrayList  strValue  
            );

            prop.SetValue(headers, true, null); // Reset Read-Only to true

            // Victory !

            //string strCheckHeaders = string.Join(Environment.NewLine, HttpContext.Current.Request.Headers.AllKeys);
        

        //
        // ISessionIDManager.SaveSessionID
        //
        public void SaveSessionID(HttpContext context, string id, out bool redirected, out bool cookieAdded)
        
            if (!Validate(id))
                throw new HttpException("Invalid session ID");

            Type t = base.GetType();

            redirected = false;
            cookieAdded = false;

            if (pConfig.Cookieless == HttpCookieMode.UseUri)
            
                // Add the SessionID to the URI. Set the redirected variable as appropriate.

                //context.Request.Headers.Add(HeaderName, id);
                //context.Request.Headers.Set(HeaderName, id);
                SetHeader(HeaderName, id);

                cookieAdded = false;
                redirected = true;
                UriBuilder newUri = new UriBuilder(context.Request.Url);
                newUri.Path = InsertSessionId(id, context.Request.FilePath);

                //http://localhost:52897/(S(sq2abm453wnasg45pvboee45))/DisplaySessionValues.aspx
                context.Response.Redirect(newUri.Uri.PathAndQuery, false);
                context.ApplicationInstance.CompleteRequest(); // Important !

                return;
            
            else
            
                context.Response.Cookies.Add(new HttpCookie(pConfig.CookieName, id));
                cookieAdded = true;
            

         // End Sub SaveSessionID


        //
        // ISessionIDManager.Validate
        //
        public bool Validate(string id)
        
            try
            
                Guid testGuid = new Guid(id);

                if (id == testGuid.ToString())
                    return true;
            
            catch
            
            

            return false;
         // End Function Validate


     // End Class MySessionIDManager : IHttpModule, ISessionIDManager


 // End Namespace Samples.AspNet.Session

【问题讨论】:

我正在尝试做一些和你类似的事情。我想更改无 cookie 模式,因为它取决于我的域和 IP(wap 代理 ip)。我完全迷路了。你得到更多的东西吗?提前谢谢 【参考方案1】:

当所有其他方法都失败时,使用 Reflector 或 ILSpy 打开 .NET 实现,看看它们在做什么不同。

【讨论】:

他们没有实现 ISessionIDManager,他们实现了一个 HTTP 模块。【参考方案2】:

从头开始创建自定义会话 ID 管理器似乎需要做很多工作。从 System.Web.SessionState.SessionIDManager 类继承并重写 CreateSessionID 方法呢?

public class MySessionIDManager : SessionIDManager, ISessionIDManager

    public override string CreateSessionID(HttpContext context)
     
        return System.Guid.NewGuid().ToString("N");
    

【讨论】:

有趣。应该管用。这也需要覆盖验证,但这没问题。 @StefanSteiger 你试过使用SessionIDManager class 吗?

以上是关于ASP.NET:为无 cookie 会话实现 ISessionIDManager?的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET 身份会话 cookie 的安全性如何?

asp.net cookie、身份验证和会话超时

如何同步表单身份验证 cookie 和 Asp.Net 会话的生命周期?

ASP.Net Core 2.1 API JWT 无 cookie 会话?

Asp.net 核心中的会话和应用程序变量,以防我们使用 JWT 令牌(因此没有基于 cookie 的会话)

使用 WIF 和 jquery ajax 请求时 ASP.NET MVC 3 中的会话 Cookie 过期处理