1

意识到有很多与表单身份验证和 cookie 的持久性有关的问题,但是花了一天的大部分时间钻研,我仍然遇到困难。

我的问题

我正在开发一个 Web 应用程序(VS2010,但 webapp 是 f/w 3.5),它将对应用程序某些部分的访问限制为经过身份验证的用户(而其他部分是打开的)。我的问题是我的身份验证 cookie 在我关闭浏览器后似乎没有持续存在。

我的方法

我写了一个简单的 login.aspx 页面,在 web.config 中配置如下:

<authentication mode="Forms">

...并且各个页面的行为声明如下:

<location path="ClientManageAccount.aspx">
     <system.web>
        <authorization>
           <deny users="?" />
        </authorization>
     </system.web>
</location>

...除了这些饼干恶作剧之外,在各个方面都很好用...

一旦我在 login.aspx 页面中针对数据库(工作正常)对用户的登录名和密码进行了身份验证,我就会手动创建身份验证 cookie。如果用户选中“保持登录”复选框,则使用以下方法生成 cookie:

    private void GenerateAuthenticationCookie(int expiryInMinutes, Guid userGuid)
    {
        DateTime cookieExpiration = DateTime.Now.AddMinutes(expiryInMinutes); // change to months for production
        var authenticationTicket =
            new FormsAuthenticationTicket(
                2,
                userGuid.ToString(), 
                DateTime.Now,
                cookieExpiration,
                true, 
                string.Empty,
                FormsAuthentication.FormsCookiePath);

        // ticket must be encrypted
        string encryptedTicket = FormsAuthentication.Encrypt(authenticationTicket);

        // create cookie to contain encrypted auth ticket
        var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
        authCookie.Expires = authenticationTicket.Expiration;
        authCookie.Path = FormsAuthentication.FormsCookiePath;

        // clear out existing cookie for good measure (probably overkill) then add
        HttpContext.Current.Response.Cookies.Remove(FormsAuthentication.FormsCookieName); 
        HttpContext.Current.Response.Cookies.Add(authCookie);
    }

这里的目标是我将用户 Guid 存储在 auth cookie 中,然后我将使用它来将用户对象恢复到会话中(这也在 login.aspx 页面中,我的想法是我想采摘来自我创建的 auth cookie 的用户 guid,并使用它将相应的用户记录填充到会话中并重定向到请求的页面):

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            TryAutoLogin();
        }            
    }

    private void TryAutoLogin()
    {
        HttpCookie cookie = HttpContext.Current.Request.Cookies.Get(FormsAuthentication.FormsCookieName);

        if (cookie != null)
        {
            FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);

            if (ticket != null)
            {
                if (ticket.Name.Length > 0)
                {
                    try
                    {
                        Guid userGuid = new Guid(ticket.Name);
                        KUser user = UserFunctions.GetUserFromUserGuid(userGuid);

                        if (user != null) Session["User"] = user;

                        FormsAuthentication.RedirectFromLoginPage(userGuid.ToString(), true);
                    }
                    catch (Exception anyException)
                    {
                        // don't do anything for now - do something smart later :-)                        }
                }
            }
        }
    }

最后,这是我的 login.aspx 页面上登录按钮的代码:

    protected void Submit_OnClick(object sender, EventArgs e)
    {
        long userId = 0;
        UserAuthenticationStatus status;

        status = (UserAuthenticationStatus)UserFunctions.UserAuthenticates(EmailAddress.Text, Password.Text, ref userId);

        switch (status)
        {
            case UserAuthenticationStatus.Authenticated:
                //email address and password match, account ok, so log this user in
                KUser user = UserFunctions.GetUser(userId);
                Session["User"] = user;

                if (ChkRememberMe.Checked)
                {
                    GenerateAuthenticationCookie(15, user.UserGuid); // 15 minutes

                    FormsAuthentication.RedirectFromLoginPage(user.UserGuid.ToString(), true);
                }
                else
                {
                    FormsAuthentication.RedirectFromLoginPage(user.UserGuid.ToString(), false);
                }
                break;
            case UserAuthenticationStatus.AuthButLocked:
                // email/pwd match but account is locked so do something
                ShowLockedAccountMessage();
                break;
            case UserAuthenticationStatus.EmailFoundIncorrectPassword:
            case UserAuthenticationStatus.EmailNotFound:
            case UserAuthenticationStatus.Unknown:
                // either the email wasn't found, or the password was incorrect or there was some other problem
                // present message stating this and offer chance to register
                ShowFailedLoginMessage();
                break;
            default:
                ShowUnavailableMessage();
                break;
        }
    }

