15

これは非常に単純な質問かもしれませんが、ASP.NET 4.0でこれがどのように機能するかを理解しようとして数時間経っても、まだわかりません。

フォーム認証を使用しています。ログインコントロールが設定されたログインページがあります。

これは、ユーザーがログインするときに必要なものです。

A-タイムアウトセットに対して何もしないまで、ユーザーはログに記録されたままにする必要があります。彼らがページをリロードした場合、タイムアウトはカウントダウンを再開する必要があります。

B-「RememberMe」チェックをクリックした場合、ブラウザを閉じてもコンピュータを再起動しても、ログアウトするまで接続を維持する必要があります。

私が抱えている問題は、彼らがログインしたときに、コンピューターにCookieが表示されないことです。

  1. クッキーはどこにありますか?メモリークッキーですか?
  2. セッションが期限切れになるとどうなりますか?タイムアウトが発生しない限り、ログに記録したままにしておきたいです。
  3. アプリプールがリサイクルされるとどうなりますか?

また、別の問題があります。「remember me」チェックをクリックしたとき(ケースB)、ログアウトボタンをクリックするまでログに記録してもらいたいです。今回はCookieが表示されますが、タイムアウトの間だけ接続されたままになっているようです...それで、私を覚えているかどうかの違いは何ですか...

認証とセッションを完全に分離したいのですが。それほど悪くない場合は、Cookieで認証を制御したいのですが。

助けてくれてありがとう-。

4

2 に答える 2

28

非永続的なスライド式の有効期限チケットの処理

フォーム認証では、チケットを永続化しない限り(たとえば、永続化する場合を除いて)、チケットにメモリ内Cookieを使用しますFormsAuthentication.SetAuthCookie(username, true)。デフォルトでは、チケットはスライド式の有効期限を使用します。リクエストが処理されるたびに、チケットは新しい有効期限とともに送信されます。その日付が期限切れになると、Cookieとチケットの両方が無効になり、ユーザーはログインページにリダイレクトされます。

フォーム認証には、タイムアウトより長く存在する、すでにレンダリングされているページをリダイレクトするための組み込みの処理がありません。これは自分で追加する必要があります。最も単純なレベルでは、JavaScriptを使用して、ドキュメントの読み込み時にタイマーを開始する必要があります。

<script type="text/javascript">
  var redirectTimeout = <%FormsAuthentication.Timeout.TotalMilliseconds%>
  var redirectTimeoutHandle = setTimeout(function() { window.location.href = '<%FormsAuthentication.LoginUrl%>'; }, redirectTimeout);
</script>

上記の場合、ページが更新または変更されredirectTimeoutHandleていない場合、またはその他の方法でキャンセルされていない場合(でclearTimeout(redirectTimeoutHandle);)、ログインページにリダイレクトされます。FormsAuthチケットはすでに有効期限が切れているはずなので、それで何もする必要はありません。

ここでの秘訣は、サイトがAJAXを機能させるかどうか、または他のクライアント側のイベントをアクティブなユーザーアクティビティ(マウスの移動やクリックなど)と見なすかどうかです。これらのイベントを手動で追跡する必要があり、発生したらリセットしredirectTimeoutHandleます。たとえば、AJAXを多用するサイトがあるため、ページが物理的に頻繁に更新されることはありません。私はjQueryを使用しているので、AJAXリクエストが発行されるたびにタイムアウトをリセットすることができます。これにより、実際には、ページが1つのページにあり、更新を行わない場合、ページがリダイレクトされます。

これが完全な初期化スクリプトです。

$(function() {
   var _redirectTimeout = 30*1000; // thirty minute timeout
   var _redirectUrl = '/Accounts/Login'; // login URL

   var _redirectHandle = null;

   function resetRedirect() {
       if (_redirectHandle) clearTimeout(_redirectHandle);
       _redirectHandle = setTimeout(function() { window.location.href = _redirectUrl; }, _redirectTimeout);
   }

   $.ajaxSetup({complete: function() { resetRedirect(); } }); // reset idle redirect when an AJAX request completes

   resetRedirect(); // start idle redirect timer initially.
});

