2

我正在为 asp.net MVC 应用程序使用自定义表单身份验证,并且在某些用户似乎没有 cookie 时遇到问题。我们使用的自定义表单身份验证方法与此类似——自定义表单身份验证。本质上,我们创建了一个自定义的 Principal 和 Identity,对其进行序列化,并将其存储在 FormsAuthenticationTicket 的 UserData 属性中:

登录

MyCustomPrincipal principal = new MyCustomPrincipal(user);
DateTime expiration = DateTime.Now.AddMinutes(30);

FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
                1,
                u.Username,
                DateTime.Now,
                expiration,
                true,
                JsonConvert.SerializeObject(principal));

HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(authTicket));
cookie.Expires = expiration;

Response.Cookies.Set(cookie);

然后我们在 global.asax 的 Application_AuthenticateRequest 事件中获取 auth cookie。

global.asax - Application_AuthenticateRequest

// Get the authentication cookie
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = Context.Request.Cookies[cookieName];

// If the cookie can't be found, don't issue the ticket
if (authCookie == null) return;

// Get the authentication ticket and rebuild the principal
// & identity
FormsAuthenticationTicket authTicket =
          FormsAuthentication.Decrypt(authCookie.Value);

MyCustomPrincipal userPrincipal = new MyCustomPrincipal(authTicket.UserData);
DateTime expiration = DateTime.Now.AddMinutes(30);

FormsAuthenticationTicket newAuthTicket = new FormsAuthenticationTicket(
              1,
              ((MyCustomIdentity)userPrincipal.Identity).Username,
              DateTime.Now,
              expiration,
              true,
              JsonConvert.SerializeObject(userPrincipal));



authCookie.Value = FormsAuthentication.Encrypt(newAuthTicket);
authCookie.Expires = expiration;

HttpContext.Current.Response.Cookies.Set(authCookie);

Context.User = userPrincipal;

网络配置

<authentication mode="Forms">
  <forms loginUrl="~/Home/Index" timeout="29" name="MYFORMSAUTH" cookieless="UseCookies"/>
</authentication>

这对大多数用户来说都很好,但是,有些用户似乎没有设置授权 cookie。我做了一些测试以向我的 Elmah 错误日志添加更多信息,以查看是否可以找到有关该问题的更多信息。

首先,我尝试在 Login 方法中设置 authcookie 之前和之后设置一些测试 cookie。这些 cookie 没有出现在 Elmah 日志中,因此在此方法中添加任何类型的 cookie 似乎都不起作用。但是,日志中还有其他 cookie,包括 ASP.NET_SessionId,一个谷歌分析 cookie,有时我什至在应用程序的其他位置设置了其他 cookie(可能来自上一个会话)

其次,我尝试从登录操作向会话添加一些信息,如果在下一个操作中找不到 authcookie,则将其包含在错误日志中。我包括了 cookie 的长度(名称的长度 + 加密值的长度)以及尝试登录的时间。仅当用户的凭据有效并且应用程序尝试添加身份验证 cookie 时才会添加这两者。我确实在生成的错误日志中看到了这些值。长度总是大于0,我没见过比2300大的,所以我觉得大小不是问题。并且尝试登录与错误发生的时间相同 - 因此会话变量应该在错误发生之前立即设置(cookie丢失)。

还有一些注意事项 -

  • 似乎没有任何浏览器特别会导致错误更多(尽管它可能在移动浏览器上发生更多)
  • 同样,该网站似乎适用于大多数用户,当然我们无法重现该问题
  • 由于我没有看到测试 cookie,我猜测由于某种原因,当时没有从登录方法设置 cookie(尽管我可以看到其他地方设置了其他 cookie,这意味着以前的成功登录)
  • elmah 日志中的 http 引用通常设置为登录页面,这意味着用户可能不会在没有登录的情况下访问违规页面(至少在某些时候) - 会话变量的状态似乎支持该假设
  • 我经常连续看到多个这些错误(相隔一分钟左右) - 暗示重复登录尝试无法解决问题(不知道为什么会这样)
  • 出现此问题的用户似乎继续遇到此问题。换句话说,它似乎不是“抽奖”——而是用户帐户(cookie 长度会话变量暗示它正在正确序列化)或客户端浏览器的某些东西。
  • 我听说至少有一个用户能够在移动设备上登录,但不能在他们的桌面上登录
  • 该站点总共可能使用了 10 个左右的 cookie(包括所有已添加的各种测试 cookie)——在添加测试 cookie 之前,它使用了大约 4 个,包括 auth cookie。此外,当错误发生时,请求中通常只有 2 或 3 个 cookie,所以我认为 cookie 的数量不是问题。

在这一点上,我愿意尝试几乎任何事情。我尝试使用存储在会话中的自定义身份作为备份进行设置,但无法使其正常工作,因此即使有人对如何实现它有想法(如果可能),我将不胜感激(如果这是题外话我可以删除它)。

对不起,文字的墙壁,但我只是想指出我们已经调查并最有可能排除的所有潜在问题。

编辑

看来可能还有另一个潜在的相关问题。我看到错误日志让我相信某些身份的“IsAuthenticated”属性在不应该设置为 false 时被设置为 false。我们确实将其初始化为 false,并在用户回答安全问题后将其设置为 true。当我们将其设置为 true 时,它​​应该更新原理和身份验证票证/cookie。我不确定这是否是因为我如何反序列化自定义主体的一些问题。

4

2 回答 2

0

是否启用了服务器端缓存?我记得我有类似的问题,原因是服务器端缓存(配置错误)并且服务器端代码未执行但客户端到达页面。此外,在我这边还有一个错误(在动态页面上启用缓存的 iis 错误),在某些情况下,会话 cookie 被发送到多个客户端,这会导致意外结果。

这可以解释您的非日志记录行为和客户端上不存在的 cookie。

问候

于 2014-04-21T08:50:17.647 回答
0

所以我有点放弃并决定使用 Session 来存储我的主体,并在我没有看到身份验证 cookie 时检查它。我可以通过创建自定义 Authorize 属性并在那里检查会话来轻松地做到这一点。我还没有将它推向生产,所以我不能 100% 确定这会解决这个问题,但初步测试表明它就足够了。

自定义授权属性

public class MyCustomAuthorizeAttribute : AuthorizeAttribute
{

    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        // Get the authentication cookie
        string cookieName = FormsAuthentication.FormsCookieName;
        HttpCookie authCookie = HttpContext.Current.Request.Cookies[cookieName];

        // If the cookie can be found, use the base authentication
        if (authCookie != null)
        {
            base.OnAuthorization(filterContext);
        }
        else
        {
            // The cookie is not found, check the session for the principal

            var p = HttpContext.Current.Session[FormsAuthentication.FormsCookieName];

            if (p != null)
            {
                // there is a principal object in the session
                MyCustomPrincipal principal = (MyCustomPrincipal)p;

                HttpContext.Current.User = principal;                   

                // we've loaded the principal, now just do the base authorization
                base.OnAuthorization(filterContext);

            }
            else
            {
                // there is no principal object in the cookie or the session, the user is not authenticated
                HandleUnauthorizedRequest(filterContext);
            }

        }
    }     
}

一旦我们使用自定义授权属性适当地设置了当前主体,我们就可以使用基本授权,因此我们不必担心自己实现该功能。基本授权应检查当前主体并据此进行授权。

我不会将此标记为答案,因为它并不能真正解决根本问题,但我认为我会提供它作为潜在的解决方法,以防其他人偶然发现类似问题。

于 2014-04-29T20:38:29.360 回答