by WebSurfer
2011年8月6日 14:59
今回は URI に認証チケットを追加して送る場合の Forms 認証のログインとログアウト動作について調べてみました。クッキーを使用した場合については、先の記事 Forms 認証のログイン・ログオフ動作 を参照してください。
web.config で authentication の forms 要素 の cookieless 属性を AutoDetect に設定し、Firefox 5 で「サイトから送られてきた Cookie を保存する」のチェックを外してテストしました。
URI を使用する場合の問題は、上の画像に示したように、ブラウザのアドレスバーに認証チケットが表示されるので、認証チケットが一般ユーザーの目に直接さらられてしまうことです。というわけで、できればクッキーのみを使うようにしたほうが無難だと思われます。
***** ログイン *****
-
匿名アクセスが許可されているページ default.aspx で、ログインしていない状態から LoginStatus をクリックする。
-
JavaScrip が起動してポストバック(default.aspx への POST 要求)がかかる。
-
サーバーからリダイレクト指示(HTTP/1.1 302 Found)が返ってくる。応答ヘッダーに含まれる Location(リダイレクト先)は web.config で指定したログインページ login.aspx となる。
-
ブラウザはリダイレクト先のページ login.aspx を GET 要求する。・・・ここまではクッキーを使った場合と同じ。
-
再び、サーバーからリダイレクト指示(HTTP/1.1 302 Found)が返ってくる。応答ヘッダーに含まれる Location は同じく login.aspx だが、クエリ文字列に AspxAutoDetectCookieSupport=1 が追加されている。
-
再び、ブラウザはリダイレクト先のページ login.aspx を GET 要求する。前回の要求の時と違うのは、クエリ文字列に AspxAutoDetectCookieSupport=1 が追加されていること。
-
サーバーから応答(login.aspx)が返ってくる。
-
ユーザーが ID とパスワードを入力して[ログイン]ボタンをクリックする。
-
[ログイン]ボタンのクリックで login.aspx にポストバックがかかる。ただし、form 要素の action 属性に (X(1))/ が追加されているので、POST 先は login.aspx ではなく (X(1))/login.aspx となる。クエリ文字列
にも AspxAutoDetectCookieSupport=1 が追加されている。
-
サーバーからリダイレクト指示(HTTP/1.1 302 Found)が返ってくる。応答ヘッダーに含まれる Location は (X(1)F(mfZdwXpqsmZ ... 2Qg2g1))/default.aspx となり、mfZdwXpqsmZ ... 2Qg2g1 という認証チケットが追加されている。
-
リダイレクト指示を受けて、ブラウザは (X(1)F(mfZdwXpqsmZ ... 2Qg2g1))/default.aspx を GET 要求する。
-
サーバーから応答(default.aspx)が返ってくる。認証チケットが URI に含まれているのでユーザーは認証済みとなる。
***** ログアウト *****
-
ログインしている状態のページ (X(1)F(mfZdwXpqsmZ ... 2Qg2g1))/default.aspx で LoginStatus をクリック。
-
JavaScrip でポストバックがかかる。
-
サーバーからリダイレクト指示(HTTP/1.1 302 Found)が返ってくる。Location は (X(1))/default.aspx となる。この時、URI から認証チケットは削除されている。
-
ブラウザは (X(1))/default.aspx を GET 要求する。
-
サーバーから応答 (X(1))/default.aspx が返ってくる(ログアウトした後も URI に (X(1)) は含まれる)。ユーザーはログアウト状態となる。
by WebSurfer
2011年8月3日 22:14
認証クッキーを使用した場合における Forms 認証のログインとログアウト動作で、サーバーとクライアントのやり取りがどのように行われているか調べてみましたので、備忘録として書いておきます。
ログアウト操作で、認証クッキーを削除するため、ポストバックして同じページにリダイレクトをかけていることと、ロール名クッキーの追加と削除が認証クッキーより 1 ステップ遅れるのが新しい発見でした。
認証クッキーを使用せず、代わりに URL に認証チケットを追加して送るケースではどうなっているかは別途調べる予定です。
***** ログイン *****
-
匿名アクセスが許可されているあるページ(ここでは default.aspx とする)において、ログインしていない状態から LoginStatus をクリックする。
-
JavaScrip が起動してポストバック(default.aspx への POST 要求)がかかる。この時、サーバーに送信される隠しフィールド __EVENTTARGET に LoginStatus の UniqueID が設定される(これにより、サーバーはログイン要求であると認識するらしい)。
-
サーバーからリダイレクト指示(HTTP/1.1 302 Found)が返ってくる。応答ヘッダーに含まれる Location(リダイレクト先)は web.config で指定されたログインページ(ここでは login.aspx とする)となる。
-
リダイレクト指示を受けて、ブラウザは login.aspx を GET 要求する。
-
サーバーから応答(login.aspx)が返ってくる。
-
ユーザーが ID とパスワードを入力して[ログイン]ボタンをクリックする。
-
[ログイン]ボタンのクリックで login.aspx にポストバックがかかる。
-
サーバーからリダイレクト指示(HTTP/1.1 302 Found)が返ってくる。応答ヘッダーに含まれる Location は default.aspx となる。この時、サーバーからの応答ヘッダーには認証クッキーが含まれる。(この時点ではロール名クッキーは送られないので注意)
-
リダイレクト指示を受けて、ブラウザは default.aspx を GET 要求する。この時、上のステップで受信した認証クッキーを���求ヘッダーに含める。
-
サーバーから応答(default.aspx)が返ってくる。認証クッキーが要求ヘッダーに含まれているので、ユーザーは認証済みとなる。(この時点で応答ヘッダーにロール名クッキーが含まれて送られてくる)
***** ログアウト *****
-
あるページ(default.aspx とする)で、ログインしている状態から LoginStatus をクリック。
-
JavaScrip でポストバック(default.aspx への POST 要求)がかかる。この時、サーバーに送信される隠しフィールド __EVENTTARGET には LoginStatus の UniqueID が設定される(これにより、サーバーはログアウト要求であると認識するらしい)。
-
サーバーからリダイレクト指示(HTTP/1.1 302 Found)が返ってくる。ただし、リダイレクト先は同一ページ(Location は default.aspx)となる。この時、認証クッキーを削除するため、応答ヘッダの Set-Cookie は、認証クッキーを空にして、expires を過去の日時に設定している。
-
ブラウザは default.aspx を GET 要求する。この前のステップの、応答ヘッダの Set-Cookie の設定により、認証クッキーは要求ヘッダーには含まれなくなる。(ただし、ロール名クッキーはこの時点ではまだ要求ヘッダーに含まれている)
-
サーバーから応答 (default.aspx) が返ってくる。ユーザーはログアウト状態となる。この時、ロール名クッキーを削除するため、応答ヘッダの Set-Cookie で、ロール名クッキーを空にして、expires を過去の日時に設定している。
なお、Windows 認証の場合は、先の記事 Windows 認証でのロール に示した Windows OS の認証ダイアログを使います。従って、Web ページにはログイン画面は実装できませんし、ログイン操作も Forms 認証とは異なります。
また、ログアウトについても、Windows 認証の場合は一旦ログインしたユーザー情報はブラウザを閉じるまで保持され続けるので、Forms 認証とは異なり、Web ページにログオフ機能は実装できません。
by WebSurfer
2011年8月2日 23:55
HTTP 要求および HTTP 応答には本質的に状態(英語で state)がありませんが、ASP.NET ではポストバックと ViewState という機能を利用して状態情報をやり取りして、Windows アプリのようなイベントドリブンな Web アプリを実現しています。
ViewState は、サーバーから送信されてくる html コードの中の隠しフィールド(type 属性が "hidden" に設定された要素)に含まれています。内容は、サーバーからクライアントに送信された時点のページの状態情報(コントロールのプロパティ値など)です。
ポストバックとは、ブラウザが表示している同一ページに HTTP POST 要求を送信することです。ポストバックがかかると、ブラウザからは form のデータと ViewState のデーターがサーバーに送信され、同一ページの再描画・再送信が要求されます。
要求を受けたサーバーはメモリにそのページをロードし、ViewState の情報(即ち、前回送信時のページの状態情報)を用いてコントロールのプロパティを前回送信時の値に設定します。その後、ポストされた form データ(例:ユーザーが入力した TextBox.Text の値)と ViewState から再生されたプロパティ値(例:前回送信時の TextBox.Text の値)を比較して変更系イベントを発生させています。
ViewState に関する注意点、どのような構造になっているか、シリアライズ/デシリアライズなどをどこでどのように行っているかなどにつき、ポイントと思う点を備忘録として書いておきます。
-
POST された form のデータ(例:ユーザーが入力した TextBox.Text の値など)は ViewState には含まれません。
-
ViewState は動的に生成されたコントロールそのものを再生成することはできません。(ただし、そのプロパティ値は、先の記事 変更系イベント発生のメカニズム で書きましたように、Load イベント後の LoadViewState メソッドで設定されます。)
-
ポストバック前後で動的に設定が変わるプロパティや、バインドするデータ、ユーザーインターフェイスがなければ、コントロールは ViewState を保持しません。(例えば、静的にページに配置した TextBox の Text プロパティがポストバック前後で空白のままであれば、ViewState には何も保持されません。ユーザーが何か入力して POST すると、その応答で初めて ViewState に先にユーザーが入力したデータが保持されます。)
-
ViewState は Base64 エンコーディングされますが、デフォルトでは暗号化はされません。ただし、改ざん防止のため鍵付きハッシュが付与されています。詳しくは、Webアプリケーション開発技術大全の ViewStateのセキュリティ を参照ください。
@ Page ディレクティブの ViewStateEncryptionMode 属性を Always に設定すると ViewState 情報は常に暗号化されるということですが、性能への影響があるので止めておいた方が良さそうです。そもそも暗号化が必要なデータは ViewState ではなく Session に格納するのが基本で、ViewState を暗号化しなければならないケースはあまりなさそうです。ViewStateEncryptionMode 属性はデフォルトの Auto のままにしておいて、どうしても ViewState の暗号化が必要なページのみPage.RegisterRequiresViewStateEncryption メソッドを使って暗号化するべきと思います。
-
ASP.NET 2.0 では、ViewState とは別に、ControlState と呼ばれる別個のオブジェクトにサーバーコントロールが機能するために必要な状態情報が格納されます。開発者が ViewState を無効にしても ControlState は影響を受けません。従って、正確には、状態情報には ViewState と ControlState の両方が含まれます。
-
状態情報は、サーバーから送信される際は Page.SavePageStateToPersistenceMedium(Object) メソッド で保存され、ポストバックされた際は Page.LoadPageStateFromPersistenceMedium メソッド で再生されます。
-
シリアライズされる前の ViewState + ControlState オブジェクトは LOS (Limited Object Serialization) 形式というらしいです。このオブジェクトのシリアライズ/デシリアライズは LosFormatter クラス により行われます。
-
ViewState + ControlState オブジェクトは、System.Web.UI.Triplet、System.Web.UI.Pair、System.Collections.ArrayList などをノードとするツリー構造になっています。ViewState: All You Wanted to Know というページに、ViewState を表示/パースするサンプルコードがあります。このコードを使って、パースした結果を示したのが、上の画像です(一部分のみです。クリックすると拡大画面が表示されます)。ルートの一つ下の Pair の First(画像で '-2110287577')が ControlState、Second(5 行目の Pair 以下)が ViewState となっているようです。
上の画像のパース結果を表示したコードは以下のとおりです。ViewStateParserPage クラスは Page を継承したもので、中身のコードは ViewState: All You Wanted to Know のページの Listing 6 の通りです。
<%@ Page Language="C#"
Inherits="ViewStateParserPage"
Trace="true" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:SqlDataSource ID="SqlDataSource1"
runat="server"
ConnectionString="<%$ ConnectionStrings:Northwind %>"
SelectCommand=
"SELECT TOP 2 [CustomerID], [ContactName], [ContactTitle]
FROM [Customers]">
</asp:SqlDataSource>
<asp:GridView ID="GridView1"
runat="server"
AutoGenerateColumns="False"
DataKeyNames="CustomerID"
DataSourceID="SqlDataSource1"
EnableViewState="True">
<Columns>
<asp:BoundField
DataField="CustomerID"
HeaderText="CustomerID"
ReadOnly="True"
SortExpression="CustomerID" />
<asp:BoundField
DataField="ContactName"
HeaderText="ContactName"
SortExpression="ContactName" />
<asp:BoundField
DataField="ContactTitle"
HeaderText="ContactTitle"
SortExpression="ContactTitle" />
</Columns>
</asp:GridView>
</div>
</form>
</body>
その他 ViewState に関する詳細は、ViewState をキーワードに検索すれば参考になる記事が多数見つかりますので、そちらを見てください。(手抜きです(笑))