ネイティブフォーム認証とセッション機能を使用するASP.NET4.5WebFormsアプリケーションがあります。どちらも20分のタイムアウトがあり、有効期限がスライドします。
次のシナリオを想像してみてください。ユーザーがアプリケーションでしばらく作業した後、他の作業に進み、アプリケーションを20分間アイドル状態のままにします。次に、ユーザーはアプリケーションに戻ってレポートを作成します。ただし、ユーザーが保存しようとすると、ログイン画面が表示され、レポートは失われます。
明らかに、これは望ましくありません。このシナリオの代わりに、認証またはセッションのいずれかが期限切れになった瞬間に、ブラウザーをログインページにリダイレクトする必要があります。これを実現するために、これが当てはまるかどうかを確認するために呼び出すことができるWebApiサービスを構築しました。
public class SessionIsActiveController : ApiController
{
/// <summary>
/// Gets a value defining whether the session that belongs with the current HTTP request is still active or not.
/// </summary>
/// <returns>True if the session, that belongs with the current HTTP request, is still active; false, otherwise./returns>
public bool GetSessionIsActive()
{
CookieHeaderValue cookies = Request.Headers.GetCookies().FirstOrDefault();
if (cookies != null && cookies["authTicket"] != null && !string.IsNullOrEmpty(cookies["authTicket"].Value) && cookies["sessionId"] != null && !string.IsNullOrEmpty(cookies["sessionId"].Value))
{
var authenticationTicket = FormsAuthentication.Decrypt(cookies["authTicket"].Value);
if (authenticationTicket.Expired) return false;
using (var asdc = new ASPStateDataContext()) // LINQ2SQL connection to the database where our session objects are stored
{
var expirationDate = SessionManager.FetchSessionExpirationDate(cookies["sessionId"].Value + ApplicationIdInHex, asdc);
if (expirationDate == null || DateTime.Now.ToUniversalTime() > expirationDate.Value) return false;
}
return true;
}
return false;
}
}
このWebApiサービスは、認証またはセッションのいずれかが期限切れかどうかを確認するために、クライアントによって10秒ごとに呼び出されます。その場合、スクリプトはブラウザをログインページにリダイレクトします。これは魅力のように機能します。
ただし、このサービスを呼び出すと、認証とセッションの両方のスライド有効期限がトリガーされます。したがって、基本的に、終了しない認証とセッションを作成します。サービスの開始時にブレークポイントを設定して、これをトリガーする独自の関数の1つであるかどうかを確認しました。ただし、そうではなく、サービスの実行前に、ASP.NETのどこかで発生しているようです。
これは不可能のようです。スライドの有効期限が有効になると、常にトリガーされます。セッションを延長せずにアクセスする方法がある場合、それを見つけることができませんでした。
では、このシナリオにどのように取り組むのでしょうか?質問で最初に提案されたものに対して、次の代替ソリューションを考え出しました。これは、x秒ごとに自宅に電話をかけるためにWebサービスを使用しないため、実際にはより効率的です。
そのため、ASP.NETのフォーム認証またはセッションのいずれかが期限切れになったことを知る方法が必要です。これにより、ユーザーを事前にログアウトできます。ユーザーが複数のブラウザーウィンドウ/タブで同時にアプリケーションを操作できるため、すべてのページの単純なjavascriptタイマー( 提案どおり Khalid Abuhakmehによる)では不十分です。
この問題を単純化するために最初に決定したのは、セッションの有効期限をフォーム認証の有効期限よりも数分長くすることです。このように、フォーム認証の前にセッションが期限切れになることはありません。次回ユーザーがログインしようとしたときに古いセッションが残っている場合は、それを破棄して新しいセッションを強制します。
了解しました。フォーム認証の有効期限のみを考慮する必要があります。
次に、フォーム認証の自動スライド有効期限(web.configで設定)を無効にして、独自のバージョンを作成することにしました。
public static void RenewAuthenticationTicket(HttpContext currentContext)
{
var authenticationTicketCookie = currentContext.Request.Cookies["AuthTicketNameHere"];
var oldAuthTicket = FormsAuthentication.Decrypt(authenticationTicketCookie.Value);
var newAuthTicket = oldAuthTicket;
newAuthTicket = FormsAuthentication.RenewTicketIfOld(oldAuthTicket); //This triggers the regular sliding expiration functionality.
if (newAuthTicket != oldAuthTicket)
{
//Add the renewed authentication ticket cookie to the response.
authenticationTicketCookie.Value = FormsAuthentication.Encrypt(newAuthTicket);
authenticationTicketCookie.Domain = FormsAuthentication.CookieDomain;
authenticationTicketCookie.Path = FormsAuthentication.FormsCookiePath;
authenticationTicketCookie.HttpOnly = true;
authenticationTicketCookie.Secure = FormsAuthentication.RequireSSL;
currentContext.Response.Cookies.Add(authenticationTicketCookie);
//Here we have the opportunity to do some extra stuff.
SetAuthenticationExpirationTicket(currentContext);
}
}
このメソッドは、アプリケーションのBasePageクラスのOnPreRenderComplete
イベントから呼び出します。このイベントから、他のすべてのページが継承されます。通常のスライド式有効期限機能とまったく同じことを行いますが、いくつかの追加作業を行う機会があります。 SetAuthenticationExpirationTicket
メソッドを呼び出すようなものです。
public static void SetAuthenticationExpirationTicket(HttpContext currentContext)
{
//Take the current time, in UTC, and add the forms authentication timeout (plus one second for some elbow room ;-)
var expirationDateTimeInUtc = DateTime.UtcNow.AddMinutes(FormsAuthentication.Timeout.TotalMinutes).AddSeconds(1);
var authenticationExpirationTicketCookie = new HttpCookie("AuthenticationExpirationTicket");
//The value of the cookie will be the expiration date formatted as milliseconds since 01.01.1970.
authenticationExpirationTicketCookie.Value = expirationDateTimeInUtc.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds.ToString("F0");
authenticationExpirationTicketCookie.HttpOnly = false; //This is important, otherwise we cannot retrieve this cookie in javascript.
authenticationExpirationTicketCookie.Secure = FormsAuthentication.RequireSSL;
currentContext.Response.Cookies.Add(authenticationExpirationTicketCookie);
}
これで、ユーザーが異なるブラウザウィンドウ/タブで作業している場合でも、常に正しいフォーム認証の有効期限を表す追加のCookieを自由に使用できます。結局のところ、Cookieにはブラウザの広い範囲があります。残っているのは、Cookieの値を確認するためのjavascript関数だけです。
function CheckAuthenticationExpiration() {
var c = $.cookie("AuthenticationExpirationTicket");
if (c != null && c != "" && !isNaN(c)) {
var now = new Date();
var ms = parseInt(c, 10);
var expiration = new Date().setTime(ms);
if (now > expiration) location.reload(true);
}
}
(Cookieを取得するために jQuery Cookie Plugin を使用することに注意してください。)
この機能を間隔を置いて配置すると、ユーザーはフォーム認証の有効期限が切れるとすぐにログアウトされます。 Voilà:-)この実装の追加の特典は、フォーム認証の有効期限がいつ延長されるかを制御できるようになったことです。有効期限を延長しない一連のWebサービスが必要な場合は、それらに対してRenewAuthenticationTicket
メソッドを呼び出さないでください。
追加するものがあればコメントをドロップしてください!
これはすべて、サーバーに戻ることなく、クライアント側で解決できます。
JavaScriptではこれを行います。
var timeout = setTimeout(function () {
window.location = "/login";
}, twentyMinutesInMilliseconds + 1);
タイムアウトは、ページが更新されるたびに20分に設定されます。これにより、タイムアウトが発生する前に、ユーザーはすべての作業を完了する必要があります。多くのサイトがこの方法を使用しており、不要なサーバー要求を行う必要がありません。
Webサイトの機能は、JavaScriptがなくても機能するはずです。そうでない場合は、ある問題を別の問題に置き換えるだけです。私もこの問題に取り組んできましたが、これがどのように解決されたかです。
自分自身を認証すると、セッションCookieが作成されます。デフォルトの有効期間は20分です。これが期限切れになると、ユーザーはログアウトされます。
ユーザーがサインインフォームで「rememberme」を選択すると、追加の永続性Cookie [AuthCookie]がクライアント側とデータベースに作成されます。このCookieの有効期間は1か月です。ページが読み込まれるたびに、セッションと永続性のCookieデータが新しい有効期間で再作成されます(通常、チケットを復号化/暗号化します)。
次のシナリオを想像してみてください。ユーザーがアプリケーションでしばらく作業した後、他の作業に進み、アプリケーションを20分間アイドル状態のままにします。次に、ユーザーはアプリケーションに戻ってレポートを作成します。ユーザーが保存しようとすると、要求の前にセッションが復元されます。
これを行う1つの方法は、global.aspxを拡張して事前要求を処理することです。次の行の何か:
void application_PreRequestHandlerExecute(object sender, EventArgs e){
...
if (HttpContext.Current.Handler is IRequiresSessionState) {
if (!context.User.Identity.IsAuthenticated)
AuthService.DefaultProvider.AuthenticateUserFromExternalSource();
AuthenticateUserFromExternalSourceは、Cookieデータがデータベースのデータと一致するかどうかを確認する必要があります。これは、クライアント側に保存されているものはすべて変更できるためです。アクセス権のある有料サービスを利用している場合は、ユーザーがまだそれらの権利を持っているかどうかを確認してから、セッションを再作成できます。