ASP.NET MVC 会话过期

14
我们有一个内部的ASP.NET MVC应用程序需要登录。登录功能良好并且符合预期。我们设置了15分钟的会话过期时间。如果用户在单个页面停留了那么长时间,他们将失去会话。如果他们试图刷新当前页面或浏览其他页面,他们将会看到登录页面。我们保留了他们的请求,因此一旦他们登录,他们就可以继续访问他们请求的页面。这样做效果很好。
但是,我的问题在于某些页面上有AJAX调用。例如,他们可能会填写表单的一部分,然后离开并让他们的会话过期。当他们回来时,屏幕仍然显示之前的内容。如果他们只是填写一个框(这将触发AJAX调用),这个AJAX调用将返回登录页面(而不是像应该的那样简单地返回实际结果)。这看起来很糟糕。
我认为解决方案是使页面本身过期(这样当会话终止时,他们自动返回登录屏幕,而不需要任何操作)。然而,我想知道是否有意见/想法如何最好地在ASP.NET MVC中实现这一点,特别是关于最佳实践方面的问题。
更新:
所以我已经按照Keltex的建议在我的OnActionExecuting中实现了这个功能。
  if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
  {
    if (filterContext.HttpContext.Request.IsAjaxRequest())
    {
      filterContext.HttpContext.Response.Write("Invalid session -- please login!");
      filterContext.HttpContext.Response.End();
    }
    else
    {
      ...
    }
  }

这确实会让事情变得更好--即使他们有两个选项卡(其中一个具有他们可以触发的一些AJAX调用),并在第二个选项卡中明确注销,他们也会立即获得更加合理的东西,而不是一堆混乱的AJAX数据。

我仍然认为我会按照womp的建议实现Javascript倒计时。


@Andrew - 这是一个优雅的解决方案。或者,filterContext.HttpContext.Response.Redirect("/error/xxx");(或其他内容)是否可行? - Keltex
@Keltex:我可能会按照你的建议将其移动到视图中,但在我的许多 AJAX 调用中,它们返回原始数据(如值列表),没有 HTML,而在其他情况下,它们可能会返回整个表格的精美格式化数据。因此,“最低公共分母”只是原始数据可能效果最好。我会试一试。 - Andrew Flanagan
6个回答

16

具体而言,我不知道是否有任何与此相关的最佳实践,但我们正在为我们的应用程序执行此操作。我们选择了客户端解决方案,在主页面中输出会话超时值到一些JavaScript中,并计算会话何时过期。

在提前5分钟,我们弹出一个模态对话框,显示“你还在吗?”并附带一个倒计时器。一旦计时器达到0:00,我们将浏览器重定向到登录页面。

它使用最少量的JavaScript来进行时间和计时器计算,以及一个简单的.ashx处理程序,如果用户在会话过期之前点击对话框上的“我回来了!”按钮,则刷新会话。这样,如果他们及时返回,则可以在无需导航的情况下刷新会话。


2
+1:这将是一种比每次请求都检查会话更低影响力、代码更少的解决方案。 - Dave Swersky
2
这非常好。我一定会抄袭这个。 - user1228
不得不查一下“gank”的含义... :-) 我想我会实现这个功能,并根据Keltex的建议提供一些AJAX保护措施。谢谢! - Andrew Flanagan

7
我昨天问了相似的问题。这是我的解决方案:
修改后的授权属性:
public class OptionalAuthorizeAttribute : AuthorizeAttribute
{
    private class Http403Result : ActionResult
    {
        public override void ExecuteResult(ControllerContext context)
        {
            // Set the response code to 403.
            context.HttpContext.Response.StatusCode = 403;
            context.HttpContext.Response.Write(CTRes.AuthorizationLostPleaseLogOutAndLogInAgainToContinue);
        }
    }

    private readonly bool _authorize;

    public OptionalAuthorizeAttribute()
    {
        _authorize = true;
    }

