1

フォーム認証と Cookie の永続性に関して多くの質問があることは承知していますが、1 日のほとんどを調査に費やしたので、まだ問題があります。

私の問題

アプリの特定の部分へのアクセスを認証されたユーザーに制限する Web アプリ (VS2010 ですが、webapp は f/w 3.5) に取り組んでいます (他の部分は開いています)。私の問題は、ブラウザを閉じた後、認証 Cookie が保持されていないように見えることです。

私のアプローチ

次のように web.config で構成されている単純な login.aspx ページを作成しました。

<authentication mode="Forms">

...そして、個々のページの動作は次のように宣言されます。

<location path="ClientManageAccount.aspx">
     <system.web>
        <authorization>
           <deny users="?" />
        </authorization>
     </system.web>
</location>

...これらのクッキーの悪ふざけを除いて、あらゆる点でうまく機能します...

login.aspx ページで、ユーザーのログインとパスワードをデータベース (正常に動作します) に対して認証したら、認証 Cookie を手動で作成します。ユーザーが「ログイン状態を維持する」チェックボックスを選択すると、次のメソッドを使用して Cookie が生成されます。

    private void GenerateAuthenticationCookie(int expiryInMinutes, Guid userGuid)
    {
        DateTime cookieExpiration = DateTime.Now.AddMinutes(expiryInMinutes); // change to months for production
        var authenticationTicket =
            new FormsAuthenticationTicket(
                2,
                userGuid.ToString(), 
                DateTime.Now,
                cookieExpiration,
                true, 
                string.Empty,
                FormsAuthentication.FormsCookiePath);

        // ticket must be encrypted
        string encryptedTicket = FormsAuthentication.Encrypt(authenticationTicket);

        // create cookie to contain encrypted auth ticket
        var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
        authCookie.Expires = authenticationTicket.Expiration;
        authCookie.Path = FormsAuthentication.FormsCookiePath;

        // clear out existing cookie for good measure (probably overkill) then add
        HttpContext.Current.Response.Cookies.Remove(FormsAuthentication.FormsCookieName); 
        HttpContext.Current.Response.Cookies.Add(authCookie);
    }

ここでの目的は、ユーザー Guid を認証 Cookie に保存し、それを使用してユーザー オブジェクトをセッションに復元することです (これは login.aspx ページにもあります。私の考えでは、作成した認証 Cookie からユーザー GUID を取得し、それを使用して対応するユーザー レコードをセッションに詰め込み、要求されたページにリダイレクトします):

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            TryAutoLogin();
        }            
    }

    private void TryAutoLogin()
    {
        HttpCookie cookie = HttpContext.Current.Request.Cookies.Get(FormsAuthentication.FormsCookieName);

        if (cookie != null)
        {
            FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);

            if (ticket != null)
            {
                if (ticket.Name.Length > 0)
                {
                    try
                    {
                        Guid userGuid = new Guid(ticket.Name);
                        KUser user = UserFunctions.GetUserFromUserGuid(userGuid);

                        if (user != null) Session["User"] = user;

                        FormsAuthentication.RedirectFromLoginPage(userGuid.ToString(), true);
                    }
                    catch (Exception anyException)
                    {
                        // don't do anything for now - do something smart later :-)                        }
                }
            }
        }
    }

最後に、login.aspx ページのログイン ボタンのコードを次に示します。

    protected void Submit_OnClick(object sender, EventArgs e)
    {
        long userId = 0;
        UserAuthenticationStatus status;

        status = (UserAuthenticationStatus)UserFunctions.UserAuthenticates(EmailAddress.Text, Password.Text, ref userId);

        switch (status)
        {
            case UserAuthenticationStatus.Authenticated:
                //email address and password match, account ok, so log this user in
                KUser user = UserFunctions.GetUser(userId);
                Session["User"] = user;

                if (ChkRememberMe.Checked)
                {
                    GenerateAuthenticationCookie(15, user.UserGuid); // 15 minutes

                    FormsAuthentication.RedirectFromLoginPage(user.UserGuid.ToString(), true);
                }
                else
                {
                    FormsAuthentication.RedirectFromLoginPage(user.UserGuid.ToString(), false);
                }
                break;
            case UserAuthenticationStatus.AuthButLocked:
                // email/pwd match but account is locked so do something
                ShowLockedAccountMessage();
                break;
            case UserAuthenticationStatus.EmailFoundIncorrectPassword:
            case UserAuthenticationStatus.EmailNotFound:
            case UserAuthenticationStatus.Unknown:
                // either the email wasn't found, or the password was incorrect or there was some other problem
                // present message stating this and offer chance to register
                ShowFailedLoginMessage();
                break;
            default:
                ShowUnavailableMessage();
                break;
        }
    }