AJAXリクエストを送信するだけで、クライアント側のタイムアウトとチケット(Cookieの形式)の両方が更新され、ユーザーは問題ないはずです。

ただし、ユーザーアクティビティによってFormsAuthチケットが更新されない場合、ユーザーは次に新しいページを要求したときに(ナビゲートまたはAJAXを介して)ログアウトしているように見えます。その場合、FormsAuthチケットを最新の状態に保つために、カスタムハンドラーやMVCアクションなどへのAJAX呼び出しでユーザーアクティビティが発生したときに、Webアプリケーションに「ping」を実行する必要があります。サーバーにpingを実行して最新の状態に保つ場合は、カーソルを移動したりクリックしたりするなどのリクエストでサーバーをあふれさせたくないので、注意する必要があることに注意してください。resetRedirectこれは、最初のページの読み込みとAJAXリクエストに加えて、ドキュメントのマウスクリックに追加する上記のinitスクリプトへの追加です。

$(function() {
   $(document).on('click', function() {
      $.ajax({url: '/ping.ashx', cache: false, type: 'GET' }); // because of the $.ajaxSetup above, this call should result in the FormsAuth ticket being updated, as well as the client redirect handle.
   });
});

「パーマネント」チケットの取り扱い

チケットは、任意に長いタイムアウトを使用して、永続的なCookieとしてクライアントに送信する必要があります。クライアントコードとweb.configはそのままにしておくことができるはずですが、ログインロジックで永続チケットに対するユーザーの設定を個別に処理します。ここで、チケットを変更する必要があります。以下は、そのようなことを行うためのログインページのロジックです。

// assumes we have already successfully authenticated

if (rememberMe)
{
    var ticket = new FormsAuthenticationTicket(2, userName, DateTime.Now, DateTime.Now.AddYears(50), true,
                                               string.Empty, FormsAuthentication.FormsCookiePath);
    var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket))
                     {
                         Domain = FormsAuthentication.CookieDomain,
                         Expires = DateTime.Now.AddYears(50),
                         HttpOnly = true,
                         Secure = FormsAuthentication.RequireSSL,
                         Path = FormsAuthentication.FormsCookiePath
                     };
    Response.Cookies.Add(cookie);
    Response.Redirect(FormsAuthentication.GetRedirectUrl(userName, true));
}
else
{
    FormsAuthentication.RedirectFromLoginPage(userName, false);
}

ボーナス:チケットに役割を保存する

役割をチケット/Cookieに保存して、再度検索する必要がないかどうかを尋ねました。はい、それは可能ですが、いくつかの考慮事項があります。

  1. Cookieは非常に大きくなる可能性があるため、チケットに入れるデータの量を制限する必要があります
  2. クライアントでロールをキャッシュする必要があるかどうかを検討する必要があります。

#2について詳しく説明するには:

ユーザーから受け取ったクレームを暗黙的に信頼するべきではありません。たとえば、ユーザーがログインして管理者であり、「remember me」をチェックして永続的な長期チケットを受け取った場合、そのユーザーは永久に(または、Cookieの有効期限が切れるか消去されるまで)管理者になります。誰かがデータベース内のそのロールからそれらを削除した場合でも、古いチケットを持っていれば、アプリケーションはそれらが管理者であると見なします。したがって、毎回ユーザーのロールを取得する方がよい場合がありますが、データベースの作業を最小限に抑えるために、アプリケーションインスタンスでロールを一定期間キャッシュします。

技術的には、これはチケット自体の問題でもあります。繰り返しになりますが、アカウントがまだ有効であるという有効なチケットを持っているという理由だけで、それを信頼するべきではありません。ロールと同様のロジックを使用できます。実際のデータベースにクエリを実行し、dbの結果を一定期間キャッシュすることで、チケットによって参照されるユーザーがまだ存在し、有効であること(ロックアウト、無効化、または削除されていないこと)を確認します。パフォーマンスを改善する時間。これは、チケットがIDクレームとして扱われるアプリケーションで行うことです(同様に、ユーザー名/パスワードは別のタイプのクレームです)。global.asax.cs(またはHTTPモジュール)の簡略化されたロジックは次のとおりです。

protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
  var application = (HttpApplication)sender;
  var context = application.Context;  

  EnsureContextUser(context);
}

