O slideshow foi denunciado.
Utilizamos seu perfil e dados de atividades no LinkedIn para personalizar e exibir anúncios mais relevantes. Altere suas preferências de anúncios quando desejar.

JJUG CCC 2017 Spring LT about Twice Submit

488 visualizações

Publicada em

LT about Twice Submit

Publicada em: Engenharia
  • Seja o primeiro a comentar

  • Seja a primeira pessoa a gostar disto

JJUG CCC 2017 Spring LT about Twice Submit

  1. 1. 2重送信問題対策を Thymeleafで使えるようにした話 Naoya KOJIMA @jugemix
  2. 2. Qiita 記事で初めて知った2重送信問題 • 送信ボタンをダブルクリックするとリクエストを2回送信する • 送信ボタンを押した後、ブラウザ戻るボタンを押してからもう 一度送信すると2回送信したことになる • 送信ボタンを押した後の画面にエラーが表示されたのでもう一 度送信を試みると、実は2回目の送信だった
  3. 3. Qiita 記事で初めて知った2重送信問題 • 送信ボタンをダブルクリックするとリクエストを2回送信する • 送信ボタンを押した後、ブラウザ戻るボタンを押してからもう 一度送信すると2回送信したことになる • 送信ボタンを押した後の画面にエラーが表示されたのでもう一 度送信を試みると、実は2回目の送信だった 僕、実装してないや。 ヤバい・・・(^_^;)
  4. 4. 2重送信問題の対策 • 送信ボタンをダブルクリックするとリクエストを2回送信する • 送信ボタンを押した後、ブラウザ戻るボタンを押してからもう 一度送信すると2回送信したことになる • 送信ボタンを押した後の画面にエラーが表示されたのでもう一 度送信を試みると、実は2回目の送信だった Qiitaに模範解答があったので 参考にする
  5. 5. 2重送信問題の対策 • 送信ボタンをダブルクリックすると、リクエストを2回送信す る • 送信ボタンを押した後、ブラウザ戻るボタンを押してからもう 一度送信すると2回送信したことになる • 送信ボタンを押した後の画面にエラーが表示されたのでもう一 度送信を試みると、実は2回目の送信だった こんなことが起きないように 例外ハンドリング頑張る
  6. 6. 2重送信問題の対策 • 送信ボタンをダブルクリックすると、リクエストを2回送信す る • 送信ボタンを押した後、ブラウザ戻るボタンを押してからもう 一度送信すると2回送信したことになる • 送信ボタンを押した後の画面にエラーが表示されたのでもう一 度送信を試みると、実は2回目の送信だった Qiita に答えが載ってない・・・
  7. 7. 2重送信問題の対策 • 送信ボタンをダブルクリックすると、リクエストを2回送信す る • 送信ボタンを押した後、ブラウザ戻るボタンを押してからもう 一度送信すると2回送信したことになる • 送信ボタンを押した後の画面にエラーが表示されたのでもう一 度送信を試みると、実は2回目の送信だった 遷移毎に発行したワンタイムトークンを 都度チェックすれば良いみたいだけど・・・ CSRFトークンはワンタイムトークンじゃないか ら使えない・・・
  8. 8. Terasolunaに答えがあった! <dependency> <groupId>org.terasoluna.gfw</groupId> <artifactId>terasoluna-gfw-web</artifactId> <version>5.3.0.RELEASE</version> </dependency> org.terasoluna.gfw.web.token.transaction.TransactionTokenCheck.class
  9. 9. TransactionTokenCheck の使い方 @TransactionTokenCheck("hogehoge") @RequestMapping("/hogehoge") @SessionAttributes(“hogehogeSession") @Controller public class HogehogeController { /* 中略 */ } 1. Hogehoge名前空間を作る
  10. 10. TransactionTokenCheck の使い方 @TransactionTokenCheck(type = TransactionTokenType.BEGIN) @RequestMapping(value = "enter", params = "_event_entered", method = RequestMethod.POST) String verifyForm(@Validated HogehogeForm hogehogeForm, BindingResult result, Model model) { /* 中略 */ return "confirm"; } 2. Hogehoge名前空間にトークン を発行する
  11. 11. TransactionTokenCheck の使い方 @TransactionTokenCheck @RequestMapping(value = "register", params = "_event_confirmed", method = RequestMethod.POST) String updateForm(@ModelAttribute("hogehogeSession") HogehogeForm hogehogeForm) throws Exception { /* 中略 */ return "redirect:complete"; } 3. Hogehoge名前空間に作った トークンを更新する
  12. 12. TransactionTokenCheck の使い方 @RequestMapping(value = "complete", method = RequestMethod.GET) String showCompleteForm(@ModelAttribute("hogehogeSession") HogehogeForm hogehogeForm, Model model, SessionStatus sessionStatus) { /* 中略 */ return "complete"; } 4. 最後は何も書かない
  13. 13. Terasolunaに答えがあった! <dependency> <groupId>org.terasoluna.gfw</groupId> <artifactId>terasoluna-gfw-web</artifactId> <version>5.3.0.RELEASE</version> </dependency> org.terasoluna.gfw.web.token.transaction.TransactionTokenCheck.class すごく便利!
  14. 14. Terasolunaに答えがあった! <dependency> <groupId>org.terasoluna.gfw</groupId> <artifactId>terasoluna-gfw-web</artifactId> <version>5.3.0.RELEASE</version> </dependency> org.terasoluna.gfw.web.token.transaction.TransactionTokenCheck.class でもトークン生成がJSP用だった
  15. 15. Thymeleaf 用を作ってみた // ダイアレクト public class ThymeleafDialect extends AbstractDialect { /* 中略 */ @Override public String getPrefix() { return PREFIX; } @Override public Set<IProcessor> getProcessors() { final Set<IProcessor> processors = new LinkedHashSet<IProcessor>(); processors.add(new FormProcessor()); return new LinkedHashSet<IProcessor>(processors); } }
  16. 16. Thymeleaf 用を作ってみた // プロセッサ public class FormProcessor extends AbstractElementProcessor { private void addTokenHiddenFields(Arguments arguments, Element element) { /* 前略 */ String tokenName = TransactionTokenInterceptor.TOKEN_REQUEST_PARAMETER; String tokenValue = nextToken.getTokenString(); httpSession.setAttribute(tokenName, tokenValue); Element node = new Element("input"); node.setAttribute("type", "hidden"); node.setAttribute("name", tokenName); node.setAttribute("value", tokenValue); element.addChild(node); /* 後略 */ }
  17. 17. Terasoluna ありがとうございます! • これからも頑張って下さい! • 僕こんな風にやってるよーなんて方、この後飲みながら一緒に 話しましょう!

×