Истечение срока действия MVC ASP.NET

У нас есть внутреннее приложение ASP.NET MVC, для которого требуется вход в систему. Вход в систему отлично работает и делает то, что ожидается. У нас заканчивается сеанс продолжительностью 15 минут. После сидения на одной странице за этот период времени пользователь потерял сеанс. Если они попытаются обновить текущую страницу или перейти к другой, они получат страницу входа в систему. Мы сохраняем их запрос, поэтому, как только они вошли в систему, они могут продолжить работу на запрошенной странице. Это отлично работает.

Однако моя проблема в том, что на некоторых страницах есть вызовы AJAX. Например, они могут заполнить часть формы, скинуться и допустить, чтобы их сеанс закончился. Когда они вернутся, экран все еще отображается. Если они просто заполнит поле (которое вызовет AJAX), вызов AJAX вернет страницу входа в систему (внутри любого div AJAX должен просто вернуть фактические результаты). Это выглядит ужасно.

Я думаю, что решение состоит в том, чтобы сделать страницу самой последней (так что, когда сеанс завершается, они автоматически возвращаются на экран входа в систему без каких-либо действий с ними). Тем не менее, мне интересно, есть ли мнения/идеи о том, как лучше всего реализовать это специально в отношении лучших практик в ASP.NET MVC.

Обновление:

Итак, я пошел вперед и реализовал это в своем OnActionExecuting (по предложению Keltex)

  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.

Я все еще думаю, что я буду использовать обратный отсчет Javascript, а также предложить матку.

Ответы

Ответ 1

В частности, я не знаю, что в нем есть какие-то лучшие практики, но я делаю это прямо сейчас для нашего приложения. Мы выбрали решение на стороне клиента, в котором мы выводим значение тайм-аута сеанса в некоторый javascript на главной странице и вычисляем, когда истечет сеанс.

За 5 минут до начала мы выставим модальное диалоговое окно с надписью "Ты все еще там?" с таймером обратного отсчета. Как только таймер достигнет 0:00, мы перенаправляем браузер на страницу входа.

Он реализован с минимальным количеством javascript, чтобы выполнять вычисления времени и таймера, и простой обработчик .ashx, который обновит сеанс, если пользователь нажмет "Я вернулся!". в диалоговом окне до истечения срока действия сеанса. Таким образом, если они вернутся вовремя, они могут обновить сеанс без какой-либо навигации.

Ответ 2

Я задал аналогичный вопрос вчера. Вот мое решение:

Измененный атрибут Authorize:

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>

Я размещаю GLOBAL-обработчик ошибок ajax, и когда evert $.post не работает с ошибкой 403, появляется ответное сообщение, и пользователь перенаправляется на страницу выхода. Теперь мне не нужно обрабатывать ошибки в каждом запросе $.post, потому что он обрабатывается глобально.

Почему 403, а не 401? 401 обрабатывается внутри с помощью среды MVC (поэтому перенаправление на страницу входа выполняется после неудачной авторизации).

Что вы думаете об этом?

EDIT:

Об отступлении от атрибута [Авторизовать]: [Авторизовать] - это не только проверка Identity.IsAuthenticated. Он также обрабатывает кеширование страниц (поэтому вы не кэшируете материал, требующий аутентификации) и перенаправление. Копировать этот код не нужно.

Ответ 3

Вы можете зайти в AjaxOptions, который можно установить в Ajax.BeginForm(). Существует параметр OnBegin, который вы можете связать с функцией javascript, которая может вызвать метод Controller, чтобы подтвердить, что сеанс по-прежнему действителен, а если нет, перенаправлять на страницу входа с помощью window.location.

Ответ 4

Частично проблема заключается в том, что вы разрешаете инфраструктуре делать все. Я бы не украсил ваш метод AJAX атрибутом [Authorize]. Вместо этого отметьте User.Identity.IsAuthenticated, и если он вернет false, создайте разумное сообщение об ошибке.

Ответ 5

Мое решение использует один метатег в форме входа и немного 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
    // ...
});

Ответ 6

Вот как я это сделал...

В моем базовом контроллере

 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-строкой. Я пропустил реализацию для краткости.