ご覧のとおり、特に複雑なことは何もありませんが、GenerateAuthenticationCookie(..) で作成された authCookie が正しく作成されているにもかかわらず (私が知る限り)、持続しません。

私が気づいたことの 1 つは、次のようなコードを global.asax.cs の Application_AuthenticateRequest メソッドに配置すると、次のようになることです。

    protected void Application_AuthenticateRequest(object sender, EventArgs e)
    {
        HttpCookie cookie = HttpContext.Current.Request.Cookies.Get(FormsAuthentication.FormsCookieName);

        if (cookie != null)
        {
            int x = 4;  // just a dummy line so that I can set a breakpoint
        }
    }

...そのブレークポイントは、新しいブラウザー セッションに続いてヒットすることがありますが、スタートアップ ページ (この場合、純粋に開発とテストのために使用されるダミーの start.aspx ページ) から移動するとヒットしなくなります。

長い質問で申し訳ありませんが、私はここで本当に苦しんでいます。

確認・試したこと

コードが実行されていることを確認する - はい ブラウザの設定 - つまり、終了時に Cookie を削除しない - 削除を確認しない 別のタイムアウトを試す - たとえば、web.config のタイムアウトと同じまたは異なる、問題ではないようです... ...もちろん、少なくとも 20 から 30 の異なる以前の質問がありますが、役に立ちませんでした。

システム/開発環境

Windows 7 64 ビット、VS2010 (ただし proj は 3.5)、SQL Server 2008 Express。

私の開発サーバーでは、この問題が残っているため、必ずしも環境に問題があるかどうかはわかりません。そのマシンは、SQL 2008R2 を実行している WS2008R2 ボックスであり、同じ問題が残っています。

ここで試してみることができるアイデアはありますか? global.asax.cs の最初の Application_AuthenticateRequest ヒットをインターセプトし、セッション状態に何かを詰め込んで「認証済み」としてマークすることで、これを機能させることができるという予感があります (そのメソッドが呼び出されるたびに高価な認証試行を避けるためです。 1ページに数回あります。

前もって感謝します、

ジョン

4

1 に答える 1

3

OK、それを書くのにずっと時間を費やしてきたので、明確な瞬間があり、(a) (これが適切に機能していれば) ログインのように Page_Load() をチェックする必要がないことに気付きました。 aspx ページはまったく呼び出されず、(b) Session_Start (TryAutoLogin コードを再配置した場所) から Cookie にアクセスできたはずです。

これ自体は一歩前進でしたが、Cookie を取得したため、そこからユーザー GUID を取得したにもかかわらず、まだ login.aspx ページにパントされていることがわかりました。

この時点で、親マスター ページと 2 つの子マスター ページがあることを思い出しました。セッション タイムアウトの問題を漠然と思い出し、次のコードを OnInit オーバーライドに配置しました。

       if (Session["User"] == null)
       {
              FormsAuthentication.SignOut();
              FormsAuthentication.RedirectToLoginPage();
              Response.End();
       }

...それ自体はそれほど悪くはありませんでした (そして、タイムアウトに関する厄介なバグを回避しました) だけでなく、start.aspx ページでも、この宝石を見つけました:

Session.Clear();

...Page_Load で!

そのため、新しく回復したユーザー レコードを配置したセッションをうっかりクリアしてしまったのです。これは、承認マスター ページの OnInit オーバーライドが、ユーザー オブジェクトの不在を検出していたことを意味します。- ユーザーをサインアウトすると、認証 Cookie が削除されます...

それで、後で少し配線して調べて、これを寝かせることができます。

読んでくれてありがとう(たとえ私が自分でそれを理解したとしても)... :)

于 2011-01-18T11:41:47.007 に答える