none
.NET FW3.5 と4.0におけるASP.NET WEBサービス クライアントのCookieContainerの挙動の違いについて RRS feed

  • 質問

  • お世話になっております。

    背景として、以下のようなシステムの.NET Frameworkの3.5 SP1 ⇒4.0のバージョンアップ作業におきまして、フォーム認証のCookieがクライアント側から送信されなくなる症状が発生しました。

    ====================

    ・クライアント側はWindowsフォームアプリケーション、サーバ側はASP.NET WEBサービス(asmx)アプリケーション。

    ・クライアント側は 「サービス参照の追加」自動生成機能の中の、互換モードの方の「WEB参照の追加」で作成。

    ====================

    MSDNのオンラインヘルプ等を探しましたが動作変更に関する記述は見つけることが出来ずにいる状態です。同様の症状が発生している方はいないのか、これが正式な動作なのか、またこの現象の回避策があればお教え頂きたいです。

    ご参考までに、現象再現の最小セットのコードを抜粋して以下に貼ります。最小セットはコンソールアプリケーションとして作成しており、最小セットの動作確認環境は、Windows 7 Ultimate SP1(x64)です。

    ===サーバ側=============

        [WebService(Namespace = "XXX")]
        [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
        [System.ComponentModel.ToolboxItem(false)]
        public class CookieSettingWebService : System.Web.Services.WebService
        {
            private const string testCookieName = "TestCookieName";

            [WebMethod]
            public string SetCookie()
            {
                HttpCookie httpCookie = new HttpCookie(testCookieName, "TestCookieValue");
                httpCookie.Expires = DateTime.MaxValue;
                this.Context.Response.AppendCookie(httpCookie);

                return "CookieSettingWebService.SetCookie(): cookie setted";
            }

            [WebMethod]
            public string CheckCookie()
            {
                if (this.Context.Request.Cookies.Count == 0)
                {
                    return "CookieSettingWebService.CheckCookie(): no cookie";
                }

                HttpCookie httpCookie = this.Context.Request.Cookies[testCookieName];
                return "CookieSettingWebService.CheckCookie(): CookieValueChecked," + httpCookie.Value;
            }
        }

    =====================

    ===クライアント側===========

                localhost.CookieSettingWebService ws = new localhost.CookieSettingWebService();
                ws.CookieContainer = new System.Net.CookieContainer();
                Console.WriteLine(ws.SetCookie());
                Console.WriteLine(ws.CheckCookie());

    =====================

    クライアント側コードが.NET Framework 3.5 SP1の場合の実行結果は以下。

    CookieSettingWebService.SetCookie(): cookie setted
    CookieSettingWebService.CheckCookie(): CookieValueChecked,TestCookieValue

    クライアント側コードが.NET Framework 4.0の場合の実行結果は以下。

    CookieSettingWebService.SetCookie(): cookie setted
    CookieSettingWebService.CheckCookie(): no cookie

    お忙しい所お手数をおかけいたしますが、ご回答のほど頂けますよう、よろしくお願いいたします。

    2013年6月24日 5:49

回答

  • 問題は Web サービスで Cookie の有効期限を設定するところにあるようです。

    最初にアップされたコードでは、

    httpCookie.Expires = DateTime.MaxValue;

    としていますが、その場合、応答ヘッダの Set-Cookie に設定される有効期限は、

    expires=Fri, 31-Dec-9999 23:59:59 GMT;

    となります。.NET 3.5 SP1 の CookieContainer はそれでも問題なく Cookie を格納しますが、.NET 4 の CookieContainer は、理由不明ですが有効な Cookie とは見なさないらしく、無視します。

    Cookie の有効期限の設定を以下のようにすればうまくいくと思います。お試しください。

    httpCookie.Expires = DateTime.Now.AddYears(50);


    • 編集済み SurferOnWww 2013年6月25日 3:55 誤字訂正
    • 回答としてマーク yazuma5 2013年6月25日 5:36
    2013年6月25日 3:41

すべての返信

  • Fiddler などで要求/応答をキャプチャしてヘッダの内容を見てみてはいかがですか?

    Vista Ultimate SP2 の IIS7 上で、VS2010 Pro から .NET 4 ベースの asmx を動かして、IE9 で要求/応答を見てみましたが、Cookie は正しく授受されていました。何かの間違いということはないですか?


    <SetCookie 要求に対する Web サービスからの応答ヘッダ>

    HTTP/1.1 200 OK
    Cache-Control: private, max-age=0
    Content-Type: text/xml; charset=utf-8
    Server: Microsoft-IIS/7.0
    X-AspNet-Version: 4.0.30319
    Set-Cookie: TestCookieName=TestCookieValue; expires=Fri, 31-Dec-9999 23:59:59 GMT; path=/
    X-Powered-By: ASP.NET
    Date: Mon, 24 Jun 2013 10:05:22 GMT
    Content-Length: 132


    <CheckCookie 要求ヘッダ>

    POST http://aspnet4site/CookieSettingWebService.asmx/CheckCookie HTTP/1.1
    Accept: text/html, application/xhtml+xml, */*
    Referer: http://aspnet4site/CookieSettingWebService.asmx?op=CheckCookie
    Accept-Language: ja-JP,en-US;q=0.7,fr-FR;q=0.3
    User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/5.0)
    Content-Type: application/x-www-form-urlencoded
    Accept-Encoding: gzip, deflate
    Host: aspnet4site
    Content-Length: 0
    Connection: Keep-Alive
    Pragma: no-cache
    Cookie: TestCookieName=TestCookieValue

    【追伸】

    CheckCookie 要求に対する応答を書き忘れました。以下の通りとなります。従い、少なくとも報告されているような問題(結果が CookieSettingWebService.CheckCookie(): no cookie になる)はサーバー側にはないという結果です。

    HTTP/1.1 200 OK
    Cache-Control: private, max-age=0
    Content-Type: text/xml; charset=utf-8
    Server: Microsoft-IIS/7.0
    X-AspNet-Version: 4.0.30319
    X-Powered-By: ASP.NET
    Date: Tue, 25 Jun 2013 00:30:55 GMT
    Content-Length: 158

    <?xml version="1.0" encoding="utf-8"?>
    <string xmlns="CookieSettingWebService.CheckCookie">http://tempuri.org/">CookieSettingWebService.CheckCookie(): CookieValueChecked,TestCookieValue</string>


    クライアント側は当方では確認してませんが、質問者さんのほうでサーバー側に問題はないかまず確認お願いします。
    • 編集済み SurferOnWww 2013年6月25日 0:45 追伸を追加
    2013年6月24日 10:47
  • SurferOnWww様

    ご返信ありがとうございます。

    はい、仰る通りで、サーバー側には何も問題はありません。最小セットのサーバー側は同じく IIS7 上で動作させて確認しており、IE9 で要求/応答を見てみてもSurferOnWww様のご返信と同じくCookieは含まれております。

    >Cookieがクライアント側から送信されなくなる症状が発生しました。

    と記述させて頂いています通り、クライアント側にCookieは送付されています。ですが、その後クライアント側で再度サーバを呼び出そうとした際に、クライアントコードのCookieContainerにセットされなくなってしまうのです。

    例えば、クライアント側の自動生成クラスに以下の様なパーシャルクラスを追加して、無理やりCookieContainerをクライアント側で保持します。そして再度サーバを呼び出そうとした際にCookieContainerに明示的にセットしてあげることで、力技ですが、最小セットにおける.NET FW3.5 と4.0における挙動の違いを埋めることが出来ることを確認しました。

    汎用的に使える部品とするのにコストがかかる、今後のバージョンアップで障害が起こるかもしれない等、問題があるコードなのでシステム上利用することは避けたいですが・・。

        public partial class CookieSettingWebService
        {
            private const char semiCoron = ';';
            private const char equal = '=';
            private System.Net.CookieContainer testCookieContainer;

            // クライアントで明示的に保持するクッキーコンテナへのアクセサを提供します。
            public System.Net.CookieContainer TestCookieContainer
            {
                get { return testCookieContainer; }
                set { testCookieContainer = value; }
            }

            //
            // 概要:
            //     XML Web サービス メソッドへの同期要求から応答を返します。
            //
            // パラメーター:
            //   request:
            //     応答の取得元の System.Net.WebRequest。
            //
            // 戻り値:
            //     System.Net.WebResponse。
            protected override System.Net.WebResponse GetWebResponse(System.Net.WebRequest request)
            {
                Console.WriteLine("# Start GetWebResponse");

                System.Net.HttpWebResponse wr = (System.Net.HttpWebResponse)base.GetWebResponse(request);
                string[] str = wr.Headers.GetValues("Set-Cookie");

                if (str != null)
                {
                    Console.WriteLine("# クッキーをサービス参照クライアントのインスタンスに明示的に保持させます。");
                    string recievedCookieString = str[0] + str[1];
                    Console.WriteLine("# 受信したクッキー文字列: " + recievedCookieString);
                    System.Net.Cookie cookie = new System.Net.Cookie();
                    cookie.Name = recievedCookieString.Split(semiCoron)[0].Split(equal)[0];
                    cookie.Value = recievedCookieString.Split(semiCoron)[0].Split(equal)[1];
                    cookie.Expires = DateTime.MaxValue;
                    cookie.Path = recievedCookieString.Split(semiCoron)[2].Split(equal)[1];
                    this.TestCookieContainer = new System.Net.CookieContainer();
                    this.TestCookieContainer.Add(new Uri("XXX"), cookie);
                }

                Console.WriteLine("# End GetWebResponse");
                return base.GetWebResponse(request);
            }
        }

    上記を利用した場合のクライアント側呼出し部分は以下のようになります。

    ===クライアント側===========

                localhost.CookieSettingWebService ws = new localhost.CookieSettingWebService();
                ws.CookieContainer = new System.Net.CookieContainer();
                Console.WriteLine(ws.SetCookie());
                ws.CookieContainer = ws.TestCookieContainer; // 追加された行。パーシャルクラスに保持したクッキーコンテナを設定する。
                Console.WriteLine(ws.CheckCookie());

    =====================

    2013年6月25日 1:47
  • 問題は Web サービスで Cookie の有効期限を設定するところにあるようです。

    最初にアップされたコードでは、

    httpCookie.Expires = DateTime.MaxValue;

    としていますが、その場合、応答ヘッダの Set-Cookie に設定される有効期限は、

    expires=Fri, 31-Dec-9999 23:59:59 GMT;

    となります。.NET 3.5 SP1 の CookieContainer はそれでも問題なく Cookie を格納しますが、.NET 4 の CookieContainer は、理由不明ですが有効な Cookie とは見なさないらしく、無視します。

    Cookie の有効期限の設定を以下のようにすればうまくいくと思います。お試しください。

    httpCookie.Expires = DateTime.Now.AddYears(50);


    • 編集済み SurferOnWww 2013年6月25日 3:55 誤字訂正
    • 回答としてマーク yazuma5 2013年6月25日 5:36
    2013年6月25日 3:41
  • SurferOnWww様

    同様の症状を再現して下さったようで、ありがとうございます。

    また当方の環境でも、Cookie の有効期限の設定を以下のようにすることで.NET 4 の CookieContainer に格納されることが確認できました。

    より有用な回避策として、採用を検討したいと思います。

    httpCookie.Expires = DateTime.Now.AddYears(50);

    .NET 4 の CookieContainer が有効期限 DateTime.MaxValueのCookieを有効な Cookie とは見なさない問題については、正式に問い合わせを行いたいと思います。

    参考になるご回答、ありがとうございました。

    2013年6月25日 5:36
  • ご参考までに、問い合わせ結果を自己返信して掲載します。

    .NET Framework 3.5 ⇒4.0で、内部の実装コードに変更があり、4.0ではCookieContainerはクライアント プログラムが動作する環境のタイム ゾーンによっては有効期限 DateTime.MaxValueのCookieを有効な Cookie とは見なさないようです。

    クライアント プログラムが動作する環境のタイム ゾーンがどこに設定されていても問題のない状態とするためには、最大時差である +13:00 に合わせる必要があるので、Web サービス側ではCookie の有効期限 DateTime.MaxValue.AddHours(-13)とするのが適切とのこと。

    上記のようなクライアントコードでの対処も動作上特に問題はないかと思うが、汎用性に乏しく、また今後のメンテナンス性なども考慮するとWeb サービス側にて対処する形が望ましいということでした。

    2013年7月22日 0:13