private void EnsureContextUser(HttpContext context)
{
   var unauthorizedUser = new GenericPrincipal(new GenericIdentity(string.Empty, string.Empty), new string[0]);

   var user = context.User;

   if (user != null && user.Identity.IsAuthenticated && user.Identity is FormsIdentity)
   {
      var ticket = ((FormsIdentity)user.Identity).Ticket;

      context.User = IsUserStillActive(context, ticket.Name) ? new GenericPrincipal(user.Identity, GetRolesForUser(context, ticket.Name)) : unauthorizedUser;

      return; 
   }

   context.User = unauthorizedUser;
}

private bool IsUserStillActive(HttpContext context, string username)
{
   var cacheKey = "IsActiveFor" + username;
   var isActive = context.Cache[cacheKey] as bool?

   if (!isActive.HasValue)
   {
      // TODO: look up account status from database
      // isActive = ???
      context.Cache[cacheKey] = isActive;
   }

   return isActive.GetValueOrDefault();
}

private string[] GetRolesForUser(HttpContext context, string username)
{
   var cacheKey = "RolesFor" + username;
   var roles = context.Cache[cacheKey] as string[];

   if (roles == null)
   {
      // TODO: lookup roles from database
      // roles = ???
      context.Cache[cacheKey] = roles;
   }

   return roles;
}

もちろん、それを気にせず、チケットを信頼し、ロールをチケットに保存することもできます。まず、ログインロジックを上から更新します。

// assumes we have already successfully authenticated

if (rememberMe)
{
    var ticket = new FormsAuthenticationTicket(2, userName, DateTime.Now, DateTime.Now.AddYears(50), true, GetUserRolesString(), FormsAuthentication.FormsCookiePath);
    var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket))
                     {
                         Domain = FormsAuthentication.CookieDomain,
                         Expires = DateTime.Now.AddYears(50),
                         HttpOnly = true,
                         Secure = FormsAuthentication.RequireSSL,
                         Path = FormsAuthentication.FormsCookiePath
                     };
    Response.Cookies.Add(cookie);
    Response.Redirect(FormsAuthentication.GetRedirectUrl(userName, true));
}
else
{
    var ticket = new FormsAuthenticationTicket(2, userName, DateTime.Now, DateTime.Now.AddMinutes(FormsAuthentication.Timeout), false, GetUserRolesString(), FormsAuthentication.FormsCookieName);
    var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket))
       {
          Domain = FormsAuthentication.CookieDomain,
          HttpOnly = true,
          Secure = FormsAuthentication.RequireSSL,
          Path = FormsAuthentication.FormsCookiePath
       };
    Response.Cookies.Add(cookie);
    Response.Redirect(FormsAuthentication.GetRedirectUrl(userName, false));
}

メソッドの追加:

   private string GetUserRolesString(string userName)
   {
        // TODO: get roles from db and concatenate into string
   }

global.asax.csを更新して、チケットからロールを取得し、HttpContext.Userを更新します。

protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
  var application = (HttpApplication)sender;
  var context = application.Context;  

  if (context.User != null && context.User.Identity.IsAuthenticated && context.User.Identity is FormsIdentity)
  {
      var roles = ((FormsIdentity)context.User.Identity).Ticket.Data.Split(",");

      context.User = new GenericPrincipal(context.User.Identity, roles);
  }
}
于 2012-10-15T21:34:55.740 に答える
1

Aの場合、セッションタイムアウト変数を、ユーザーがログインしたままにする時間を設定します。Timeoutプロパティは、アプリケーションのSessionオブジェクトに割り当てられたタイムアウト期間を分単位で指定します。ユーザーがタイムアウト期間内にページを更新または要求しない場合、セッションは終了します。

パートBでは、その値をセッション変数(またはCookieですが、サーバーには存在しません)に格納し、global.asaxファイルのSession_Endイベントでその値を確認することをお勧めします。設定されている場合は、セッションを更新します。

Session_Endイベントは、ブラウザーを閉じたときに発生しません。サーバーが特定の期間(デフォルトでは20分)にユーザーからの要求を受け取らなかったときに発生します。

于 2012-10-15T19:58:37.850 に答える