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