質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.59%

Laravel メール送信が絡むと多重送信可能になる

解決済

回答 1

投稿

  • 評価
  • クリップ 1
  • VIEW 2,454

score 26

Laravel 5.7 でお問合せフォームを作成しました。

「入力画面→完了画面」 方式です。

多重送信防止ですが、処理にメール送信があると効きません。

$request->session()->regenerateToken();

POSTされた際のアクション

public function send(Request $request)
{
    $data = $request->only('name', 'email', 'message');
    //DBに保存
    Contact::create($data);
    //メール送信
    Mail::to($to)->send(new ContactMail($data)); //この行がなければ多重送信防げる
    // 多重送信対策
    $request->session()->regenerateToken();
    return redirect()->action('ContactController@thanks');
}

ルーティング

Route::get('/contact', 'ContactController@form');   //入力画面表示
Route::post('/contact', 'ContactController@send');  //送信処理
Route::get('/thanks', 'ContactController@thanks');  //完了画面表示

formメソッドとthanksメソッドは画面を表示するだけです。

public function form()
{
    return view('form');
}

public function thanks()
{
      return view('thanks');
}


メール送信処理があると、なぜ多重送信されてしまうのでしょうか。

また、完了画面に直接リンクできる状態ですが、不自然でしょうか。
この場合、sendメソッドから完了画面にリダイレクトするときにセッションを付与して、thanksメソッドで確認する等の処理が最適ですか。

回答よろしくお願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+2

regenerateToken()の効果が出るのが間に合っていないからではないでしょうか。

セッションがあるリクエストの処理は大雑把に言うと以下のようになります。

  • リクエストの処理をはじめるときにクライアントから送られてきたセッションIDをもとにセッション情報をよみだす
  • リクエストの処理の本体。この例でいうとsend
  • 最後にセッション情報を書き出します。

この一連の処理をロックして行う流儀とロックはしない流儀があります。ロックする方だとこのような問題は起きないのですがLaravelのセッションはロックしない方です。(その分ロックする方は同じセッションのリクエストは一度に一つしか処理できないので効率が悪いという弱点があるので一長一短ではあります)

メール送信はそこそこ時間のかかる処理なので、送信連打があると一つ目のregenerateToken()で更新された新しいトークンを持ったセッション情報を書き出す前に別のリクエストが動きはじめることがあって、そのときトークンが古いままで処理されるのでCSRFトークン不一致になることなく処理が継続できてしまいます。

メール送信を入れる前は起きなかったとありますが、おそらく同じく問題になるタイミングは短いながらもあるのですが、人間が連打したぐらいではなかなか起きないということではないかと思います。

対策ですがブラウザ側でjavascriptなどで対策する方法とサーバ側で対策する方法が考えられます。

サーバ側でやる場合は、使用済みトークンかどうか判定するのがいいのではないでしょうか。csrfトークンが使用済みであることを記録し、入り口でチェックします。

例:

Cache::addはキーが登録済みならfalseを返すのでこういう用途には便利です。regenerateTokenで新たに作られたトークンを含んだセッション情報が保存されるまで持てばいいので保持期間は1分もあれば十分でしょう。

    if (!Cache::add('used_token.'.$request->session()->token(), 1, 1)) {
        # 使用済みだったときの処理
        # 例: TokenMismatchExceptionを投げる(CSRFトークン不一致の扱いにする)
        #     独自のエラー表示にする など
    }

またメール送信は遅いので別プロセスで後で処理するようにするというのもよく行われます。Laravelだとキューの仕組みがありますから、リクエストの処理ではキューに積むだけで送信は後でやるというようなことも比較的簡単に実現できます。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/03/10 20:22

    ありがとうございます!

    以下のようにsendメソッドの入口に、ご提示いただいたコードを記述したら多重送信防止できました。

    public function send(Request $request)
    {
    if (!Cache::add('used_token.'.$request->session()->token(), 1, 1))
    { return abort(403);}

    //メール送信処理など
    }

    キャンセル

  • 2019/03/10 20:27

    完了画面のURLへ直接リンクできてしまうことに関しては、

    sendメソッドの最後で以下のようにセッションを付与して

    return redirect()->action('ContactController@thanks')->with('status', true);

    thanksメソッドでは以下のように、セッションがあれば完了画面を表示

    if(session('status')){ return view('thanks'); }
    return redirect('/');

    このやり方が良いでしょうか?

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.59%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る