正如您所看到的,没有什么特别复杂的事情发生,但是尽管在 GenerateAuthenticationCookie(..) 中创建的 authCookie 被正确创建(据我所知)它不会持续存在。

我注意到的一件事是,如果我将一些代码放入 global.asax.cs 中的 Application_AuthenticateRequest 方法中,例如:

    protected void Application_AuthenticateRequest(object sender, EventArgs e)
    {
        HttpCookie cookie = HttpContext.Current.Request.Cookies.Get(FormsAuthentication.FormsCookieName);

        if (cookie != null)
        {
            int x = 4;  // just a dummy line so that I can set a breakpoint
        }
    }

...该断点有时会在新的浏览器会话之后被命中,尽管一旦我离开启动页面(在本例中是一个纯粹用于开发和测试的虚拟 start.aspx 页面)它就会停止命中。

所以,为这个冗长的问题道歉,但我真的在这里受苦。

我检查过/尝试过的事情

确保代码正在执行 - 是 浏览器设置 - 即退出时不删除 cookie - 确认不删除 尝试不同的超时 - 例如与 web.config 超时相同或不同,似乎并不重要......当然至少有二十或三十个不同的先前问题,但无济于事。

系统/开发环境

Windows 7 64 位,VS2010(但 proj 是 3.5),SQL Server 2008 Express。

在我的开发服务器上,这个问题仍然存在,所以我不确定它是否一定是环境问题——那台机器是运行 SQL 2008R2 的 WS2008R2 机器——同样的问题仍然存在。

有没有人在任何地方对我可以在这里尝试的事情有任何想法?我有一种预感,我可以通过拦截 global.asax.cs 中的初始 Application_AuthenticateRequest 命中并将某些内容填充到会话状态中以标记为“已验证”(以避免每次调用该方法时进行昂贵的身份验证尝试,结果是每页要多次。

提前致谢,

约翰

4

1 回答 1

3

好的,花了这么多时间写这些,我有一个清晰的时刻并意识到(a)我不需要对 Page_Load() 进行任何检查,因为(如果它工作正常)登录。根本不会调用 aspx 页面,并且 (b) 我应该能够从 Session_Start 获取 cookie - 这是我重新定位 TryAutoLogin 代码的地方。

这本身就是一个进步,但是尽管检索了 cookie 并从中检索了用户 guid,但我发现我仍然被退回到 login.aspx 页面。

正是在这一点上,我想起了这样一个事实,即我有一个父母版页和两个子母版页——我为非身份验证页面(例如主页)设置了一个,为需要身份验证的页面设置了一个。我模糊地回忆起会话超时的问题,并在 OnInit 覆盖中放置了以下代码:

       if (Session["User"] == null)
       {
              FormsAuthentication.SignOut();
              FormsAuthentication.RedirectToLoginPage();
              Response.End();
       }

...这本身并没有那么糟糕(并且避免了一个令人讨厌的超时错误)而且在 start.aspx 页面上,我发现了这个 gem:

Session.Clear();

...在 Page_Load!

所以,发生的事情是我无意中清除了我放置新恢复的用户记录的会话。这意味着授权母版页的 OnInit 覆盖然后检测到用户对象的缺失 - ta dah!- 将用户注销,这反过来又会删除授权 cookie...

所以,稍后进行一些接线和一些调查,我可以把这个放在床上。

感谢阅读(即使我确实自己弄清楚了)... :)

于 2011-01-18T11:41:47.007 回答