GETとPOSTのセキュリティと使い分け
「セキュリティのためにPOSTを使う」という考え方の実態と誤解を整理し、HTTPメソッド選択の正しい基準と具体的なセキュリティ対策を理解する
TL;DR
- 「GETだとキャッシュされるからPOSTにする」は誤った対策であることが多い。
Cache-Controlヘッダーを正しく設定すれば GET でもキャッシュを防げる - GETとPOSTの選択基準は**セキュリティではなく HTTP の意味論(副作用の有無)**に基づくべき
- センシティブな値(パスワード・トークン)を URL に含めること自体が問題であり、POST にすれば解決するわけではない
- 実際に必要な対策は「適切なHTTPヘッダー」「HTTPS」「ロギングの管理」の組み合わせ
目的
Web開発では「GETはキャッシュされるからセキュリティ上危険、機密情報を扱う操作はPOSTにすべき」という考え方が広まっている。
この考え方には正しい部分もあるが、単純に GET を POST に置き換えるだけでは解決しない問題も多い。
このページでは、GETのキャッシュに関するセキュリティリスクの実態を整理した上で、HTTPメソッドを正しく選択するための基準と、本当に有効なセキュリティ対策を説明する。
前提知識
HTTP メソッドの基本的な意味
| メソッド | 意味 | 安全性 | 冪等性 |
|---|---|---|---|
| GET | リソースの取得 | 安全(副作用なし) | 冪等 |
| POST | リソースの作成・処理の実行 | 安全でない | 非冪等 |
| PUT | リソースの全体更新 | 安全でない | 冪等 |
| DELETE | リソースの削除 | 安全でない | 冪等 |
- 安全(Safe): サーバーの状態を変更しない操作。何度呼んでもリソースに副作用がない
- 冪等(Idempotent): 同じリクエストを何度送っても結果が変わらない
キャッシュが発生する場所
HTTPのキャッシュは複数の場所で発生する。
クライアント(ブラウザ)
↓
ブラウザキャッシュ(ローカル)
↓ HTTPS
プロキシ / CDN / リバースプロキシ
↓
サーバー| 場所 | 主体 | 何をキャッシュするか |
|---|---|---|
| ブラウザキャッシュ | ブラウザ自身 | URL単位でレスポンスを保存 |
| プロキシキャッシュ | 企業・ISPのプロキシ | 複数ユーザーで共有されることがある |
| CDN | Cloudfront / Cloudflareなど | 地理的に分散したキャッシュ |
| サーバー側キャッシュ | Redis・Memcachedなど | アプリ側で制御 |
HTTPS(TLS)の役割
HTTPS は通信経路を暗号化する。GET であれ POST であれ、HTTPS を使っていれば 通信内容(URLのパス・クエリ・ボディ)はすべて暗号化 される。
HTTPS 使用時の通信(暗号化される範囲):
ホスト名以外のすべて(パス・クエリ・ヘッダー・ボディ)
HTTPS 非使用時:
すべてが平文で流れる(GETもPOSTも関係なく危険)GET のリスクと誤解
GETが危険とされる具体的な理由を一つひとつ確認する。
1. ブラウザキャッシュに残る
GETのレスポンスは、Cache-Control ヘッダーが適切に設定されていないとブラウザにキャッシュされる。
実態:
- これは POST でも同様に制御できる問題。
Cache-Control: no-storeを付ければ GET でもキャッシュされない - POST レスポンスも
Cache-Controlヘッダー次第ではキャッシュされる(RFC7234)
HTTP/1.1 200 OK
Cache-Control: no-store, no-cacheこのヘッダーを付ければ GET でもブラウザキャッシュに残らない。
2. ブラウザの履歴に URL が残る
GET のリクエストは URL が完全にブラウザ履歴に記録される。?token=abc123 のようなセンシティブな値が URL にある場合、共有端末では漏洩リスクになる。
実態:
- これは「センシティブな値をクエリパラメータに含めること」自体が問題
- POST にしても履歴には残らないが、「URLにセンシティブな値を含まない」という設計が本質的な解決策
3. サーバーのアクセスログに URL が記録される
多くのWebサーバー(nginx・Apacheなど)はデフォルトで URL をアクセスログに出力する。
# nginx のデフォルトログ(access.log の例)
192.168.1.1 - - [16/Jun/2026:10:00:00 +0900] "GET /reset?token=abc123 HTTP/1.1" 200 512この場合、ログにトークンが平文で残る。
実態:
- POST でもボディをログに出力するよう設定されている場合は同様に残る
- 逆に GET でも、URL にセンシティブな値を含めなければ問題にならない
- APMツールやミドルウェアがボディを自動記録するケースもあり、POST でも安全とは言いきれない
4. Referer(リファラー)ヘッダーに含まれる
ページ内に外部サイトへのリンクがある場合、ブラウザはリンク元の URL を Referer ヘッダーに付けてリクエストを送る。
GET https://external-site.example.com/something
Referer: https://myapp.example.com/search?query=secret_value実態:
Referrer-Policyヘッダーで制御可能- POST でも、フォーム submit 後の遷移先ページで
document.referrerにリクエスト元が残ることがある
POST にすれば解決するのか
| リスク | GETでの状況 | POSTにした場合 | 正しい解決策 |
|---|---|---|---|
| ブラウザキャッシュ | キャッシュされやすい | デフォルトではキャッシュされにくい | Cache-Control: no-store |
| ブラウザ履歴 | URLが残る | 残らない | URLにセンシティブな値を入れない |
| サーバーログ | URLが記録される | ボディは通常記録されない(設定次第) | ログ設定を見直す / URLに機密値を含めない |
| Referer | クエリが漏洩する | 漏洩しない | Referrer-Policy: no-referrer |
| 通信盗聴 | HTTPS必須 | HTTPS必須(POSTでも同じ) | HTTPS を必ず使う |
POSTは「ブラウザ履歴に残らない」「通常ログにボディが出ない」という点で一定の優位性はある。しかし POST に変えるだけでは解決しないリスクも多く、POST で安心してしまうことで本来すべき対策が抜け落ちるリスクがある。
HTTPメソッドの選択基準
GETとPOSTはセキュリティのためではなく、操作の意味で選ぶのが正しい。HTTP仕様(RFC7231)での定義に基づく。
GETを使うべき場合
- データの取得(副作用がない操作)
- 冪等な操作(何度実行しても結果が変わらない)
- ブックマーク・共有ができる必要がある
- キャッシュを活かしてパフォーマンスを上げたい
GET /users/123 → ユーザー情報の取得
GET /search?q=typescript → 検索
GET /products?page=2 → 一覧取得POSTを使うべき場合
- リソースの作成(新規登録、送信など)
- 副作用がある操作(決済、メール送信など)
- 冪等でない操作(同じリクエストを2回送ったら2件作成されてしまうもの)
- リクエストボディが大きい(URLの文字数制限を超える場合)
POST /users → ユーザーの新規作成
POST /orders → 注文の確定
POST /messages → メッセージの送信「セキュリティのためにPOST」の誤用例
// ❌ 誤用:検索なのにPOSTにしている(キャッシュを避けたいだけ)
POST /api/search
Content-Type: application/json
{ "query": "typescript" }
// ✓ 正しい:検索はGETで、Cache-Controlで制御する
GET /api/search?q=typescript
Cache-Control: no-store // センシティブな検索結果ならここで制御本当に必要なセキュリティ対策
1. Cache-Control ヘッダーの設定
キャッシュを防ぎたい場合は、メソッドに関わらず明示的にヘッダーを付ける。
Cache-Control: no-store| ディレクティブ | 意味 |
|---|---|
no-store | キャッシュに一切保存しない(最も強い) |
no-cache | 必ずサーバーに再検証してから使う |
private | ブラウザのみキャッシュ可。プロキシには保存しない |
public | 共有キャッシュ(CDN等)に保存を許可 |
センシティブなページ・APIレスポンスには no-store を付けることが基本。
2. センシティブな値を URL に含めない
メソッドに関わらず、パスワード・トークン・個人情報はURLに含めない。
// ❌ NG:トークンがURLに露出する
GET /reset-password?token=abc123
// ✓ OK:トークンをリクエストボディやヘッダーに含める
POST /reset-password
{ "token": "abc123", "newPassword": "..." }
// ✓ OK:認証トークンはAuthorizationヘッダーで送る
GET /me
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...3. HTTPS を必ず使う
GET・POST どちらであっても、HTTP(非暗号化)で通信している場合は通信内容がすべて平文で流れる。TLSは選択肢ではなく必須。
// HTTP(危険)
http://example.com/api/users // URLもボディも平文
// HTTPS(安全)
https://example.com/api/users // URLもボディも暗号化される4. Referrer-Policy ヘッダーの設定
外部リンクがある場合、リファラー経由のURL漏洩を防ぐ。
Referrer-Policy: no-referrer-when-downgrade| 値 | 意味 |
|---|---|
no-referrer | Refererを一切送らない |
no-referrer-when-downgrade | HTTPS→HTTPの遷移時は送らない(デフォルト) |
strict-origin | オリジンのみ送る(パスを送らない) |
strict-origin-when-cross-origin | 同一オリジンはフル URL、クロスオリジンはオリジンのみ |
5. サーバーログの管理
認証トークンや個人情報がアクセスログに残らないよう、ログの出力設定を見直す。
# nginx でクエリストリングを除いたログを出力する例
log_format masked '$remote_addr - $remote_user [$time_local] '
'"$request_method $uri" $status $body_bytes_sent';
access_log /var/log/nginx/access.log masked;GETとPOSTの選択フロー
この操作は何か?
↓
リソースを取得するだけ(副作用なし)?
Yes → GET を使う
↓
センシティブなデータをレスポンスに含む?
Yes → Cache-Control: no-store を付ける
No → キャッシュを活用してもよい
↓
No(データを作成・変更・削除する / 副作用がある)→ POST/PUT/DELETE を使う
↓
リクエストにセンシティブな値を含む?
Yes → URLやクエリに含めず、ボディまたはヘッダーで送る
No → 通常の設計でよいGETが適切な具体例
以下は「センシティブそうに見えるがGETが正解」なケース。
検索API
GET /api/articles?category=security&page=1
Cache-Control: no-store // 個人化された検索結果の場合はキャッシュを防ぐ
Authorization: Bearer {jwt} // 認証はヘッダーでユーザー自身の情報取得
GET /api/me
Authorization: Bearer {jwt}
Cache-Control: private, no-storeこれらはデータを「取得するだけ」なのでGETが正しい。認証はAuthorizationヘッダーで行い、キャッシュはヘッダーで制御する。
よくある落とし穴
「POSTなら安全」という過信
POST にしても Cache-Control ヘッダーを付けなければ、一部のキャッシュサーバーはレスポンスをキャッシュする場合がある。また、ボディをログに記録するAPMツールや開発用ミドルウェアを本番に残してしまうと POST でも情報漏洩する。
CSRF との関係
POST は CSRF 攻撃を防ぐわけではない。状態を変更する操作(POST/PUT/DELETE)に対しては、CSRFトークンまたは SameSite Cookie の設定が別途必要。
GETを「読み取り専用(副作用なし)」に徹底することで、CSRF対策を POST 系のみに絞りやすくなるという副次的なメリットはある。
ブラウザバックの二重送信
POSTで決済・注文などを実行した後、ブラウザバックをしてリロードすると「フォームを再送しますか?」というダイアログが表示される。GET に変えれば問題はなくなるが、副作用のある操作を GET で実装することはできない。
正しい対処は PRG パターン(Post/Redirect/Get) を使うことで、POST 後に GET のページへリダイレクトし、リロードしても再送信されない構造にする。
POST /orders → 注文処理(副作用あり)
↓ 302 Redirect
GET /orders/123 → 注文完了ページ(キャッシュ可)まとめ
| 観点 | 正しい考え方 |
|---|---|
| GETはキャッシュされる | Cache-Control で制御できる。POST に変えるだけでは解決しない |
| センシティブな操作にはPOST | 副作用があるならPOSTが正しい。ただしキャッシュ対策は別途必要 |
| URLに機密情報を含めたくない | メソッドに関わらず、URLに機密値を含めない設計が基本 |
| 通信を安全にしたい | HTTPS(TLS)を使う。これはGET/POSTに関係なく必須 |
| ログに残したくない | ログ出力の設定を見直す。POSTでも記録されうる |
HTTPメソッドの選択はセキュリティではなく操作の意味で決める。その上で、必要なセキュリティ対策(ヘッダー設定・HTTPS・ロギング管理)を別途適切に組み合わせるのが正しいアプローチ。