2重送信問題対策をThymeleafで使えるようにした話
Naoya KOJIMA @jugemix
Qiita記事で初めて知った2重送信問題
•送信ボタンをダブルクリックするとリクエストを2回送信する
•送信ボタンを押した後、ブラウザ戻るボタンを押してからもう一度送信すると2回送信したことになる
•送信ボタンを押した後の画面にエラーが表示されたのでもう一度送信を試みると、実は2回目の送信だった
Qiita記事で初めて知った2重送信問題
•送信ボタンをダブルクリックするとリクエストを2回送信する
•送信ボタンを押した後、ブラウザ戻るボタンを押してからもう一度送信すると2回送信したことになる
•送信ボタンを押した後の画面にエラーが表示されたのでもう一度送信を試みると、実は2回目の送信だった
僕、実装してないや。ヤバい・・・(^_^;)
2重送信問題の対策
•送信ボタンをダブルクリックするとリクエストを2回送信する
•送信ボタンを押した後、ブラウザ戻るボタンを押してからもう一度送信すると2回送信したことになる
•送信ボタンを押した後の画面にエラーが表示されたのでもう一度送信を試みると、実は2回目の送信だった
Qiitaに模範解答があったので参考にする
2重送信問題の対策
•送信ボタンをダブルクリックすると、リクエストを2回送信する
•送信ボタンを押した後、ブラウザ戻るボタンを押してからもう一度送信すると2回送信したことになる
•送信ボタンを押した後の画面にエラーが表示されたのでもう一度送信を試みると、実は2回目の送信だった
こんなことが起きないように例外ハンドリング頑張る
2重送信問題の対策
•送信ボタンをダブルクリックすると、リクエストを2回送信する
•送信ボタンを押した後、ブラウザ戻るボタンを押してからもう一度送信すると2回送信したことになる
•送信ボタンを押した後の画面にエラーが表示されたのでもう一度送信を試みると、実は2回目の送信だった
Qiita に答えが載ってない・・・
2重送信問題の対策
•送信ボタンをダブルクリックすると、リクエストを2回送信する
•送信ボタンを押した後、ブラウザ戻るボタンを押してからもう一度送信すると2回送信したことになる
•送信ボタンを押した後の画面にエラーが表示されたのでもう一度送信を試みると、実は2回目の送信だった
遷移毎に発行したワンタイムトークンを都度チェックすれば良いみたいだけど・・・
CSRFトークンはワンタイムトークンじゃないから使えない・・・
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
TransactionTokenCheckの使い方
@TransactionTokenCheck("hogehoge")
@RequestMapping("/hogehoge")
@SessionAttributes(“hogehogeSession")
@Controller
public class HogehogeController {
/* 中略 */
}
1. Hogehoge名前空間を作る
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名前空間にトークンを発行する
TransactionTokenCheckの使い方
@TransactionTokenCheck
@RequestMapping(value = "register", params = "_event_confirmed",
method = RequestMethod.POST)
String updateForm(@ModelAttribute("hogehogeSession") HogehogeFormhogehogeForm) throws Exception {
/* 中略 */
return "redirect:complete";
}
3. Hogehoge名前空間に作ったトークンを更新する
TransactionTokenCheckの使い方
@RequestMapping(value = "complete", method = RequestMethod.GET)
String showCompleteForm(@ModelAttribute("hogehogeSession") HogehogeForm hogehogeForm, Model model, SessionStatussessionStatus) {
/* 中略 */
return "complete";
}
4. 最後は何も書かない
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
すごく便利!
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用だった
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);
}
}
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);
/* 後略 */
}
Terasolunaありがとうございます!
•これからも頑張って下さい!
•僕こんな風にやってるよーなんて方、この後飲みながら一緒に話しましょう!
Top Related