5.12. 二重送信防止¶
Caution
本バージョンの内容は既に古くなっています。最新のガイドラインはこちらからご参照ください。
目次
- Overview
- How to use- JavaScriptによるボタンの2度押し防止の適用
- PRG(Post-Redirect-Get)パターンの適用
- トランザクショントークンチェックの適用- 共通ライブラリから提供しているトランザクショントークンチェックについて
- @TransactionTokenCheckアノテーションの属性について
- トランザクショントークンの形式について
- トランザクショントークンのライフサイクルについて
- トランザクショントークンチェックを使用するための設定
- トランザクショントークンエラーをハンドリングするための設定
- トランザクショントークンチェックのControllerでの利用方法
- トランザクショントークンチェックのView(JSP)での利用方法
- 1つのController内で複数のユースケースを実施する場合
- トランザクショントークンチェックの代表的な適用例
- セッション使用時の並行処理の排他制御について
 
 
- How to extend
- Appendix
5.12.1. Overview¶
5.12.1.1. Problems¶
画面を提供するWebアプリケーションでは、以下の操作が行われると、同じ処理が複数回実行されてしまうことがある。
項番 操作 操作概要 
それぞれ具体的な問題点を、以下に示す。
5.12.1.1.1. 更新系ボタンの二重クリック¶
項番 説明 Warning
上記のケースでは、購入者が誤って注文ボタンを押下することで、まったく同じ商品の購入が2回行われてしまうことになる。 購入者の操作ミスが原因ではあるが、アプリケーションとして上記の問題が発生しないように制御する事が望ましい。
5.12.1.1.2. 更新処理完了後の画面の再読み込み¶
項番 説明 Warning
上記のケースでは、購入者が誤ってブラウザのリロード機能を実行することで、まったく同じ商品の購入が2回行われてしまうことになる。 購入者の操作ミスが原因ではあるが、アプリケーションとして上記の問題が発生しないように制御する事が望ましい。
5.12.1.1.3. ブラウザの戻るボタンを使用した不正な画面遷移¶
項番 説明 Note
上記のケースでは、購入者の操作ミスではないため、購入者に対して問題が発生することはない。
ただし、不正な画面操作を行った後でも更新処理が実行できてしまうと、以下のような問題が発生する。
Warning
上記のケースのように、不正な画面操作を行った後でも更新処理が実行できてしまうと、悪意のある攻撃者によって、正規のルート経由せずに直接更新処理が実行される危険度が高まる。
項番 説明 不正なリクエストによって購入処理を実行することで、各サーバの負荷が高くなったり、正規のルートで商品が購入できなくなるなどの問題が発生してしまう。 結果的に、正規のルートで購入している利用者に対して問題が波及する事になるため、アプリケーションとして上記の問題が発生しないように制御する事が望ましい。
5.12.1.2. Solutions¶
項番 Solution 概要 Note
「トランザクショントークンチェックの適用」のみの対策だと、単純な操作ミスを行った場合でもトランザクショントークンエラーとなるため、利用者に対してユーザビリティの低いアプリケーションになってしまう。
ユーザビリティを確保しつつ、二重送信で発生する問題を防止するためには、「JavaScriptによるボタンの2度押し防止」及び「PRG(Post-Redirect-Get)パターンの適用」が必要となる。
本ガイドラインでは、全ての対策を行うことを推奨するが、アプリケーションの要件によって対策の有無は判断すること。
Warning
AjaxとWebサービスでは、リクエスト毎に変更されるトランザクショントークンの受け渡しを行いにくいため、トランザクショントークンチェックを使用しなくてよい。 Ajaxの場合は、JavaScriptによるボタンの2度押し防止のみで二重送信防止を行う。
Todo
TBD
AjaxとWebサービスでのチェック方法は、今後検討の余地あり。
5.12.1.2.1. JavaScriptによるボタンの2度押し防止について¶
- ボタンやリンクを非活性化することで、ボタンやリンクを押下できないように制御する。
- 処理状態をフラグとして保持しておき、処理中にボタンやリンクが押された場合に処理中であることを通知するメッセージを表示する。
下記は、ボタンを非活性化した際のイメージとなる。
5.12.1.2.2. PRG(Post-Redirect-Get)パターンについて¶
項番 説明 Note
更新処理を伴う処理の場合は、PRGパターンを適用し、ブラウザの更新ボタンが押された際に、GETメソッドのリクエストが送信されるように制御することを推奨する。
Warning
PRGパターンでは、完了画面でブラウザの戻るボタンを押下することで、更新処理を再度実行されることを防ぐことはできない。 ブラウザの戻るボタンを使った不正な画面遷移後の更新処理の再実行を防ぐ場合は、トランザクショントークンチェックを行う必要がある。
5.12.1.2.3. トランザクショントークンチェックについて¶
トランザクショントークンチェックは、
- サーバは、クライアントからクエストが来た際に、サーバ上にトランザクションを一意に識別するための値(以下、トランザクショントークン)を保持する。
- サーバは、クライアントへトランザクショントークンを引き渡す。画面を提供するWebアプリケーションの場合は、formのhiddenタグを使用してクライアントにトランザクショントークンを引き渡す。
- クライアントは次のリクエストを送信する際に、サーバから引き渡されたトランザクショントークンを送る。サーバは、クライアントから受け取ったトランザクショントークンと、サーバ上で管理しているトランザクショントークンを比較する。
という、3つの処理で構成され、リクエストで送信されてきたトランザクショントークン値と、サーバ上で保持しているトランザクショントークン値が一致していない場合は、不正なリクエストとみなしてエラーを返す。
Warning
トランザクショントークンチェックの濫用は、アプリケーションのユーザビリティ低下につながるため、以下の点を考慮して、適用範囲を決めること。
データの更新を伴わない参照系のリクエストや、単に画面遷移のみ行うリクエストについては、トランザクショントークンチェックの範囲に含める必要はない。必要以上にトランザクションの範囲を広げてしまうと、トランザクショントークンエラーが発生しやすくなるため、アプリケーションのユーザビリティを低下させる事になる。
ビジネス観点で何回更新されても問題ないような処理(ユーザー情報更新など)では、トランザクショントークンチェックは必須ではない。
入金処理や商品の購入処理など、処理が二重で実行されると問題がある場合は、トランザクショントークンチェックが必須である。
以下に、トランザクショントークンチェック使用時において、想定通りの操作を行った場合の処理フローと、想定外の操作を行った場合の処理フローについて説明する。
項番 説明 
項番 説明 
トランザクショントークンチェックで防ぐことが出来るのは、以下の3つの事象である。
- 決められた画面遷移を行うことが求められる業務において、不正な画面遷移が行われる。
- 正規の画面遷移を伴わない不正なリクエストによって、データが更新される。
- 二重送信によって、更新処理が重複して実行される。
以下のフローによって、決められた画面遷移を行うことが求められる業務において、不正な画面遷移が行われる事を防ぐ事ができる。
項番 説明 
以下のフローによって、正規の画面遷移を伴わない不正なリクエストでデータが更新される事を防ぐことができる。
以下のフローによって、二重送信発生時に更新処理が重複して実行される事を防ぐことができる。
項番 説明 Warning
二重送信発生時に更新処理が重複して実行される事は防ぐことが出来るが、処理が完了した事を通知する画面を応答することが出来ないという問題が残る。 そのため、JavaScriptによるボタンの2度押し防止も合わせて対応することを推奨する。
5.12.1.3. トランザクショントークンのネームスペースについて¶
共通ライブラリから提供しているトランザクショントークンチェック機能では、トランザクショントークンを管理するための器にネームスペースを設けることが出来る。 これは、タブブラウザや複数ウィンドウを使用して、更新処理を並行して操作できるようにするための仕組みである。
5.12.1.3.1. ネームスペースがない場合の問題点について¶
項番 説明 Warning
ネームスペースがない場合は、更新処理を並行して操作することができないため、ユーザビリティの低いアプリケーションとなってしまう。
5.12.1.3.2. ネームスペース指定時の動作について¶
5.12.2. How to use¶
5.12.2.1. JavaScriptによるボタンの2度押し防止の適用¶
Todo
TBD
JavaScriptでのチェック方法については、次版以降で詳細化する予定である。
5.12.2.2. PRG(Post-Redirect-Get)パターンの適用¶
- Controller
@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) } }
項番 説明 "prgExample/create?complete"というURLに対してGETメソッドで リクエストされる。リダイレクト先にデータを引き渡す場合は、RedirectAttributesのaddFlashAttributeメソッドを呼び出し、引き渡すデータを追加する。ModelのaddAttributeメソッドは、リダイレクト先にデータを引き渡すことはできない。spring-mvc.xmlに定義されているViewResolverによって付与されるため、処理メソッドの返却値からは省略している。Note
- リダイレクトする際は、処理メソッドの返り値として返却する遷移情報のプレフィックスとして「redirect:」を付与する。
- リダイレクト先の処理にデータを引き渡したい場合は、
RedirectAttributesのaddFlashAttributeメソッドを呼び出し、引き渡すデータを追加する。
- createForm.jsp
<h1>Create User</h1> <div id="prgForm"> <form:form action="${pageContext.request.contextPath}/rpgExample/create" method="post" modelAttribute="postRedirectGetForm"> <form:label path="firstName">FirstName</form:label> <form:input path="firstName" /><br> <form:label path="lastName">LastName:</form:label> <form:input path="lastName" /><br> <form:button name="confirm">Confirm Create User</form:button> </form:form> </div>
- createConfirm.jsp
<h1>Confirm Create User</h1> <div id="prgForm"> <form:form action="${pageContext.request.contextPath}/rpgExample/create" method="post" modelAttribute="postRedirectGetForm"> FirstName:${f:h(postRedirectGetForm.firstName)}<br> <form:hidden path="firstName" /> LastName:${f:h(postRedirectGetForm.lastName)}<br> <form:hidden path="lastName" /> <form:button>Create User</form:button> <%-- (6) --%> </form:form> </div>
項番 説明 
- createComplete.jsp
<h1>Successful Create User Completion</h1> <div id="prgForm"> <form:form action="${pageContext.request.contextPath}/rpgExample/create" method="get" modelAttribute="postRedirectGetForm"> output:${f:h(output)}<br> <%-- (7) --%> <form:button name="backToTop">Top</form:button> </form:form> </div>
項番 説明 RedirectAttributesの addFlashAttributeメソッドで追加したデータの属性名を指定する。上記例では、"output"が引き渡したデータを参照するための属性名となる。
5.12.2.3. トランザクショントークンチェックの適用¶
5.12.2.3.1. 共通ライブラリから提供しているトランザクショントークンチェックについて¶
共通ライブラリから提供しているトランザクショントークンチェック機能では、
- トランザクショントークンのネームスペース化
- トランザクションの開始
- トランザクション内のトークン値チェック
- トランザクションの終了
を行うために、 @org.terasoluna.gfw.web.token.transaction.TransactionTokenCheckアノテーションを提供している。
トランザクショントークンチェックを行う場合は、Controllerクラス及びControllerクラスの処理メソッドに対して、 @TransactionTokenCheckアノテーションを付与することで、
宣言的にトランザクショントークンチェックを行うことが出来る。
5.12.2.3.2. @TransactionTokenCheckアノテーションの属性について¶
@TransactionTokenCheckアノテーションに指定できる属性について説明する。
@TransactionTokenCheckアノテーションパラメタ一覧¶項番 属性名 内容 default 例 
value 無 
type IN Note
value属性に設定する値は、
@RequestMappingアノテーションのvalue属性の設定値と、同じ値を設定することを推奨する。Note
type属性には、 NONE 及び END を指定することが出来るが、通常使用することはないため、説明は省略する。
5.12.2.3.3. トランザクショントークンの形式について¶
共通ライブラリから提供しているトランザクショントークンチェックで使用するトランザクショントークンは、以下の形式となる。
項番 構成要素 説明 NameSpace 
- NameSpaceは、一連の画面遷移を識別するための論理的な名称を付与するための要素となる。
- NameSpaceを設けることで、異なるNameSpaceに属するリクエストが干渉しあう事を防ぐ事が出来るため、並行して操作を行うことができる画面遷移を増やすことが出来る。
- NameSpaceとして使用する値は、
@TransactionTokenCheckアノテーションのvalue属性で指定した値が使用される。- クラスアノテーションのvalue属性とメソッドアノテーションのvalue属性の両方を指定した場合は、 両方の値を
"/"で連結した値がNameSpaceとなる。複数のメソッドで同じ値を指定した場合は、同じNameSpaceに属するメソッドとなる。- クラスアノテーションにのみvalue属性を指定した場合は、そのクラスで生成されるトランザクショントークンのNameSpaceは、全てクラスアノテーションで指定した値となる。
- メソッドアノテーションにのみvalue属性を指定した場合は、生成されるトランザクショントークンのNameSpaceはメソッドアノテーションで指定した値となる。複数のメソッドで同じ値を指定した場合は、同じNameSpaceに属するメソッドとなる。
- クラスアノテーションのvalue属性とメソッドアノテーションのvalue属性の両方を省略した場合は、グローバルトークンに属するメソッドとなる。グローバルトークンについては、グローバルトークンを参照されたい。
TokenKey 
TokenKeyは、ネームスペース内で管理されているトランザクションを識別するための要素となる。
TokenKeyは、
@TransactionTokenCheckアノテーションのtype属性にTransactionTokenType.BEGINが宣言されているメソッドが実行されたタイミングで生成れる。複数のTokenKeyを同時に保持することが出来る数には上限数があり、デフォルト10である。TokenKeyの保持数はNameSpace毎に管理される。
TransactionTokenType.BEGIN時にNameSpace毎に管理されている保持数が最大値に達している場合は、実行された日時が最も古いTokenKeyを破棄することで(Least Recently Used (LRU))、新しいトランザクションを有効なトランザクションとして管理する仕組みとなっている。破棄されたトランザクショントークンを使ってアクセスした場合は、トランザクショントークンエラーとなる。
TokenValue 
- TokenValueは、トランザクションのトークン値を保持するための要素となる。
- TokenValueは、
@TransactionTokenCheckアノテーションのtype属性にTransactionTokenType.BEGIN又はTransactionTokenType.INが宣言されているメソッドが実行されたタイミングで生成される。Warning
メソッドアノテーションにのみvalue属性を指定した場合、他のControllerで同じ値を指定している場合に、一連の画面遷移を行うためのリクエストとして扱われる点に注意する必要がある。 この方法での指定は、Controllerを跨いだ画面遷移を同一トランザクションとして扱いたい場合にのみ、使用すること。
原則的には、メソッドアノテーションにのみvalue属性を指定する方法は使用しない事を推奨する。
Note
NameSpaceの指定方法として、
- クラスアノテーションのvalue属性とメソッドアノテーションのvalue属性の両方を指定する場合
- クラスアノテーションにのみvalue属性を指定する場合
の使い分けについては、Controllerの作成粒度に応じて使い分ける。
Controllerに、複数のユースケースに対応する処理メソッドを実装する場合は、クラスアノテーションのvalue属性とメソッドアノテーションのvalue属性の両方を指定する。例えば、ユーザの登録、変更、削除を一つのControllerで実装する場合は、このパターンとなる。
Controllerに、一つのユースケースに対応する処理メソッドを実装する場合は、クラスアノテーションにのみvalue属性を指定する。例えば、ユーザの登録、変更、削除毎にControllerを実装する場合は、このパターンとなる。
5.12.2.3.4. トランザクショントークンのライフサイクルについて¶
トランザクショントークンのライフサイクル(生成、更新、破棄)制御は、以下のタイミングで行われる。
項番 ライフサイクル制御 説明 @TransactionTokenCheckアノテーションのtype属性にTransactionTokenType.BEGINが指定されたメソッドの処理が終了したタイミングで新たなトークンが生成され、トランザクションが開始される。@TransactionTokenCheckアノテーションのtype属性にTransactionTokenType.INが指定されたメソッドの処理が終了したタイミングでトークン(TokenValue)が更新され、トランザクションが継続される。@TransactionTokenCheckアノテーションのtype属性にTransactionTokenType.BEGINが指定されたメソッドを呼び出すタイミングで、リクエストパラメータに指定されているトランザクショントークンが破棄され、不要なトランザクションが終了される。[2]NameSpace内で保持することが出来るトランザクショントークン(TokenKey)の数が上限数に達している状態で、新たにトランザクションが開始される場合、実行された日時が最も古いトランザクショントークンが破棄され、トランザクションが強制終了される。[3]システムエラーなどの例外が発生した場合、リクエストパラメータに指定されているトランザクショントークンが破棄され、トランザクションを終了される。Note
NameSpace内で保持することが出来るトランザクショントークン(TokenKey)の数には上限数が設けられており、新たにトランザクショントークンを生成する際に 上限値に達していた場合は、実行された日時が最も古いTokenKeyをもつトランザクショントークンを破棄(Least Recently Used (LRU))することで、 新しいトランザクションを有効なトランザクションとして管理する仕組みとなっている。
NameSpaceごとに保持できるトランザクショントークンの上限数のデフォルト10個である。 上限値を変更する場合は、トランザクショントークンの上限数の変更方法についてを参照されたい。
- NameSpace内で保持することが出来るトランザクショントークンの数には上限数は、デフォルト値(10)が指定されている。
- Controllerのクラスアノテーションとして、 @TransactionTokenCheck("name")が指定されている。
- 同じNameSpaceのトランザクショントークンが上限値に達している状態である。
5.12.2.3.5. トランザクショントークンチェックを使用するための設定¶
共通ライブラリから提供しているトランザクショントークンチェックを使用するための設定を、以下に示す。
- spring-mvc.xml
<mvc:interceptors> <mvc:interceptor> <!-- (1) --> <mvc:mapping path="/**" /> <!-- (2) --> <mvc:exclude-mapping path="/resources/**" /> <!-- (2) --> <mvc:exclude-mapping path="/**/*.html" /> <!-- (2) --> <!-- (3) --> <bean class="org.terasoluna.gfw.web.token.transaction.TransactionTokenInterceptor" /> </mvc:interceptor> </mvc:interceptors> <bean id="requestDataValueProcessor" class="org.terasoluna.gfw.web.mvc.support.CompositeRequestDataValueProcessor"> <constructor-arg> <util:list> <!-- (4) --> <bean class="org.terasoluna.gfw.web.token.transaction.TransactionTokenRequestDataValueProcessor" /> <!-- omitted --> </util:list> </constructor-arg> </bean>
項番 説明 HandlerInterceptorを設定する。HandlerInterceptorを適用するリクエストパスを指定する。上記例では、 /resources配下へのリクエストとHTMLへのリクエストを除く、全てのリクエストに対して適用している。@TransactionTokenCheckアノテーションを使用して、トランザクショントークンの生成及びチェックを実施するためのクラス(TransactionTokenInterceptor)を指定する。<fomr:form>タグを使用してHidden領域に自動的に埋め込むためのクラス(TransactionTokenRequestDataValueProcessor)を設定する。
5.12.2.3.6. トランザクショントークンエラーをハンドリングするための設定¶
org.terasoluna.gfw.web.token.transaction.InvalidTransactionTokenException が発生する。- applicationContext.xmlに定義されている- ExceptionCodeResolver
- spring-mvc.xmlに定義されている- SystemExceptionResolver
の設定に対して、 InvalidTransactionTokenExceptionのハンドリング定義を追加する必要がある。
設定の追加方法については、
を参照されたい。
5.12.2.3.7. トランザクショントークンチェックのControllerでの利用方法¶
- Controller
@Controller @RequestMapping("transactionTokenCheckExample") @TransactionTokenCheck("transactionTokenCheckExample") // (1) public class TransactionTokenCheckExampleController { @RequestMapping(params = "first", method = RequestMethod.GET) public String first() { return "transactionTokenCheckExample/firstView"; } @RequestMapping(params = "second", method = RequestMethod.POST) @TransactionTokenCheck(type = TransactionTokenType.BEGIN) // (2) public String second() { return "transactionTokenCheckExample/secondView"; } @RequestMapping(params = "third", method = RequestMethod.POST) @TransactionTokenCheck // (3) public String third() { return "transactionTokenCheckExample/thirdView"; } @RequestMapping(params = "fourth", method = RequestMethod.POST) @TransactionTokenCheck // (3) public String fourth() { return "transactionTokenCheckExample/fourthView"; } @RequestMapping(params = "fifth", method = RequestMethod.POST) @TransactionTokenCheck // (3) public String fifth() { return "redirect:/transactionTokenCheckExample?complete"; } @RequestMapping(params = "complete", method = RequestMethod.GET) public String complete() { // (4) return "transactionTokenCheckExample/fifthView"; } }
項番 説明 @RequestMappingのvalue属性と同じ値を指定している。@TransactionTokenCheck(type = TransactionTokenType.IN)を指定した時と同じ動作となる。@TransactionTokenCheckアノテーションの指定は行っていない。Note
@TransactionTokenCheckアノテーションのtype属性にBEGINを指定した場合は、新しくTokenKeyが生成されるため、トランザクショントークンのチェックは行われない。
@TransactionTokenCheckアノテーションのtype属性にINが指定された場合は、リクエストで指定されたトークン値とサーバ上で保持しているトークン値が同一のものがあるかをチェックする。
5.12.2.3.8. トランザクショントークンチェックのView(JSP)での利用方法¶
<form:form>タグを使して自動的にトランザクショントークンをhidden要素に埋め込む方法を推奨する。- firstView.jsp
<h1>First</h1> <form:form method="post" action="transactionTokenCheckExample"> <input type="submit" name="second" value="second" /> </form:form>
- secondView.jsp
<h1>Second</h1> <form:form method="post" action="transactionTokenCheckExample"><!-- (1) --> <input type="submit" name="third" value="third" /> </form:form>
- thirdView.jsp
<h1>Third</h1> <form:form method="post" action="transactionTokenCheckExample"><!-- (1) --> <input type="submit" name="fourth" value="fourth" /> </form:form>
- fourthView.jsp
<form:form>タグを使用する場合<h1>Fourth</h1> <form:form method="post" action="transactionTokenCheckExample"><!-- (1) --> <input type="submit" name="fifth" value="fifth" /> </form:form>
HTMLの
<form>タグを使用する場合<h1>Fourth</h1> <form method="post" action="transactionTokenCheckExample"> <t:transaction /><!-- (2) --> <!-- (3) --> <input type="hidden" name="${f:h(_csrf.parameterName)}" value="${f:h(_csrf.token)}"/> <input type="submit" name="fifth" value="fifth" /> </form>
- fifthView.jsp
<h1>Fifth</h1> <form:form method="get" action="transactionTokenCheckExample"> <input type="submit" name="first" value="first" /> </form:form>
項番 説明 <form:form>タグを使用した場合は、@TransactionTokenCheckアノテーションのtype属性にBEGINかINを指定すると、name="_TRANSACTION_TOKEN"に対するValueが、hiddenタグとして自動的に埋め込まれる。<form>タグを使用する場合は、<t:transaction />を使用することで、(1)と同様のhiddenタグが埋め込まれる。<form>タグを使用する場合は、Spring Securityから提供されているCSRFトークンチェックで必要となるcsrfトークンをhidden項目として埋め込む必要がある。CSRFトークンチェックで必要となるcsrfトークンについては、CSRFトークンを明示的に埋め込む方法を参照されたい。Note
<form:form>タグでを使用すると、CSRFトークンチェックで必要となるパラメータも自動的に埋め込まれる。 CSRFトークンチェックで必要となるパラメータについては、CSRFトークンを自動で埋め込む方法を参照されたい。Note
<t:transaction />は、共通ライブラリから提供しているJSPタグライブラリである。 (2)で使用している「t:」については、インクルード用の共通JSPの作成を参照されたい。
- HTMLの出力例
出力されたHTMLを確認すると、
- NameSpaceは、クラスアノテーションのvalue属性で指定した値が設定される。上記例だと、"transactionTokenCheckExample"(橙色の下線)がNameSpaceとなる。
- TokenKeyは、トランザクション開始時に払い出された値が引き回されて設定される。上記例だと、"c0123252d531d7baf730cd49fe0422ef"(青色の下線)がTokenKeyとなる。
- TokenValueは、リクエスト毎に値が変化している。上記例だと、"3f610684e1cb546a13b79b9df30a7523"、"da770ed81dbca9a694b232e84247a13b"、"bd5a2d88ec446b27c06f6d4f486d4428"(緑色の下線)がTokenValueとなる。
ことが、わかる。
5.12.2.3.9. 1つのController内で複数のユースケースを実施する場合¶
- Controller
@Controller @RequestMapping("transactionTokenChecFlowkExample") @TransactionTokenCheck("transactionTokenChecFlowkExample") // (1) public class TransactionTokenCheckFlowExampleController { @RequestMapping(value = "flowOne", params = "first", method = RequestMethod.GET) public String flowOneFirst() { return "transactionTokenChecFlowkExample/flowOneFirstView"; } @RequestMapping(value = "flowOne", params = "second", method = RequestMethod.POST) @TransactionTokenCheck(value = "flowOne", type = TransactionTokenType.BEGIN) // (2) public String flowOneSecond() { return "transactionTokenChecFlowkExample/flowOneSecondView"; } @RequestMapping(value = "flowOne", params = "third", method = RequestMethod.POST) @TransactionTokenCheck(value = "flowOne", type = TransactionTokenType.IN) // (2) public String flowOneThird() { return "transactionTokenChecFlowkExample/flowOneThirdView"; } @RequestMapping(value = "flowTwo", params = "first", method = RequestMethod.GET) public String flowTwoFirst() { return "transactionTokenChecFlowkExample/flowTwoFirstView"; } @RequestMapping(value = "flowTwo", params = "second", method = RequestMethod.POST) @TransactionTokenCheck(value = "flowTwo", type = TransactionTokenType.BEGIN) // (3) public String flowTwoSecond() { return "transactionTokenChecFlowkExample/flowTwoSecondView"; } @RequestMapping(value = "flowTwo", params = "third", method = RequestMethod.POST) @TransactionTokenCheck(value = "flowTwo", type = TransactionTokenType.IN) // (3) public String flowTwoThird() { return "transactionTokenChecFlowkExample/flowTwoThirdView"; } @RequestMapping(value = "flowThree", params = "first", method = RequestMethod.GET) public String flowThreeFirst() { return "transactionTokenChecFlowkExample/flowThreeFirstView"; } @RequestMapping(value = "flowThree", params = "second", method = RequestMethod.POST) @TransactionTokenCheck(value = "flowThree", type = TransactionTokenType.BEGIN) // (4) public String flowThreeSecond() { return "transactionTokenChecFlowkExample/flowThreeSecondView"; } @RequestMapping(value = "flowThree", params = "third", method = RequestMethod.POST) @TransactionTokenCheck(value = "flowThree", type = TransactionTokenType.IN) // (4) public String flowThreeThird() { return "transactionTokenChecFlowkExample/flowThreeThirdView"; } }
項番 説明 @RequestMappingのvalue属性と同じ値を指定している。"flowOne"という名前を持つユースケースの処理に対して、トランザクショントークンチェックを行う。上記例では、本ガイドラインの推奨パターンである@RequestMappingのvalue属性と同じ値を指定している。"flowTwo"という名前を持つユースケースの処理に対して、トランザクショントークンチェックを行う。上記例では、本ガイドラインの推奨パターンである@RequestMappingのvalue属性と同じ値を指定している。"flowThree"という名前を持つユースケースの処理に対して、トランザクショントークンチェックを行う。上記例では、本ガイドラインの推奨パターンである@RequestMappingのvalue属性と同じ値を指定している。Note
ユースケース毎にNameSpaceを割り振ることで、各ユースケース毎にトランザクショントークンのチェックを行うことが出来る。
5.12.2.3.10. トランザクショントークンチェックの代表的な適用例¶
「入力画面 -> 確認画面 -> 完了画面」といったシンプルな画面遷移を行うユースケースに対して、トランザクショントークンチェックを適用する際の実装例を以下に示す。
- Controller
@Controller @RequestMapping("user") @TransactionTokenCheck("user") // (1) public class UserController { // omitted @RequestMapping(value = "create", params = "form") public String createForm(UserCreateForm form) { // (2) return "user/createForm"; } @RequestMapping(value = "create", params = "confirm", method = RequestMethod.POST) @TransactionTokenCheck(value = "create", type = TransactionTokenType.BEGIN) // (3) public String createConfirm(@Validated UserCreateForm form, BindingResult result) { // omitted return "user/createConfirm"; } @RequestMapping(value = "create", method = RequestMethod.POST) @TransactionTokenCheck(value = "create") // (4) public String create(@Validated UserCreateForm form, BindingResult result) { // omitted return "redirect:/user/create?complete"; } @RequestMapping(value = "create", params = "complete") public String createComplete() { // (5) return "user/createComplete"; } // omitted }
項番 説明 "user"というNameSpaceを設定している。上記例では、推奨パターンの@RequestMappingアノテーションのvalue属性と同じ値を指定している。@TransactionTokenCheckアノテーションを指定していない。@TransactionTokenCheckアノテーションを指定していない。Warning
@TransactionTokenCheckアノテーションを定義した処理メソッドの遷移先は、View(JSP)を指定する必要がある。 リダイレクト先などのView(JSP)以外を遷移先に指定すると、次の処理でTransactionTokenの値が変わっており、必ずTransactionTokenエラーが発生する。
5.12.2.3.11. セッション使用時の並行処理の排他制御について¶
@SessionAttributeアノテーションを使用してフォームオブジェクトなどをセッションに格納した場合、
同じ処理の画面遷移を複数並行して行うと、互いの画面操作が干渉しあい、画面に表示されている値とセッション上で保持している値が一致しなくなってしまう事がある。
こうような不整合な状態になっている画面からのリクエストを不正なリクエストとして防ぐ方法として、トランザクショントークンチェック機能を使用することができる。
NameSpaceごとに保持できるトランザクショントークンの上限数を1を設定する。
- spring-mvc.xml
<mvc:interceptor> <mvc:mapping path="/**" /> <!-- omitted --> <bean class="org.terasoluna.gfw.web.token.transaction.TransactionTokenInterceptor"> <constructor-arg value="1"/> <!-- (1) --> </bean> </mvc:interceptor>
項番 説明 Note
@SessionAttributeアノテーションを使用してフォームオブジェクトなどをセッションに格納した場合は、 NameSpaceごとのトランザクショントークンの保持数を”1”に設定するとこで、 古いデータを表示している画面からのリクエストを不正なリクエストとして防ぐことが可能となる。
5.12.3. How to extend¶
5.12.3.1. プログラマティックにトランザクショントークンのライフサイクルを管理する方法について¶
以下の設定を追加することで、Controllerの処理メソッドの引数としてorg.terasoluna.gfw.web.token.transaction.TransactionTokenContextを受け取り、プログラマティックにトランザクショントークンのライフサイクルを管理することができる。
- spring-mvc.xml
<mvc:annotation-driven> <mvc:argument-resolvers> <!-- (1) --> <bean class="org.terasoluna.gfw.web.token.transaction.TransactionTokenContextHandlerMethodArgumentResolver" /> </mvc:argument-resolvers> </mvc:annotation-driven>
項番 説明 <mvc:argument-resolvers>要素に、Controllerのメソッドの引数として、プログラマスティックにトランザクショントークンのライフサイクルを管理するためのオブジェクト(TransactionTokenContext)を引き渡すためのクラス(TransactionTokenContextHandlerMethodArgumentResolver)を設定をする。プログラマスティックにトランザクショントークンのライフサイクルを管理する必要がない場合は、本設定は不要である。Note
使用されなくなったトランザクショントークンは、1つのNameSpaceで保持することが出来る上限値を超えた時点で自動的に破棄されていくため、基本的には、本設定は不要である。
5.12.3.2. トランザクショントークンの上限数の変更方法について¶
以下の設定を行うことで、1つのNameSpace上で保持する事ができるトランザクショントークンの上限数を変更することができる。
- spring-mvc.xml
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**" /> <mvc:exclude-mapping path="/resources/**" /> <mvc:exclude-mapping path="/**/*.html" /> <bean class="org.terasoluna.gfw.web.token.transaction.TransactionTokenInterceptor" /> <constructor-arg value="5"/> <!-- (1) --> </mvc:interceptor> </mvc:interceptors>
項番 説明 TransactionTokenInterceptorのコンストラクタの値として、1つのNameSpace上で保持する事ができるトランザクショントークンの上限数を指定する。デフォルト値(デフォルトコンストラクタ使用時に設定される値)は、10となっている。上記例では、 デフォルト値(10)から5に変更している。
5.12.4. Appendix¶
5.12.4.1. グローバルトークン¶
@TransactionTokenCheckアノテーションのvalue属性の指定を省略すると、グローバルなトランザクショントークンとして扱われる。"globalToken"(固定値)が使用される。Note
アプリケーション全体として、単一の画面遷移のみを許容する場合は、NameSpaceごとに保持できるトランザクショントークンの上限数を1に設定し、グルーバルトークンを使用することで実現することが出来る。
アプリケーション全体として、単一の画面遷移のみを許容する場合場合の設定及び実装例を以下に示す。
5.12.4.1.1. NameSpaceごとに保持できるトランザクショントークンの上限数の変更¶
NameSpaceごとに保持できるトランザクショントークンの上限数を1を設定する。
- spring-mvc.xml
<mvc:interceptor> <mvc:mapping path="/**" /> <!-- omitted --> <bean class="org.terasoluna.gfw.web.token.transaction.TransactionTokenInterceptor"> <constructor-arg value="1"/> <!-- (1) --> </bean> </mvc:interceptor>
項番 説明 
5.12.4.1.2. Controllerの実装¶
グルーバルトークン用のNameSpaceとなるようにするために、@TransactionTokenCheckアノテーションのvalue属性には、値を指定しない。
- Controller
@Controller @RequestMapping("globalTokenCheckExample") public class GlobalTokenCheckExampleController { // (1) @RequestMapping(params = "first", method = RequestMethod.GET) public String first() { return "globalTokenCheckExample/firstView"; } @RequestMapping(params = "second", method = RequestMethod.POST) @TransactionTokenCheck(type = TransactionTokenType.BEGIN) // (2) public String second() { return "globalTokenCheckExample/secondView"; } @RequestMapping(params = "third", method = RequestMethod.POST) @TransactionTokenCheck // (2) public String third() { return "globalTokenCheckExample/thirdView"; } @RequestMapping(params = "fourth", method = RequestMethod.POST) @TransactionTokenCheck // (2) public String fourth() { return "globalTokenCheckExample/fourthView"; } @RequestMapping(params = "fifth", method = RequestMethod.POST) public String fifth() { return "globalTokenCheckExample/fifthView"; } }
項番 説明 @TransactionTokenCheckアノテーションを指定しない。@TransactionTokenCheckアノテーションのvalue属性を指定しない。
- HTMLの出力例
JSPは、トランザクショントークンチェックのView(JSP)での利用方法で用意したJSPと同等のものを用意する。actionを、"transactionTokenCheckExample"から"globalTokenCheckExample"に変更したのみで、他は同じである。
出力されたHTMLを確認すると、
- NameSpaceは、"globalToken"という固定値が設定される。
- TokenKeyは、トランザクション開始時に払い出された値が引き回されて設定される。上記例だと、"9d937be4adc2f5dd2032292d153f1133"(青色の下線)がTokenKeyとなる。
- TokenValueは、リクエスト毎に値が変化している。上記例だと、"9204d7705ce7a17f16ca6cec24cfd88b"、"69c809fefcad541dbd00bd1983af2148"、"6b83f33b365f1270ee1c1b263f046719"(緑色の下線)がTokenValueとなる。
ことが、わかる。
以下に、NameSpaceごとのトランザクショントークンの上限数を1に設定して、グローバルトークンを使用した場合の動作について説明する。
項番 説明 Note
サーバ上に残っているトランザクショントークンは、グローバルトークンが新たに生成されたタイミングで自動的に削除される。
5.12.4.2. Quick Reference¶
番号 Namespace毎に保持するトークン数 classで指定したnamespace値 メソッドで指定したnamespace値 生成されるトークンの例 業務制限 


















