二重送信防止¶Caution本バージョンの内容は既に古くなっています。最新のガイドラインはこちらからご参照ください。(1)更新系ボタンの二重クリック更新処理を行うボタンを連続してクリックする。(2)更新処理完了後の画面の再読み込みブラウザの更新ボタンを使用することで、更新処理完了後の画面の再読み込みを行う。更新処理の完了画面からブラウザの戻るボタンを使用してページを戻し、更新処理を行うボタンを再度クリックする。それぞれ具体的な問題点を、以下に示す。更新処理を行うボタンを連続してクリックすると、以下のような問題が発生する。以下では、ショッピングサイトの商品購入を例として、対策を行わない場合にどのような問題が発生するのかを説明する。説明(1)購買者が、商品購入画面で注文ボタンをクリックする。(2)(1)のレスポンスが返る前に、購買者が誤って注文ボタンをもう一度クリックする。(3)サーバは、(1)のリクエストで受けた商品の購入処理をDBに対して反映する。(4)サーバは、(2)のリクエストで受けた商品の購入処理をDBに対して反映する。(5)サーバは、(2)のリクエストで受けた商品の購入完了画面を応答する。Warning上記のケースでは、購入者が誤って注文ボタンを押下することで、まったく同じ商品の購入が2回行われてしまうことになる。購入者の操作ミスが原因ではあるが、アプリケーションとして上記の問題が発生しないように制御する事が望ましい。更新処理完了後の画面の再読み込みを行うと、以下のような問題が発生する。以下では、ショッピングサイトの商品購入を例として、対策を行わない場合にどのような問題が発生するのかを説明する。説明(1)購買者が、商品購入画面で注文ボタンをクリックする。(2)サーバは、(1)のリクエストで受けた商品の購入処理をDBに対して反映する。(3)サーバは、(1)のリクエストで受けた商品の購入完了画面を応答する。(4)購買者が、誤ってブラウザのリロード機能を実行する。(5)サーバは、(4)のリクエストで受けた商品の購入処理をDBに対して反映する。(6)サーバは、(4)のリクエストで受けた商品の購入完了画面を応答する。Warning上記のケースでは、購入者が誤ってブラウザのリロード機能を実行することで、まったく同じ商品の購入が2回行われてしまうことになる。購入者の操作ミスが原因ではあるが、アプリケーションとして上記の問題が発生しないように制御する事が望ましい。ブラウザの戻るボタンを使用した不正な画面遷移¶ブラウザの戻るボタンを使用した不正な画面遷移を行うと、以下のような問題が発生する。以下では、ショッピングサイトの商品購入を例として、対策を行わない場合にどのような問題が発生するのかを説明する。説明(1)購買者が、商品購入画面で注文ボタンをクリックする。(2)サーバは、(1)のリクエストで受けた商品の購入処理をDBに対して反映する。(3)サーバは、(1)のリクエストで受けた商品の購入完了画面を応答する。(4)購買者が、ブラウザの戻るボタンを使って購入画面を再度表示する。(5)購買者が、ブラウザの戻るボタンを使って表示した購入画面で注文ボタンを再度クリックする。(6)サーバは、(5)のリクエストで受けた商品の購入処理をDBに対して反映する。(7)サーバは、(5)のリクエストで受けた商品の購入完了画面を応答する。Note上記のケースでは、購入者の操作ミスではないため、購入者に対して問題が発生することはない。不正な画面操作を行った後でも更新処理が実行できてしまうと、以下のような問題が発生する。Warning上記のケースのように、不正な画面操作を行った後でも更新処理が実行できてしまうと、悪意のある攻撃者によって、正規のルート経由せずに直接更新処理が実行される危険度が高まる。説明(1)攻撃者が、正規の画面遷移を行わずに、直接商品の購入を行う処理に対してリクエストを実行する。(2)サーバは、不正なルートでリクエストが行われていることを検知することができないため、リクエストで受けた商品の購入処理をDBに対して反映してしまう。不正なリクエストによって購入処理を実行することで、各サーバの負荷が高くなったり、正規のルートで商品が購入できなくなるなどの問題が発生してしまう。結果的に、正規のルートで購入している利用者に対して問題が波及する事になるため、アプリケーションとして上記の問題が発生しないように制御する事が望ましい。上記の問題を解決する方法として、下記の対策が必要になる。(3)の「トランザクショントークンチェックの適用」は必須である。更新処理を行うボタンを押下した際に、JavaScriptによるボタン制御を行うことで、2度押しされた際にリクエストが送信されないようにする。更新処理を行うリクエスト(POSTメソッドによるリクエスト)に対する応答としてリダイレクトを返却し、その後ブラウザから自動的にリクエストされるGETメソッドの応答として遷移先の画面を返却するようにする。PRGパターンを適用することで、画面表示後にページの再読み込みを行った場合に発生するリクエストがGETメソッドになるため、更新処理の再実行を防ぐことが出来る。画面遷移毎にトークン値を払い出し、ブラウザから送信されたトークン値とサーバ上で保持しているトークン値を比較することで、トランザクション内で不正な画面操作が行われないようにする。トランザクショントークンチェックを適用することで、ブラウザの戻るボタンを使ってページを移動した後の更新処理の再実行を防ぐことが出来る。トークン値のチェックを行った後にサーバで管理しているトークン値を破棄することで、サーバ側の処理として二重送信を防ぐことも出来る。Note「トランザクショントークンチェックの適用」のみの対策だと、単純な操作ミスを行った場合でもトランザクショントークンエラーとなるため、利用者に対してユーザビリティの低いアプリケーションになってしまう。ユーザビリティを確保しつつ、二重送信で発生する問題を防止するためには、「JavaScriptによるボタンの2度押し防止」及び「PRG(Post-Redirect-Get)パターンの適用」が必要となる。本ガイドラインでは、全ての対策を行うことを推奨するが、アプリケーションの要件によって対策の有無は判断すること。WarningAjaxとWebサービスでは、リクエスト毎に変更されるトランザクショントークンの受け渡しを行いにくいため、トランザクショントークンチェックを使用しなくてよい。Ajaxの場合は、JavaScriptによるボタンの2度押し防止のみで二重送信防止を行う。TodoTBDAjaxとWebサービスでのチェック方法は、今後検討の余地あり。更新処理を行うボタンや、時間のかかる検索処理を行うボタンなどに対して、ボタンの二重クリックを防止する。ボタンが押された際に、JavaScriptを使用してボタンやリンクの無効化の制御を行う。無効化するための代表的な制御例としては、ボタンやリンクを非活性化することで、ボタンやリンクを押下できないように制御する。処理状態をフラグとして保持しておき、処理中にボタンやリンクが押された場合に処理中であることを通知するメッセージを表示する。下記は、ボタンを非活性化した際のイメージとなる。Warning画面上に存在する全てのボタン及びリンクを無効化してしまうと、サーバからの応答がない場合に、画面操作が行えなくなってしまう。そのため、「前画面に戻る」や「トップ画面へ移動」などのイベントを実行するボタンやリンクは無効化しないようにすることを推奨する。更新処理を行うリクエスト(POSTメソッドによるリクエスト)に対する応答としてリダイレクトを返却し、その後ブラウザから自動的にリクエストされるGETメソッドの応答として遷移先の画面を返却するようにする。PRGパターンを適用することで、画面表示後にページの再読み込みを行った場合に発生するリクエストがGETメソッドになるため、更新処理の再実行を防ぐことが出来る。説明(1)購買者が、商品購入画面で注文ボタンをクリックする。リクエストは、POSTメソッドを使って送信される。(2)サーバは、(1)のリクエストで受けた商品の購入処理をDBに対して反映する。(3)サーバは、商品の購入完了画面を表示するためのURLに対するリダイレクト応答を行う。(4)ブラウザは、商品の購入完了画面を表示するためのURLにリクエストを送信する。リクエストは、GETメソッドを使って送信される。(5)サーバは、商品の購入完了画面を応答する。(6)購買者が、誤ってブラウザのリロード機能を実行する。更新処理が再実行されることはない。(7)サーバは、商品の購入完了画面を応答する。Note更新処理を伴う処理の場合は、PRGパターンを適用し、ブラウザの更新ボタンが押された際に、GETメソッドのリクエストが送信されるように制御することを推奨する。WarningPRGパターンでは、完了画面でブラウザの戻るボタンを押下することで、更新処理を再度実行されることを防ぐことはできない。ブラウザの戻るボタンを使った不正な画面遷移後の更新処理の再実行を防ぐ場合は、トランザクショントークンチェックを行う必要がある。トランザクショントークンチェックについて¶トランザクショントークンチェックは、サーバは、クライアントからリクエストが来た際に、サーバ上にトランザクションを一意に識別するための値(以下、トランザクショントークン)を保持する。サーバは、クライアントへトランザクショントークンを引き渡す。画面を提供するWebアプリケーションの場合は、formのhiddenタグを使用してクライアントにトランザクショントークンを引き渡す。クライアントは次のリクエストを送信する際に、サーバから引き渡されたトランザクショントークンを送る。サーバは、クライアントから受け取ったトランザクショントークンと、サーバ上で管理しているトランザクショントークンを比較する。いう、3つの処理で構成され、リクエストで送信されてきたトランザクショントークン値と、サーバ上で保持しているトランザクショントークン値が一致していない場合は、不正なリクエストとみなしてエラーを返す。Warningトランザクショントークンチェックの濫用は、アプリケーションのユーザビリティ低下につながるため、以下の点を考慮して、適用範囲を決めること。データの更新を伴わない参照系のリクエストや、単に画面遷移のみ行うリクエストについては、トランザクショントークンチェックの範囲に含める必要はない。必要以上にトランザクションの範囲を広げてしまうと、トランザクショントークンエラーが発生しやすくなるため、アプリケーションのユーザビリティを低下させる事になる。ビジネス観点で何回更新されても問題ないような処理(ユーザー情報更新など)では、トランザクショントークンチェックは必須ではない。入金処理や商品の購入処理など、処理が二重で実行されると問題がある場合は、トランザクショントークンチェックが必須である。以下に、トランザクショントークンチェック使用時において、想定通りの操作を行った場合の処理フローと、想定外の操作を行った場合の処理フローについて説明する。想定通りの操作を行った場合の処理フローについて説明する。説明(1)クライアントから、リクエストを送信する。(2)サーバは、トランザクショントークン(token001)を作成し、サーバ上で保持する。(3)サーバは、作成したトランザクショントークン(token001)を、クライアントに引き渡す。(4)クライアントから、トランザクショントークン(token001)を含めたリクエストを送信する。(5)サーバは、サーバ上で保持しているトランザクショントークン(token001)と、クライアントから送信されたトランザクショントークン(token001)が同一かチェックする。値が同一なので、正規のリクエストと判断される。(6)サーバは、次のリクエストで使用するトランザクショントークン(token002)を生成し、サーバ上で管理している値を更新する。この時点で、トランザクショントークン(token001)は破棄される。(7)サーバは、更新したトランザクショントークン(token002)を、クライアントに引き渡す。想定外の操作を行った場合の処理フローについて説明する。ここではブラウザの戻るボタンを例にしているが、ショートカットからの直接リクエストなどでも同様である。説明(8)クライアントでブラウザの戻るボタンをクリックする。(9)クライアントから戻った画面にあるトランザクショントークン(token001)を含めたリクエストを送信する。(10)サーバは、サーバ上に保持しているトランザクショントークン(token002)と、クライアントから送信されたトランザクショントークン(token001)が同一かチェックする。不正なリクエストと判断し、トランザクショントークンエラーとする。(11)サーバは、トランザクショントークンエラーが発生した事を通知するエラー画面を応答する。トランザクショントークンチェックで防ぐことが出来るのは、以下の3つの事象である。決められた画面遷移を行うことが求められる業務において、不正な画面遷移が行われる。正規の画面遷移を伴わない不正なリクエストによって、データが更新される。二重送信によって、更新処理が重複して実行される。以下のフローによって、決められた画面遷移を行うことが求められる業務において、不正な画面遷移が行われる事を防ぐ事ができる。説明(1)購買者が、商品購入画面で注文ボタンをクリックする。サーバ上で保持しているトランザクショントークンと、クライアントから送信されたトランザクショントークンが一致するため、商品を購入する処理を実行する。このタイミングで、サーバ上で保持していたトランザクショントークの値が破棄され、新しいトークン値に更新される。(2)サーバは、(1)のリクエストで受けた商品の購入処理をDBに対して反映する。(3)サーバは、(1)のリクエストで受けた商品の購入完了画面を応答する。(4)購買者が、ブラウザの戻るボタンを使って購入画面を再度表示する。(5)購買者が、ブラウザの戻るボタンを使って表示した購入画面で注文ボタンを再度クリックする。クライアントから送信されたトランザクショントークンは既に破棄された値のため、トランザクショントークンエラーとなる。(6)サーバは、トランザクショントークンエラーが発生した事を通知するエラー画面を応答する。以下のフローによって、正規の画面遷移を伴わない不正なリクエストでデータが更新される事を防ぐことができる。説明(1)攻撃者が、正規の画面遷移を行わずに、直接商品の購入を行う処理に対してリクエストを実行する。トランザクショントークンを生成するためのリクエストを実行していないため、トランザクショントークンエラーとなる。(2)サーバは、トランザクショントークンエラーが発生した事を通知するエラー画面を応答する。以下のフローによって、二重送信発生時に更新処理が重複して実行される事を防ぐことができる。説明(1)購買者が、商品購入画面で注文ボタンをクリックする。サーバ上で保持しているトランザクショントークンと、クライアントから送信されたトランザクショントークンが一致するため、商品を購入する処理を実行する。このタイミングで、サーバ上で保持していたトランザクショントークの値が破棄され、新しいトークン値に更新される。(2)(1)のレスポンスが返る前に、購買者が誤って注文ボタンをもう一度クリックする。クライアントから送信されたトランザクショントークンは既に破棄された値のため、トランザクショントークンエラーとなる。トランザクショントークンエラーが発生した事を通知するエラー画面を応答する。(4)サーバは、(1)のリクエストで受けた商品の購入処理をDBに対して反映する。(5)サーバは、(1)のリクエストで受けた商品の購入完了画面を応答しようとするが、(2)のリクエストが送信された事により、(1)のリクエストに対する応答を行うためのストリームが閉じられているため、購入完了画面を応答することができない。Warning二重送信発生時に更新処理が重複して実行される事は防ぐことが出来るが、処理が完了した事を通知する画面を応答することが出来ないという問題が残る。そのため、JavaScriptによるボタンの2度押し防止も合わせて対応することを推奨する。共通ライブラリから提供しているトランザクショントークンチェック機能では、トランザクショントークンを管理するための器にネームスペースを設けることが出来る。これは、タブブラウザや複数ウィンドウを使用して、更新処理を並行して操作できるようにするための仕組みである。ネームスペースがない場合の問題点について¶まず、ネームスペースがない場合の問題点について説明する。以下の図では、clientが左右にわかれているが、実際は同一マシン上に2つのWindowを立ち上げた際の例となる。説明(1)Window1からリクエストを送信し、応答されたトランザクショントークン(token001)をブラウザに保持する。サーバ上で保持しているトランザクショントークンはtoken001となる。(2)Window2からリクエストを送信し、応答されたトランザクショントークン(token002)をブラウザに保持する。サーバ上で保持しているトランザクショントークンはtoken002となる。このタイミングで(1)の処理で生成されたトランザクショントークン(token001)は破棄される。(3)Window1からブラウザで保持しているトランザクショントークン(token001)を含めてリクエストを送信する。サーバ上で保持しているトランザクショントークン(token002)と、リクエストで送信されたトランザクショントークン(token002)が一致しないため、不正なリクエストと判断され、トランザクショントークンエラーとなる。Warningネームスペースがない場合は、更新処理を並行して操作することができないため、ユーザビリティの低いアプリケーションとなってしまう。次に、ネームスペースを付与した際の動作について説明する。ネームスペースがない場合は、更新処理を並行して操作することができないという問題があったが、ネームスペースも設けることで、この問題を解決することが出来る。以下の図では、clientが左右にわかれているが、実際は同一マシン上に2つのWindowを立ち上げた際の例となる。222の部分が、ネームスペースとなる。ネームスペースを与えることで、トランザクションに割り振られたネームスペース内に存在するトランザクショントークンのみを操作するため、別のネームスペースのトランザクションに対して影響を与えない。ここでは、ブラウザを別のWindowで記述しているが、タブブラウザでも同じである。生成されるキーや使用方法については、トランザクショントークンチェックの適用で説明する。JavaScriptによるボタンの2度押し防止の適用¶クライアントでのボタンの二重クリック防止は、JavaScriptで実現することになる。ボタンをクリックした後は、再描画するまでクリックできないようにする。TodoTBDJavaScriptでのチェック方法については、次版以降で詳細化する予定である。PRG(Post-Redirect-Get)パターンの適用¶PRG(Post-Redirect-Get)パターンを適用する場合の実装例について説明する。いうシンプルな画面遷移を行うアプリケーションを例に説明する。画像の番号と、ソースのコメント番号を連動させている。(1)~(4)については、PRGパターンと直接関係ないため、説明は省略する。Userボタン)が押下時の処理を行うハンドラメソッド。POSTメソッドでリクエストを受け取る。(6)完了画面を表示するためのURLへリダイレクトする。RedirectAttributesのaddFlashAttributeメソッドを呼び出し、引き渡すデータを追加する。ModelのaddAttributeメソッドは、リダイレクト先にデータを引き渡すことはできない。(7)完了画面を表示するためのハンドラメソッド。GETメソッドでリクエストを受け取る。(8)完了画面を表示するView(JSP)を呼び出し、完了画面を応答する。ViewResolverによって付与されるため、ハンドラメソッドの返却値からは省略している。Noteリダイレクトする際は、ハンドラメソッドの返り値として返却する遷移情報のプレフィックスとして「redirect:」を付与する。リダイレクト先の処理にデータを引き渡したい場合は、RedirectAttributesのaddFlashAttributeメソッドを呼び出し、引き渡すデータを追加する。POSTメソッドでリクエスト送る。addFlashAttributeメソッドで追加したデータの属性名を指定する。"output"が引き渡したデータを参照するための属性名となる。トランザクショントークンチェックの適用¶トランザクショントークンチェックを適用する場合の実装例について説明する。MVCから提供されている機能ではなく、共通ライブラリから提供している機能となる。@org.terasoluna.gfw.web.token.transaction.TransactionTokenCheckアノテーションを提供している。宣言的にトランザクショントークンチェックを行うことが出来る。@TransactionTokenCheckアノテーションの属性について¶@TransactionTokenCheckアノテーションに指定できる属性について説明する。default例value任意文字列。NameSpaceとして使用される。namespace任意文字列。NameSpaceとして使用される。value属性のエイリアスである。"create"と同義である。value属性が使用できないため、value属性の代わりとして利用する。typeBEGINトランザクショントークンを作成し、新たなトランザクションを開始する。INトランザクショントークンの妥当性チェックを実施する。リクエストされたトークン値とサーバ上で管理しているトークン値が一致している場合は、トランザクショントークンのトークン値を更新する。TransactionTokenType.INNotevalue属性またはnamespace属性に設定する値は、@RequestMappingアノテーションのvalue属性の設定値と、同じ値を設定することを推奨する。指定することが出来るが、通常使用することはないため、説明は省略する。(1)NameSpaceNameSpaceは、一連の画面遷移を識別するための論理的な名称を付与するための要素となる。NameSpaceを設けることで、異なるNameSpaceに属するリクエストが干渉しあう事を防ぐ事が出来るため、並行して操作を行うことができる画面遷移を増やすことが出来る。NameSpaceとして使用する値は、@TransactionTokenCheckアノテーションのvalue属性で指定した値が使用される。両方の値を"/"で連結した値がNameSpaceとなる。複数のメソッドで同じ値を指定した場合は、同じNameSpaceに属するメソッドとなる。クラスアノテーションにのみvalue属性を指定した場合は、そのクラスで生成されるトランザクショントークンのNameSpaceは、全てクラスアノテーションで指定した値となる。メソッドアノテーションにのみvalue属性を指定した場合は、生成されるトランザクショントークンのNameSpaceはメソッドアノテーションで指定した値となる。複数のメソッドで同じ値を指定した場合は、同じNameSpaceに属するメソッドとなる。クラスアノテーションのvalue属性とメソッドアノテーションのvalue属性の両方を省略した場合は、グローバルトークンに属するメソッドとなる。グローバルトークンについては、グローバルトークンを参照されたい。(2)TokenKeyTokenKeyは、ネームスペース内で管理されているトランザクションを識別するための要素となる。TokenKeyは、@TransactionTokenCheckアノテーションのtype属性にTransactionTokenType.BEGINが宣言されているメソッドが実行されたタイミングで生成れる。複数のTokenKeyを同時に保持することが出来る数には上限数があり、デフォルト10である。TokenKeyの保持数はNameSpace毎に管理される。(LRU))、新しいトランザクションを有効なトランザクションとして管理する仕組みとなっている。破棄されたトランザクショントークンを使ってアクセスした場合は、トランザクショントークンエラーとなる。(3)TokenValueTokenValueは、トランザクションのトークン値を保持するための要素となる。TokenValueは、@TransactionTokenCheckアノテーションのtype属性にTransactionTokenType.BEGIN又はTransactionTokenType.INが宣言されているメソッドが実行されたタイミングで生成される。Warningメソッドアノテーションにのみvalue属性を指定した場合、他のControllerで同じ値を指定している場合に、一連の画面遷移を行うためのリクエストとして扱われる点に注意する必要がある。この方法での指定は、Controllerを跨いだ画面遷移を同一トランザクションとして扱いたい場合にのみ、使用すること。原則的には、メソッドアノテーションにのみvalue属性を指定する方法は使用しない事を推奨する。NoteNameSpaceの指定方法として、クラスアノテーションのvalue属性とメソッドアノテーションのvalue属性の両方を指定する場合クラスアノテーションにのみvalue属性を指定する場合の使い分けについては、Controllerの作成粒度に応じて使い分ける。Controllerに、複数のユースケースに対応するハンドラメソッドを実装する場合は、クラスアノテーションのvalue属性とメソッドアノテーションのvalue属性の両方を指定する。ユーザの登録、変更、削除を一つのControllerで実装する場合は、このパターンとなる。Controllerに、一つのユースケースに対応するハンドラメソッドを実装する場合は、クラスアノテーションにのみvalue属性を指定する。Controllerを実装する場合は、このパターンとなる。トランザクショントークンのライフサイクルについて¶トランザクショントークンのライフサイクル(生成、更新、破棄)制御は、以下のタイミングで行われる。説明(1)トークンの生成@TransactionTokenCheckアノテーションのtype属性にTransactionTokenType.BEGINが指定されたメソッドの処理が終了したタイミングで新たなトークンが生成され、トランザクションが開始される。(2)トークンの更新@TransactionTokenCheckアノテーションのtype属性にTransactionTokenType.INが指定されたメソッドの処理が終了したタイミングでトークン(TokenValue)が更新され、トランザクションが継続される。以下の何れかのタイミングで破棄され、トランザクションが終了される。[1]@TransactionTokenCheckアノテーションのtype属性にTransactionTokenType.BEGINが指定されたメソッドを呼び出すタイミングで、リクエストパラメータに指定されているトランザクショントークンが破棄され、不要なトランザクションが終了される。[2]NameSpace内で保持することが出来るトランザクショントークン(TokenKey)の数が上限数に達している状態で、新たにトランザクションが開始される場合、実行された日時が最も古いトランザクショントークンが破棄され、トランザクションが強制終了される。[3]システムエラーなどの例外が発生した場合、リクエストパラメータに指定されているトランザクショントークンが破棄され、トランザクションを終了される。新しいトランザクションを有効なトランザクションとして管理する仕組みとなっている。NameSpaceごとに保持できるトランザクショントークンの上限数のデフォルト10個である。上限値を変更する場合は、トランザクショントークンの上限数の変更方法についてを参照されたい。以下に、新たにトランザクショントークンを生成する際に上限値に達していた場合の動作について説明する。前提条件は以下の通りとする。NameSpace内で保持することが出来るトランザクショントークンの数には上限数は、デフォルト値(10)が指定されている。@TransactionTokenCheck("name")が指定されている。同じNameSpaceのトランザクショントークンが上限値に達している状態である。説明(1)同じNameSpaceのトランザクショントークンが上限値に達している状態で、新たなトランザクションを開始するリクエストを受け付ける。(2)新たにトランザクショントークンを生成する。(3)生成したトランザクショントークンをトークン格納先に追加する。この時点で上限数を超えるトランザクショントークンがNameSpace内に存在する状態となる。(4)NameSpace内で保持することが出来るトランザクショントークンの数には上限数を超える分のトランザクショントークンを削除する。トランザクショントークンを削除する際は、実行された日時が最も古いものから順に削除する。共通ライブラリから提供しているトランザクショントークンチェックを使用するための設定を、以下に示す。HandlerInterceptorを設定する。(2)HandlerInterceptorを適用するリクエストパスを指定する。/resources配下へのリクエストとHTMLへのリクエストを除く、全てのリクエストに対して適用している。(3)@TransactionTokenCheckアノテーションを使用して、トランザクショントークンの生成及びチェックを実施するためのクラス(TransactionTokenInterceptor)を指定する。MVCの<fomr:form>タグを使用してHidden領域に自動的に埋め込むためのクラス(TransactionTokenRequestDataValueProcessor)を設定する。InvalidTransactionTokenExceptionのハンドリング定義を追加する必要がある。共通設定アプリケーション層の設定を参照されたい。トランザクショントークンチェックのControllerでの利用方法¶トランザクショントークンチェックを行う場合、Controllerではトランザクションを開始するメソッドの定義、チェックを行うメソッドの定義が必要となる。以下では、1つのcontrollerで、1つのユースケースで必要となるハンドラメソッドを実装する場合の説明となる。説明(1)クラスアノテーションのvalue属性でNameSpaceを指定する。@RequestMappingのvalue属性と同じ値を指定している。(2)トランザクションを開始し、新しいトランザクショントークンを払い出す。ここでは、Controller単位でトランザクショントークンを管理するため、メソッドアノテーションのvalue属性を指定しない。(3)トランザクショントークンをチェックし、トランザクショントークンのトークン値を更新する。TransactionTokenType.IN)を指定した時と同じ動作となる。(4)ユースケースの完了を通知する画面を表示するためのリクエストでは、トランザクショントークンチェックを行う必要はないため@TransactionTokenCheckアノテーションの指定は行っていない。Note@TransactionTokenCheckアノテーションのtype属性にBEGINを指定した場合は、新しくTokenKeyが生成されるため、トランザクショントークンのチェックは行われない。@TransactionTokenCheckアノテーションのtype属性にINが指定された場合は、リクエストで指定されたトークン値とサーバ上で保持しているトークン値が同一のものがあるかをチェックする。トランザクショントークンチェックのView(JSP)での利用方法¶トランザクショントークンチェックを行う場合、払い出されたトランザクショントークンが、リクエストパラメータとして送信されるようにView(JSP)を実装する必要がある。リクエストパラメータとして送信されるようにする方法としては、トランザクショントークンチェックを使用するための設定を行った上で、<form:form>タグを使して自動的にトランザクショントークンをhidden要素に埋め込む方法を推奨する。説明(1)JSPで、<form:form>タグを使用した場合は、@TransactionTokenCheckアノテーションのtype属性にBEGINかINを指定すると、name="_TRANSACTION_TOKEN"に対するValueが、hiddenタグとして自動的に埋め込まれる。使用することで、(1)と同様のhiddenタグが埋め込まれる。Securityから提供されているCSRFトークンチェックで必要となるcsrfトークンをhidden項目として埋め込む必要がある。MVCを使用した連携を参照されたい。Note<form:form>タグでを使用すると、CSRFトークンチェックで必要となるパラメータも自動的に埋め込まれる。CSRFトークンチェックで必要となるパラメータについては、HTMLフォーム使用時の連携を参照されたい。/>は、共通ライブラリから提供しているJSPタグライブラリである。(2)で使用している「t:」については、インクルード用の共通JSPの作成を参照されたい。出力されたHTMLを確認すると、NameSpaceは、クラスアノテーションのvalue属性で指定した値が設定される。"transactionTokenCheckExample"(橙色の下線)がNameSpaceとなる。TokenKeyは、トランザクション開始時に払い出された値が引き回されて設定される。"c0123252d531d7baf730cd49fe0422ef"(青色の下線)がTokenKeyとなる。TokenValueは、リクエスト毎に値が変化している。"3f610684e1cb546a13b79b9df30a7523"、"da770ed81dbca9a694b232e84247a13b"、"bd5a2d88ec446b27c06f6d4f486d4428"(緑色の下線)がTokenValueとなる。1つのController内で複数のユースケースを実施する場合¶1つのController内で複数のユースケースの処理を実装する場合のトランザクショントークンチェックの実装例を以下に示す。下記の例では、(2),(3),(4)を別々のユースケースの画面遷移として扱っている。説明(1)クラスアノテーションのvalue属性でNameSpaceを指定する。@RequestMappingのvalue属性と同じ値を指定している。(2)"flowOne"という名前を持つユースケースの処理に対して、トランザクショントークンチェックを行う。@RequestMappingのvalue属性と同じ値を指定している。(3)"flowTwo"という名前を持つユースケースの処理に対して、トランザクショントークンチェックを行う。@RequestMappingのvalue属性と同じ値を指定している。(4)"flowThree"という名前を持つユースケースの処理に対して、トランザクショントークンチェックを行う。@RequestMappingのvalue属性と同じ値を指定している。Noteユースケース毎にNameSpaceを割り振ることで、各ユースケース毎にトランザクショントークンのチェックを行うことが出来る。完了画面」といったシンプルな画面遷移を行うユースケースに対して、トランザクショントークンチェックを適用する際の実装例を以下に示す。説明(1)クラスアノテーションとして、"user"というNameSpaceを設定している。上記例では、推奨パターンの@RequestMappingアノテーションのvalue属性と同じ値を指定している。(2)入力画面の表示するためのハンドラメソッド。ユースケースを開始するための画面ではあるが、データの更新を伴わない表示のみの処理であるため、トランザクションを開始する必要はない。@TransactionTokenCheckアノテーションを指定していない。(3)入力チェックを行い、確認画面を表示するためのハンドラメソッド。確認画面には更新処理を実行するためのボタンが配置されているため、このタイミングでトランザクションを開始する。遷移先には、View(JSP)を指定する。(4)更新処理を実行するためのハンドラメソッド。更新処理を行うメソッドなので、トランザクショントークンのチェックを行う。(4)完了画面を表示するためのハンドラメソッド。完了画面を表示するだけなので、トランザクショントークンのチェックは不要である。@TransactionTokenCheckアノテーションを指定していない。Warning@TransactionTokenCheckアノテーションを定義したハンドラメソッドの遷移先は、View(JSP)を指定する必要がある。リダイレクト先などのView(JSP)以外を遷移先に指定すると、次の処理でTransactionTokenの値が変わっており、必ずTransactionTokenエラーが発生する。同じ処理の画面遷移を複数並行して行うと、互いの画面操作が干渉しあい、画面に表示されている値とセッション上で保持している値が一致しなくなってしまう事がある。ような不整合な状態になっている画面からのリクエストを不正なリクエストとして防ぐ方法として、トランザクショントークンチェック機能を使用することができる。NameSpaceごとに保持できるトランザクショントークンの上限数を1を設定する。説明(1)NameSpaceごとのトランザクショントークンの保持数を、”1”に設定する。古いデータを表示している画面からのリクエストを不正なリクエストとして防ぐことが可能となる。以下の設定を追加することで、Controllerのハンドラメソッドの引数としてorg.terasoluna.gfw.web.token.transaction.TransactionTokenContextを受け取り、プログラマティックにトランザクショントークンのライフサイクルを管理することができる。説明(1)<mvc:argument-resolvers>要素に、Controllerのメソッドの引数として、プログラマティックにトランザクショントークンのライフサイクルを管理するためのオブジェクト(TransactionTokenContext)を引き渡すためのクラス(TransactionTokenContextHandlerMethodArgumentResolver)を設定をする。プログラマティックにトランザクショントークンのライフサイクルを管理する必要がない場合は、本設定は不要である。Note使用されなくなったトランザクショントークンは、1つのNameSpaceで保持することが出来る上限値を超えた時点で自動的に破棄されていくため、基本的には、本設定は不要である。以下の設定を行うことで、1つのNameSpace上で保持する事ができるトランザクショントークンの上限数を変更することができる。説明(1)TransactionTokenInterceptorのコンストラクタの値として、1つのNameSpace上で保持する事ができるトランザクショントークンの上限数を指定する。デフォルト値(デフォルトコンストラクタ使用時に設定される値)は、10となっている。デフォルト値(10)から5に変更している。トランザクショントークンエラーが発生する前にWebブラウザの有効期限切れメッセージが表示される。具体的には(8)のブラウザの戻るボタンをクリックすると以下の画面が表示される。11を使用した場合である。この場合でも二重送信自体は防止されているため、問題はない。Securityの機能でキャッシュが無効になる設定が行われている。/>を設定しておくことを推奨する。グローバルトークン¶@TransactionTokenCheckアノテーションのvalue属性の指定を省略すると、グローバルなトランザクショントークンとして扱われる。グローバルなトランザクショントークンのNameSpaceには、"globalToken"(固定値)が使用される。Noteアプリケーション全体として、単一の画面遷移のみを許容する場合は、NameSpaceごとに保持できるトランザクショントークンの上限数を1に設定し、グルーバルトークンを使用することで実現することが出来る。アプリケーション全体として、単一の画面遷移のみを許容する場合場合の設定及び実装例を以下に示す。NameSpaceごとに保持できるトランザクショントークンの上限数の変更¶NameSpaceごとに保持できるトランザクショントークンの上限数を1を設定する。説明(1)NameSpaceごとのトランザクショントークンの保持数を、”1”に設定する。Controllerの実装¶グルーバルトークン用のNameSpaceとなるようにするために、@TransactionTokenCheckアノテーションのvalue属性には、値を指定しない。説明(1)クラスアノテーションとして、@TransactionTokenCheckアノテーションを指定しない。@TransactionTokenCheckアノテーションのvalue属性を指定しない。HTMLの出力例JSPは、トランザクショントークンチェックのView(JSP)での利用方法で用意したJSPと同等のものを用意する。actionを、"transactionTokenCheckExample"から"globalTokenCheckExample"に変更したのみで、他は同じである。出力されたHTMLを確認すると、NameSpaceは、"globalToken"という固定値が設定される。TokenKeyは、トランザクション開始時に払い出された値が引き回されて設定される。"9d937be4adc2f5dd2032292d153f1133"(青色の下線)がTokenKeyとなる。TokenValueは、リクエスト毎に値が変化している。"9204d7705ce7a17f16ca6cec24cfd88b"、"69c809fefcad541dbd00bd1983af2148"、"6b83f33b365f1270ee1c1b263f046719"(緑色の下線)がTokenValueとなる。以下に、NameSpaceごとのトランザクショントークンの上限数を1に設定して、グローバルトークンを使用した場合の動作について説明する。説明(1)window1の処理にて、TransactionTokenType.BEGINを行い、グローバルトークンを生成する。(2)window2の処理にて、TransactionTokenType.BEGINでtokenを更新する。内部的に更新ではなく入れ替えとなるが、サーバ上保持できるトランザクショントークンは1つなので、トークンが更新されるイメージとなる。(3)window1の処理のTransactionTokenType.INにて、tokenの値をチェックする。1の処理で生成したトランザクショントークンをリクエストパラメータとして送信するが、サーバ上に指定したトークンが存在しないため、トランザクショントークンエラーとなる。(4)window2の処理のTransactionTokenType.INにて、tokenの値をチェックする。2の処理で生成したトランザクショントークンをリクエストパラメータとして送信し、サーバ上で保持しているトークン値と一致することをチェックする。一致している場合は、処理が継続される。