理系学生日記

おまえはいつまで学生気分なのか

忍者TOOLS

PRGパターンとは何か

最近はじめてフロントエンドの開発をすることになって、PRG パターンって常識だよみたいな雰囲気でウワワワワー! ってかんじになりました。義務教育ではそんなの教えてくれなかった。 そういうわけなので、涙ごしに霞むモニタと向きあいながら PRG パターンについて調べてみました。

PRG というのは、Post/Redirect/Get の略で、

  1. 二重サブミット対策
  2. セキュリティ対策

の双方の文脈で使用されるパターンのようです。 PRG パターンについての言及は、基本的にこちらの解説に到達しているようですし、PRG パターンの命名もこのページ上でされていることもありますので、まぁここを読んでいれば間違いないんじゃないでしょうか。 ]

PRG パターン

PRG パターンのない素直な実装の問題

PRG パターンの意味する Post/Redirect/Get は、画面を表示する際のクライアント (UserAgent) - サーバ間のやりとりを示しています。ここでいう画面っていうのは、「ユーザが Web サイトのページ上で入力した情報を POST メソッドで送信するときに表示する画面」のことです。 普通にサーバ側の処理を実装しようとすると、POST メソッドのリクエストを受信した後、その BODY 部に含まれる情報を元にして DB 更新等の処理を行い、結果を表示する完了画面をクライアントに返却する、って実装になります。 これを図示すると以下のような図になります。(図は wikipedia:Post/Redirect/Get より)

f:id:kiririmode:20160618170234p:plain

この画面遷移の何が問題になるかというと、入力した情報の二重サブミットです。ユーザが意図せず二重サブミットを行ってしまうことにより、二重課金や二重購入なんて事象が発生します。 このケースにおいては、たとえばユーザによる以下のような操作により、二重サブミットが発生します。

  1. 完了画面において、ユーザが画面をリロードする (F5 ボタンなど)
  2. 完了画面で「戻る」ボタンを押下して入力画面に戻った後、「進む」ボタンを押下する
  3. 完了画面で「戻る」ボタンを押下して入力画面の戻った後、「Submit」ボタンを押して入力した情報を再送信する

このうち、PRG パターンは 上 2 つに直接対応します。

PRG パターン

PRG パターンを使った UserAgent - サーバ間は以下のような通信になります。

f:id:kiririmode:20160618170301p:plain

前の図よりも複雑になっていますが、UserAgent が POST でリクエストを送信した際、サーバはそれを処理するとともに、HTTP ステータス 3xx を UserAgent に返却します。通常、302 または 303 が使用されることが多いみたいですね (理由は後述します)。 UserAgent は 3xx に従って、GET リクエストをサーバに送ります。サーバはその GET リクエストに応答する形で、完了画面を返すという流れになります。

この PRG パターンの何がポイントかというと、ユーザがブラウザのリロード等をした際にサーバに送信されるリクエストが、(ユーザが入力した情報を含んだ) POST ではなく、情報を含まない GET で行われるという点です。こういう GET だったら、重複してリクエストをされようが、単に同じ完了画面を返却してやれば良いですね。

あれ 3 番目の対策は

前述のとおり、二重サブミットを起こす方法として、

  1. 完了画面で「戻る」ボタンを押下して入力画面の戻った後、「Submit」ボタンを押して入力した情報を再送信する

ていうのもありますが、PRG パターンに関して述べられているのは、「戻る」ボタンを押下したときの画面はキャッシュを無効化しておいて、input フィールドに値を保存させないようブラウザに指示しておくことです。 この他、トークンを使う方法があり、PRG のページにも「Prevent resubmits」でトークンについて触れられています。 ただ、実際にこの手の二重サブミットを防ぐ方法は、以下のようなページを参照すれば良いんじゃないですかね。

セキュリティ対策

PRG パターンについては、上述のとおり主に二重サブミット対策として述べられることが多いですが、セキュリティ対策として用いられることがあります。 具体例として、まずは問題となるケースを示します。

  1. PC を使うユーザが、ログイン画面でログインパスワードを入力
  2. Submit ボタンを押して、ログイン成功
  3. ユーザが PC を離れる
  4. 悪意のある第三者が当該 PC・ブラウザを使用し、リロード (あるいは「戻る」ボタン→ 「進む」ボタンを押下)

これにより、3. のタイミングで例えセッションが切れていたとしても、4. で第三者がログインに成功しますし、あるいは、再送信される POST リクエストを解析すれば、パスワードを入手することが可能なケースがあります。 これも結局、秘匿すべき情報を含む POST リクエストが再送信されることが問題なので、PRG パターンを採用することでこの問題を解決することができます。