    //OptionalAuthorize is turned on on base controller class, so it has to be turned off on some controller. 
    //That is why parameter is introduced.
    public OptionalAuthorizeAttribute(bool authorize)
    {
        _authorize = authorize;
    }

    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        //When authorize parameter is set to false, not authorization should be performed.
        if (!_authorize)
            return true;

        var result = base.AuthorizeCore(httpContext);

        return result;
    }

    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
        {
            //Ajax request doesn't return to login page, it just returns 403 error.
            filterContext.Result = new Http403Result();
        }
        else
            base.HandleUnauthorizedRequest(filterContext);
    }
}

“HandleUnauthorizedRequest”被重写,因此在使用Ajax时返回“Http403Result”。 “Http403Result”将StatusCode更改为403并在响应中向用户返回消息。 在属性中还有一些附加逻辑(“authorize”参数),因为我在基本控制器中启用了“[Authorize]”,但在某些页面中禁用它。
另一个重要部分是客户端全局处理此响应。 这就是我放置在Site.Master中的内容:
<script type="text/javascript">
    $(document).ready(
        function() {
            $("body").ajaxError(
                function(e,request) {
                    if (request.status == 403) {
                        alert(request.responseText);
                        window.location = '/Logout';
                    }
                }
            );
        }
    );
</script>

我放置了全局 ajax 错误处理程序,当每个 $.post 失败时出现 403 错误,响应消息会被弹出并将用户重定向到注销页面。现在我不必在每个 $.post 请求中处理错误,因为它是全局处理的。
为什么是 403 而不是 401?401 是由 MVC 框架内部处理的(这就是为什么在授权失败后重定向到登录页面)。
你认为呢?
编辑:
关于放弃 [Authorize] 属性:[Authorize] 不仅用于检查 Identity.IsAuthenticated,还处理页面缓存(因此您不会缓存需要身份验证的材料)和重定向。没有必要复制此代码。

2

您可以查看Ajax.BeginForm()中可以设置的AjaxOptions。有一个OnBegin设置,您可以与javascript函数相关联,该函数可以调用Controller方法以确认会话是否仍然有效,如果无效,则使用window.location重定向到登录页面。


1
问题的一部分似乎是你让框架来做所有事情。我不会用[Authorize]属性装饰你的AJAX方法。相反,检查User.Identity.IsAuthenticated,如果返回false,则创建合理的错误消息。

谢谢!我在OnActionExecuting中实现了这个(请参见上文)。 - Andrew Flanagan
Authorize属性没问题,但你必须适当修改它。检查User.Identity.IsAuthenticated不是同一件事。 - LukLed

0

我的解决方案在登录表单上使用一个元标记和一点Javascript/jQuery。

LogOn.cshtml

<html>
  <head>
    <meta data-name="__loginform__" content="true" />
    ...
  </head>
  ...
</html>

Common.js

var Common = {
    IsLoginForm: function (data) {
        var res = false;

        if (data.indexOf("__loginform__") > 0) {
            // Do a meta-test for login form
            var temp =
                $("<div>")
                    .html(data)
                    .find("meta[data-name='__loginform__']")
                    .attr("content");

            res = !!temp;
        }
        return res;
    }
};

AJAX 代码

$.get(myUrl, myData, function (serverData) {
    if (Common.IsLoginForm(serverData)) {
        location.reload();
        return;
    }

    // Proceed with filling your placeholder or whatever you do with serverData response
    // ...
});

-1

这是我是如何做到的...

在我的基础控制器中

 protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
        {
            if (filterContext.HttpContext.Request.IsAjaxRequest())
            {
                filterContext.HttpContext.Response.StatusCode = 403;
                filterContext.HttpContext.Response.Write(SessionTimeout);
                filterContext.HttpContext.Response.End();
            }
        }
    }

然后在我的全局 .js 文件中

$.ajaxSetup({
error: function (x, status, error) {
    if (x.status == 403) {
        alert("Sorry, your session has expired. Please login again to continue");
        window.location.href = "/Account/Login";
    }
    else {
        alert("An error occurred: " + status + "nError: " + error);
    }
}

});

SessionTimeout变量是一个noty字符串。出于简洁起见,我省略了实现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接