MVC Forms Authentication Timeout
シナリオ:.NET MVC、Forms Authentication にてtime out を60分に設定する。
IISのタイムアウトを65分に設定:
<system.web>
<authentication mode="Forms">
<forms loginUrl="~/Pub/SignIn" defaultUrl="~/List/Index" name="authest" protection="All" path="/" timeout="65" slidingExpiration="true" cookieless="UseCookies" enableCrossAppRedirects ="true"/>
</authentication>
<sessionState mode="InProc" timeout="65" customProvider="DefaultSessionProvider">
JSでタイムアウトを 60分に設定:
var timeout = {
timoutPeriod: 60, // in min.
timerId: null,
resetTimeOut: function () {
if (0 < timeout.timoutPeriod) {
window.clearTimeout(timeout.timerId);
timeout.timerId = window.setTimeout(function () { timeout.timeOut(); }, timeout.timoutPeriod * 60000);
}
},
timeOut: function () {
dirty.pauseOnce();
virLoc("Pub/SignOout");
}
};
protected void Application_EndRequest(object sender, EventArgs args)
{
HttpContextWrapper context = new HttpContextWrapper(Context);
if (context.Response.StatusCode == 302 && !IsAuthorised(context))
if (context.Request.IsAjaxRequest())
context.Response.StatusCode = 401; // to let jQuery know (if 302 no error occures)
else if (context.Request.RequestType == "POST")
context.Response.Redirect("~/Pub/Signin"); // remove redirect url
}
bool IsAuthorised(HttpContextWrapper context)
{
if (context.User.Identity.IsAuthenticated)
return true;
HttpCookie authCookie = context.Response.Cookies.Get(FormsAuthentication.FormsCookieName);
return authCookie != null && DateTime.Now < authCookie.Expires;
}
JS側はこれを受けて:
$(document).ajaxError(function (e, jqXHR, settings, thrownError) {
if (jqXHR.status === 401 || jqXHR.responseText && 0 <= jqXHR.responseText.indexOf("Pub/SignIn"))
bootbox.alert("Timeout occurred. Please sign in.", function () {
virLoc("Pub/SignIn"); // this does window.location.href = ....
});
else if (jqXHR.responseText)
bootbox.alert(jqXHR.responseText);
else if (jqXHR.responseJSON && jqXHR.responseJSON.errors)
bootbox.alert(jqXHR.responseJSON.errors);
else
bootbox.alert("Communication error.");
});
さらに一つ汚くなるものあり、auth 期限切れ File Download request。このままだと、Sign In 成功後、Download の Action に redirect されるので、白紙のページが表示され、File download が始まる。汚いし、Auth 期限が一度切れたのだから、やり直してもらおう。とはいえ、file download は通常 Page GET でくるから、上のように区別付けることが出来ない。まあ、download 目的のAction数は、かなり限られているだろうから、Authentication 成功後の挙動でカバーする。
...
Response.Cookies.Add(idCookie);
string redirect = FormsAuthentication.GetRedirectUrl(vm.UserId, false);
if (string.IsNullOrEmpty(redirect)
|| redirect.ToLower().Contains("signin")
|| redirect.ToLower().Contains("signout")
|| redirect.ToLower().Contains("getattachment")
)
return RedirectToAction("Index", "List");
else
return Redirect(redirect);
File Upload は、POST で来るので、if (context.Request.RequestType == "POST") に引っかかるのでOK。
IISのタイムアウトを65分に設定:
<system.web>
<authentication mode="Forms">
<forms loginUrl="~/Pub/SignIn" defaultUrl="~/List/Index" name="authest" protection="All" path="/" timeout="65" slidingExpiration="true" cookieless="UseCookies" enableCrossAppRedirects ="true"/>
</authentication>
<sessionState mode="InProc" timeout="65" customProvider="DefaultSessionProvider">
</system.web>
JSでタイムアウトを 60分に設定:
var timeout = {
timoutPeriod: 60, // in min.
timerId: null,
resetTimeOut: function () {
if (0 < timeout.timoutPeriod) {
window.clearTimeout(timeout.timerId);
timeout.timerId = window.setTimeout(function () { timeout.timeOut(); }, timeout.timoutPeriod * 60000);
}
},
timeOut: function () {
dirty.pauseOnce();
virLoc("Pub/SignOout");
}
};
timeout.resetTimeOut();
$(document).ajaxStop(function () {
timeout.resetTimeOut();
});
基本上でOK。しかしIIS Application Poolがリセットとかの火事はありえる。期限切れ auth cookies にて Requestが来た場合に綺麗に処理したい。
- Page GET request は IIS が Sign in 画面に redirect してくれるので問題なし。
- Page POST が期限切れの場合、少々厄介。Page GET同様、Sign In page への 302 redirect が返るのでそこは問題ない。しかし、そこに RedirectURL が残る。無事 Sign In が終了すると、無論その後の作りによるが、RedirectURL へ redirect する場合も多いだろう。この場合、そのまま Action が実行されると、、、パラメターは既に失われているので、事故となる可能性も全否定できず、幸運でもユーザーさんを悩ますようなものがまちがいなく表示されるは濃厚。POST を期待している Action (通常は CRUDする)には、[HttpPost] を明示しておこう。これを付けていれば、Page Not Found で、事故は防げる。ただし見た目汚い。この解決は後述。
- Ajax request の場合も少々厄介。JS側は Json を期待しているが、期限切れAuthの場合、IISは Page GET と同様、text で 302 redirect を返してしまう。これは$(document).ajaxError には引っかからずに、かつ Browser は実際に redirect を受けて実行する。かなり汚い状態。よって、Ajax request の場合で、期限切れの場合、302 ではなく、明示的なエラーを返すべき(期限切れAjax に対して default behavior がなぜ 302 処理しちゃうのかは不明、MVCの悪仕様か)。よって global.asaxにて:
{
HttpContextWrapper context = new HttpContextWrapper(Context);
if (context.Response.StatusCode == 302 && !IsAuthorised(context))
if (context.Request.IsAjaxRequest())
context.Response.StatusCode = 401; // to let jQuery know (if 302 no error occures)
else if (context.Request.RequestType == "POST")
context.Response.Redirect("~/Pub/Signin"); // remove redirect url
}
bool IsAuthorised(HttpContextWrapper context)
{
if (context.User.Identity.IsAuthenticated)
return true;
HttpCookie authCookie = context.Response.Cookies.Get(FormsAuthentication.FormsCookieName);
return authCookie != null && DateTime.Now < authCookie.Expires;
}
JS側はこれを受けて:
$(document).ajaxError(function (e, jqXHR, settings, thrownError) {
if (jqXHR.status === 401 || jqXHR.responseText && 0 <= jqXHR.responseText.indexOf("Pub/SignIn"))
bootbox.alert("Timeout occurred. Please sign in.", function () {
virLoc("Pub/SignIn"); // this does window.location.href = ....
});
else if (jqXHR.responseText)
bootbox.alert(jqXHR.responseText);
else if (jqXHR.responseJSON && jqXHR.responseJSON.errors)
bootbox.alert(jqXHR.responseJSON.errors);
else
bootbox.alert("Communication error.");
});
さらに一つ汚くなるものあり、auth 期限切れ File Download request。このままだと、Sign In 成功後、Download の Action に redirect されるので、白紙のページが表示され、File download が始まる。汚いし、Auth 期限が一度切れたのだから、やり直してもらおう。とはいえ、file download は通常 Page GET でくるから、上のように区別付けることが出来ない。まあ、download 目的のAction数は、かなり限られているだろうから、Authentication 成功後の挙動でカバーする。
...
Response.Cookies.Add(idCookie);
if (string.IsNullOrEmpty(redirect)
|| redirect.ToLower().Contains("signin")
|| redirect.ToLower().Contains("signout")
|| redirect.ToLower().Contains("getattachment")
)
return RedirectToAction("Index", "List");
else
return Redirect(redirect);
コメント
コメントを投稿