二重送信防止 ================================================================================ .. only:: html .. contents:: 目次 :depth: 4 :local: Overview -------------------------------------------------------------------------------- Problems ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 画面を提供するWebアプリケーションでは、以下の操作が行われると、同じ処理が複数回実行されてしまうことがある。 .. tabularcolumns:: |p{0.10\linewidth}|p{0.30\linewidth}|p{0.60\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 30 60 * - 項番 - 操作 - 操作概要 * - | (1) - | 更新系ボタンの二重クリック - | 更新処理を行うボタンを連続してクリックする。 * - | (2) - | 更新処理完了後の画面の再読み込み - | ブラウザの更新ボタンを使用することで、更新処理完了後の画面の再読み込みを行う。 * - | (3) - | ブラウザの戻るボタンを使用した不正な画面遷移 - | 更新処理の完了画面からブラウザの戻るボタンを使用してページを戻し、更新処理を行うボタンを再度クリックする。 それぞれ具体的な問題点を、以下に示す。 更新系ボタンの二重クリック """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | 更新処理を行うボタンを連続してクリックすると、以下のような問題が発生する。 | 以下では、ショッピングサイトの商品購入を例として、対策を行わない場合にどのような問題が発生するのかを説明する。 .. figure:: ./images/duplicate-double-click.png :alt: duplicate double click :width: 100% .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 購買者が、商品購入画面で注文ボタンをクリックする。 * - | (2) - | (1)のレスポンスが返る前に、購買者が誤って注文ボタンをもう一度クリックする。 * - | (3) - | サーバは、(1)のリクエストで受けた商品の購入処理をDBに対して反映する。 * - | (4) - | サーバは、(2)のリクエストで受けた商品の購入処理をDBに対して反映する。 * - | (5) - | サーバは、(2)のリクエストで受けた商品の購入完了画面を応答する。 .. warning:: 上記のケースでは、購入者が誤って注文ボタンを押下することで、**まったく同じ商品の購入が2回行われてしまうことになる。** 購入者の操作ミスが原因ではあるが、アプリケーションとして上記の問題が発生しないように制御する事が望ましい。 更新処理完了後の画面の再読み込み """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | 更新処理完了後の画面の再読み込みを行うと、以下のような問題が発生する。 | 以下では、ショッピングサイトの商品購入を例として、対策を行わない場合にどのような問題が発生するのかを説明する。 .. figure:: ./images/duplicate-reload.png :alt: duplicate reload :width: 100% .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 購買者が、商品購入画面で注文ボタンをクリックする。 * - | (2) - | サーバは、(1)のリクエストで受けた商品の購入処理をDBに対して反映する。 * - | (3) - | サーバは、(1)のリクエストで受けた商品の購入完了画面を応答する。 * - | (4) - | 購買者が、誤ってブラウザのリロード機能を実行する。 * - | (5) - | サーバは、(4)のリクエストで受けた商品の購入処理をDBに対して反映する。 * - | (6) - | サーバは、(4)のリクエストで受けた商品の購入完了画面を応答する。 .. warning:: 上記のケースでは、購入者が誤ってブラウザのリロード機能を実行することで、**まったく同じ商品の購入が2回行われてしまうことになる。** 購入者の操作ミスが原因ではあるが、アプリケーションとして上記の問題が発生しないように制御する事が望ましい。 ブラウザの戻るボタンを使用した不正な画面遷移 """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | ブラウザの戻るボタンを使用した不正な画面遷移を行うと、以下のような問題が発生する。 | 以下では、ショッピングサイトの商品購入を例として、対策を行わない場合にどのような問題が発生するのかを説明する。 .. figure:: ./images/duplicate-invalid-screenflow.png :alt: duplicate invalid screen flow :width: 100% .. raw:: latex \newpage .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 :class: longtable * - 項番 - 説明 * - | (1) - | 購買者が、商品購入画面で注文ボタンをクリックする。 * - | (2) - | サーバは、(1)のリクエストで受けた商品の購入処理をDBに対して反映する。 * - | (3) - | サーバは、(1)のリクエストで受けた商品の購入完了画面を応答する。 * - | (4) - | 購買者が、ブラウザの戻るボタンを使って購入画面を再度表示する。 * - | (5) - | 購買者が、ブラウザの戻るボタンを使って表示した購入画面で注文ボタンを再度クリックする。 * - | (6) - | サーバは、(5)のリクエストで受けた商品の購入処理をDBに対して反映する。 * - | (7) - | サーバは、(5)のリクエストで受けた商品の購入完了画面を応答する。 .. raw:: latex \newpage .. note:: 上記のケースでは、購入者の操作ミスではないため、購入者に対して問題が発生することはない。 | ただし、不正な画面操作を行った後でも更新処理が実行できてしまうと、以下のような問題が発生する。 .. figure:: ./images/duplicate-allow-malicious-request.png :alt: duplicate allow a malicious request :width: 100% .. warning:: 上記のケースのように、不正な画面操作を行った後でも更新処理が実行できてしまうと、悪意のある攻撃者によって、正規のルートを経由せずに直接更新処理が実行される危険度が高まる。 .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 :class: longtable * - 項番 - 説明 * - | (1) - | 攻撃者が、正規の画面遷移を行わずに、直接商品の購入を行う処理に対してリクエストを実行する。 * - | (2) - | サーバは、不正なルートでリクエストが行われていることを検知することができないため、リクエストで受けた商品の購入処理をDBに対して反映してしまう。 不正なリクエストによって購入処理を実行することで、各サーバの負荷が高くなったり、正規のルートで商品が購入できなくなるなどの問題が発生してしまう。 結果的に、正規のルートで購入している利用者に対して問題が波及する事になるため、アプリケーションとして上記の問題が発生しないように制御する事が望ましい。 Solutions ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | 上記の問題を解決する方法として、下記の対策が必要になる。 | リクエストの改竄など悪意あるオペレーションを考慮すると、 **(3)の「トランザクショントークンチェックの適用」は必須である。** .. tabularcolumns:: |p{0.10\linewidth}|p{0.20\linewidth}|p{0.70\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 20 70 * - 項番 - Solution - 概要 * - | (1) - | JavaScriptによるボタンの2度押し防止 - | 更新処理を行うボタンを押下した際に、JavaScriptによるボタン制御を行うことで、2度押しされた際にリクエストが送信されないようにする。 * - | (2) - | PRG(Post-Redirect-Get)パターンの適用 - | 更新処理を行うリクエスト(POSTメソッドによるリクエスト)に対する応答としてリダイレクトを返却し、その後ブラウザから自動的にリクエストされるGETメソッドの応答として遷移先の画面を返却するようにする。 | PRGパターンを適用することで、画面表示後にページの再読み込みを行った場合に発生するリクエストがGETメソッドになるため、更新処理の再実行を防ぐことが出来る。 * - | (3) - | トランザクショントークンチェックの適用 - | 画面遷移毎にトークン値を払い出し、ブラウザから送信されたトークン値とサーバ上で保持しているトークン値を比較することで、トランザクション内で不正な画面操作が行われないようにする。 | トランザクショントークンチェックを適用することで、ブラウザの戻るボタンを使ってページを移動した後の更新処理の再実行を防ぐことが出来る。 | また、トークン値のチェックを行った後にサーバで管理しているトークン値を破棄することで、サーバ側の処理として二重送信を防ぐことも出来る。 .. note:: 「トランザクショントークンチェックの適用」のみの対策だと、単純な操作ミスを行った場合でもトランザクショントークンエラーとなるため、利用者に対してユーザビリティの低いアプリケーションになってしまう。 ユーザビリティを確保しつつ、二重送信で発生する問題を防止するためには、「JavaScriptによるボタンの2度押し防止」及び「PRG(Post-Redirect-Get)パターンの適用」が必要となる。 **本ガイドラインでは、全ての対策を行うことを推奨するが、アプリケーションの要件によって対策の有無は判断すること。** .. Warning:: AjaxとWebサービスでは、リクエスト毎に変更されるトランザクショントークンの受け渡しを行いにくいため、トランザクショントークンチェックを使用しなくてよい。 Ajaxの場合は、JavaScriptによるボタンの2度押し防止のみで二重送信防止を行う。 JavaScriptによるボタンの2度押し防止について """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | 更新処理を行うボタンや、時間のかかる検索処理を行うボタンなどに対して、ボタンの二重クリックを防止する。 | ボタンが押された際に、JavaScriptを使用してボタンやリンクの無効化の制御を行う。 | 無効化するための代表的な制御例としては、 #. ボタンやリンクを非活性化することで、ボタンやリンクを押下できないように制御する。 #. 処理状態をフラグとして保持しておき、処理中にボタンやリンクが押された場合に処理中であることを通知するメッセージを表示する。 | などがあげられる。 下記は、ボタンを非活性化した際のイメージとなる。 .. figure:: ./images/prevent-double-click.png :alt: prevent double click :width: 60% .. warning:: 画面上に存在する全てのボタン及びリンクを無効化してしまうと、サーバからの応答がない場合に、画面操作が行えなくなってしまう。 そのため、「前画面に戻る」や「トップ画面へ移動」などのイベントを実行するボタンやリンクは無効化しないようにすることを推奨する。 .. _DoubleSubmitProtectionAboutPRG: PRG(Post-Redirect-Get)パターンについて """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | 更新処理を行うリクエスト(POSTメソッドによるリクエスト)に対する応答としてリダイレクトを返却し、その後ブラウザから自動的にリクエストされるGETメソッドの応答として遷移先の画面を返却するようにする。 | PRGパターンを適用することで、画面表示後にページの再読み込みを行った場合に発生するリクエストがGETメソッドになるため、更新処理の再実行を防ぐことが出来る。 .. figure:: ./images/prevent-double-submit-reload.png :alt: prevent double submit by reload :width: 100% .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 購買者が、商品購入画面で注文ボタンをクリックする。 | **リクエストは、POSTメソッドを使って送信される。** * - | (2) - | サーバは、(1)のリクエストで受けた商品の購入処理をDBに対して反映する。 * - | (3) - | **サーバは、商品の購入完了画面を表示するためのURLに対するリダイレクト応答を行う。** * - | (4) - | ブラウザは、商品の購入完了画面を表示するためのURLにリクエストを送信する。 | **リクエストは、GETメソッドを使って送信される。** * - | (5) - | サーバは、商品の購入完了画面を応答する。 * - | (6) - | 購買者が、誤ってブラウザのリロード機能を実行する。 | リロード機能によって要求されるリクエストは、商品の購入完了画面を表示するためのリクエストとなるため、 **更新処理が再実行されることはない。** * - | (7) - | サーバは、商品の購入完了画面を応答する。 .. note:: 更新処理を伴う処理の場合は、\ :abbr:`PRG (Post-Redirect-Get)`\ パターンを適用し、ブラウザの更新ボタンが押された際に、GETメソッドのリクエストが送信されるように制御することを推奨する。 .. warning:: \ :abbr:`PRG (Post-Redirect-Get)`\ パターンでは、完了画面でブラウザの戻るボタンを押下することで、更新処理を再度実行されることを防ぐことはできない。 ブラウザの戻るボタンを使用して不正な遷移をした画面から、更新処理の再実行を防ぐ場合は、トランザクショントークンチェックを行う必要がある。 .. _double-submit_transactiontokencheck: トランザクショントークンチェックについて """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" トランザクショントークンチェックは、 * サーバは、クライアントからリクエストが来た際に、サーバ上にトランザクションを一意に識別するための値(以下、トランザクショントークン)を保持する。 * サーバは、クライアントへトランザクショントークンを引き渡す。画面を提供するWebアプリケーションの場合は、formのhiddenタグを使用してクライアントにトランザクショントークンを引き渡す。 * クライアントは次のリクエストを送信する際に、サーバから引き渡されたトランザクショントークンを送る。サーバは、クライアントから受け取ったトランザクショントークンと、サーバ上で管理しているトランザクショントークンを比較する。 という、3つの処理で構成され、リクエストで送信されてきたトランザクショントークン値と、サーバ上で保持しているトランザクショントークン値が一致していない場合は、不正なリクエストとみなしてエラーを返す。 .. warning:: トランザクショントークンチェックの濫用は、アプリケーションのユーザビリティ低下につながるため、以下の点を考慮して、適用範囲を決めること。 * | データの更新を伴わない参照系のリクエストや、単に画面遷移のみ行うリクエストについては、トランザクショントークンチェックの範囲に含める必要はない。 | 必要以上にトランザクションの範囲を広げてしまうと、トランザクショントークンエラーが発生しやすくなるため、アプリケーションのユーザビリティを低下させる事になる。 * | ビジネス観点で何回更新されても問題ないような処理(ユーザー情報更新など)では、トランザクショントークンチェックは必須ではない。 * | 入金処理や商品の購入処理など、処理が二重で実行されると問題がある場合は、トランザクショントークンチェックが必須である。 | 以下に、トランザクショントークンチェック使用時において、想定通りの操作を行った場合の処理フローと、想定外の操作を行った場合の処理フローについて説明する。 .. figure:: ./images/transaction-token-check-overview.png :alt: transaction token overview :width: 100% | 想定通りの操作を行った場合の処理フローについて説明する。 .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 :class: longtable * - 項番 - 説明 * - | (1) - | クライアントから、リクエストを送信する。 * - | (2) - | サーバは、トランザクショントークン(token001)を作成し、サーバ上で保持する。 * - | (3) - | サーバは、作成したトランザクショントークン(token001)を、クライアントに引き渡す。 * - | (4) - | クライアントから、トランザクショントークン(token001)を含めたリクエストを送信する。 * - | (5) - | サーバは、サーバ上で保持しているトランザクショントークン(token001)と、クライアントから送信されたトランザクショントークン(token001)が同一かチェックする。 | **値が同一なので、正規のリクエストと判断される。** * - | (6) - | サーバは、次のリクエストで使用するトランザクショントークン(token002)を生成し、サーバ上で管理している値を更新する。 | この時点で、トランザクショントークン(token001)は破棄される。 * - | (7) - | サーバは、更新したトランザクショントークン(token002)を、クライアントに引き渡す。 .. raw:: latex \newpage | 想定外の操作を行った場合の処理フローについて説明する。 | ここではブラウザの戻るボタンを例にしているが、ショートカットからの直接リクエストなどでも同様である。 .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (8) - | クライアントでブラウザの戻るボタンをクリックする。 * - | (9) - | クライアントから戻った画面にあるトランザクショントークン(token001)を含めたリクエストを送信する。 * - | (10) - | サーバは、サーバ上に保持しているトランザクショントークン(token002)と、クライアントから送信されたトランザクショントークン(token001)が同一かチェックする。 | **値が同一ではないので、 不正なリクエストと判断し、トランザクショントークンエラーとする。** * - | (11) - | サーバは、トランザクショントークンエラーが発生した事を通知するエラー画面を応答する。 | トランザクショントークンチェックで防ぐことが出来るのは、以下の3つの事象である。 * 決められた画面遷移を行うことが求められる業務において、不正な画面遷移が行われる。 * 正規の画面遷移を伴わない不正なリクエストによって、データが更新される。 * 二重送信によって、更新処理が重複して実行される。 | 以下のフローによって、決められた画面遷移を行うことが求められる業務において、不正な画面遷移が行われる事を防ぐ事ができる。 .. figure:: ./images/transaction-token-check-prevent-invalid-screenflow.png :alt: prevent invalid screen flow by transaction token check :width: 100% .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 :class: longtable * - 項番 - 説明 * - | (1) - | 購買者が、商品購入画面で注文ボタンをクリックする。 | サーバ上で保持しているトランザクショントークンと、クライアントから送信されたトランザクショントークンが一致するため、商品を購入する処理を実行する。 | **このタイミングで、サーバ上で保持していたトランザクショントークの値が破棄され、新しいトークン値に更新される。** * - | (2) - | サーバは、(1)のリクエストで受けた商品の購入処理をDBに対して反映する。 * - | (3) - | サーバは、(1)のリクエストで受けた商品の購入完了画面を応答する。 * - | (4) - | 購買者が、ブラウザの戻るボタンを使って購入画面を再度表示する。 * - | (5) - | 購買者が、ブラウザの戻るボタンを使って表示した購入画面で注文ボタンを再度クリックする。 | **クライアントから送信されたトランザクショントークンは既に破棄された値のため、トランザクショントークンエラーとなる。** * - | (6) - | サーバは、トランザクショントークンエラーが発生した事を通知するエラー画面を応答する。 .. raw:: latex \newpage | 以下のフローによって、正規の画面遷移を伴わない不正なリクエストでデータが更新される事を防ぐことができる。 .. figure:: ./images/transaction-token-check-prevent-malicious-request.png :alt: prevent malicious request by transaction token check :width: 100% .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 攻撃者が、正規の画面遷移を行わずに、直接商品の購入を行う処理に対してリクエストを実行する。 | **トランザクショントークンを生成するためのリクエストを実行していないため、トランザクショントークンエラーとなる。** * - | (2) - | サーバは、トランザクショントークンエラーが発生した事を通知するエラー画面を応答する。 | 以下のフローによって、二重送信発生時に更新処理が重複して実行される事を防ぐことができる。 .. figure:: ./images/transaction-token-check-prevent-double-submit.png :alt: prevent double submit by transaction token check :width: 100% .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | 購買者が、商品購入画面で注文ボタンをクリックする。 | サーバ上で保持しているトランザクショントークンと、クライアントから送信されたトランザクショントークンが一致するため、商品を購入する処理を実行する。 | **このタイミングで、サーバ上で保持していたトランザクショントークの値が破棄され、新しいトークン値に更新される。** * - | (2) - | (1)のレスポンスが返る前に、購買者が誤って注文ボタンをもう一度クリックする。 | (1)の処理が実行されることによって、 **クライアントから送信されたトランザクショントークンは既に破棄された値のため、トランザクショントークンエラーとなる。** * - | (3) - | サーバは、(2)のリクエストに対して、 **トランザクショントークンエラーが発生した事を通知するエラー画面を応答する。** * - | (4) - | サーバは、(1)のリクエストで受けた商品の購入処理をDBに対して反映する。 * - | (5) - | サーバは、(1)のリクエストで受けた商品の購入完了画面を応答しようとするが、(2)のリクエストが送信された事により、(1)のリクエストに対する応答を行うためのストリームが閉じられているため、購入完了画面を応答することができない。 .. warning:: 二重送信発生時に更新処理が重複して実行される事は防ぐことが出来るが、処理が完了した事を通知する画面を応答することが出来ないという問題が残る。 そのため、JavaScriptによるボタンの2度押し防止も合わせて対応することを推奨する。 トランザクショントークンのネームスペースについて ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 共通ライブラリから提供しているトランザクショントークンチェック機能では、トランザクショントークンを管理するための器にネームスペースを設けることが出来る。 これは、タブブラウザや複数ウィンドウを使用して、更新処理を並行して操作できるようにするための仕組みである。 ネームスペースがない場合の問題点について """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | まず、ネームスペースがない場合の問題点について説明する。 | 以下の図では、clientが左右にわかれているが、実際は同一マシン上に2つのWindowを立ち上げた際の例となる。 .. figure:: ./images/token-only-one.png :alt: token only one :width: 100% .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (1) - | Window1からリクエストを送信し、応答されたトランザクショントークン(token001)をブラウザに保持する。 | サーバ上で保持しているトランザクショントークンはtoken001となる。 * - | (2) - | Window2からリクエストを送信し、応答されたトランザクショントークン(token002)をブラウザに保持する。 | **サーバ上で保持しているトランザクショントークンはtoken002となる。このタイミングで(1)の処理で生成されたトランザクショントークン(token001)は破棄される。** * - | (3) - | Window1からブラウザで保持しているトランザクショントークン(token001)を含めてリクエストを送信する。 | サーバ上で保持しているトランザクショントークン(token002)と、リクエストで送信されたトランザクショントークン(token002)が一致しないため、不正なリクエストと判断され、トランザクショントークンエラーとなる。 .. warning:: **ネームスペースがない場合は、更新処理を並行して操作することができないため、ユーザビリティの低いアプリケーションとなってしまう。** | ネームスペース指定時の動作について """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" | 次に、ネームスペースを付与した際の動作について説明する。 | ネームスペースがない場合は、更新処理を並行して操作することができないという問題があったが、ネームスペースも設けることで、この問題を解決することが出来る。 | 以下の図では、clientが左右にわかれているが、実際は同一マシン上に2つのWindowを立ち上げた際の例となる。 .. figure:: ./images/token-namespace.png :alt: token namespace :width: 100% | 上記の図の、111, 222の部分が、ネームスペースとなる。 | **ネームスペースを与えることで、トランザクションに割り振られたネームスペース内に存在するトランザクショントークンのみを操作するため、別のネームスペースのトランザクションに対して影響を与えない。** | ここでは、ブラウザを別のWindowで記述しているが、タブブラウザでも同じである。生成されるキーや使用方法については、\ :ref:`doubleSubmit_how_to_use_transaction_token_check`\ で説明する。 | .. _How-to-use: How to use -------------------------------------------------------------------------------- JavaScriptによるボタンの2度押し防止の適用 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | クライアントでのボタンの二重クリック防止は、JavaScriptで実現することになる。 | ボタンをクリックした後は、再描画するまでクリックできないようにする。 PRG(Post-Redirect-Get)パターンの適用 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | PRG(Post-Redirect-Get)パターンを適用する場合の実装例について説明する。 | 以降では、入力画面 -> 確認画面 -> 完了画面 というシンプルな画面遷移を行うアプリケーションを例に説明する。 .. figure:: ./images/staff-redirect-flow.png :alt: STAFF REDIRECT FLOW :width: 100% | 画像の番号と、ソースのコメント番号を連動させている。 | ただし、(1)~(4)については、PRGパターンと直接関係ないため、説明は省略する。 - Controller .. code-block:: java :emphasize-lines: 35,36,47-49,52-54,56 @Controller @RequestMapping("prgExample") public class PostRedirectGetExampleController { @Inject UserService userService; @ModelAttribute public PostRedirectGetForm setUpForm() { PostRedirectGetForm form = new PostRedirectGetForm(); return form; } @RequestMapping(value = "create", method = RequestMethod.GET, params = "form") // (1) public String createForm( PostRedirectGetForm postRedirectGetForm, BindingResult bindingResult) { return "prg/createForm"; // (2) } @RequestMapping(value = "create", method = RequestMethod.POST, params = "confirm") // (3) public String createConfirm( @Validated PostRedirectGetForm postRedirectGetForm, BindingResult bindingResult) { if (bindingResult.hasErrors()) { return "prg/createForm"; } return "prg/createConfirm"; // (4) } @RequestMapping(value = "create", method = RequestMethod.POST) // (5) public String create( @Validated PostRedirectGetForm postRedirectGetForm, BindingResult bindingResult, RedirectAttributes redirectAttributes) { if (bindingResult.hasErrors()) { return "prg/createForm"; } // omitted String output = "result register..."; // (6) redirectAttributes.addFlashAttribute("output", output); // (6) return "redirect:/prgExample/create?complete"; // (6) } @RequestMapping(value = "create", method = RequestMethod.GET, params = "complete") // (7) public String createComplete() { return "prg/createComplete"; // (8) } } .. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}| .. list-table:: :header-rows: 1 :widths: 10 90 * - 項番 - 説明 * - | (5) - | 確認画面の登録ボタン(Create Userボタン)が押下時の処理を行うハンドラメソッド。 | **POSTメソッドでリクエストを受け取る。** * - | (6) - | **完了画面を表示するためのURLへリダイレクトする。** | 上記例では、\ ``prgExample/create?complete``\ というURLに対して\ ``GET``\メソッドで リクエストされる。 | リダイレクト先にデータを引き渡す場合は、 \ ``RedirectAttributes``\のaddFlashAttributeメソッドを呼び出し、引き渡すデータを追加する。 | \ ``Model``\ のaddAttributeメソッドは、リダイレクト先にデータを引き渡すことはできない。 * - | (7) - | 完了画面を表示するためのハンドラメソッド。 | **GETメソッドでリクエストを受け取る。** * - | (8) - | 完了画面を表示するView(JSP)を呼び出し、完了画面を応答する。 | JSPの拡張子は :file:`spring-mvc.xml` に定義されている \ ``ViewResolver``\によって付与されるため、ハンドラメソッドの返却値からは省略している。 .. note:: * リダイレクトする際は、ハンドラメソッドの返り値として返却する遷移情報のプレフィックスとして「redirect:」を付与する。 * リダイレクト先の処理にデータを引き渡したい場合は、\ ``RedirectAttributes``\ のaddFlashAttributeメソッドを呼び出し、引き渡すデータを追加する。 - :file:`createForm.jsp` .. code-block:: jsp