Web API - kin-kin/memo GitHub Wiki

Home

Reference : Web API: The Good Parts(水野 貴明、O'REILLY)

1. Web API

1.1. Web APIを美しく設計する重要性

  • 設計の美しいWeb APIは使いやすい
  • 設計の美しいWeb APIは変更しやすい
  • 設計の美しいWeb APIは頑強である
  • 設計の美しいWeb APIは恥ずかしくない

1.2. Web APIを美しくするには

  • 仕様が決まっているものに関しては仕様に従う
  • 仕様が存在していないものに関してはデファクトスタンダードに従う

1.3. RESTとWeb API

<RESTの意味>

  1. Roy FieldingのRESTアーキテクチャスタイルの原則に合わせたウェブサービスシステム
  2. RPCスタイルに合わせた簡易なXML(やJSON) + HTTPインターフェースを採用したシステム(SOAPは使わない)

Web APIは2番目の定義におけるRESTなAPIだが、極力RESTという言葉は使わない。

2. エンドポイントの設計とリクエストの形式

2.1. APIとして公開する機能の設計

  • 公開したAPIがどのように使われるのか、そのユースケースをきちんと考える
    • ユースケースとエンドポイントは一対一ではない

2.2. APIエンドポイントの考え方

  • 短く入力しやすいURI(= シンプルで覚えやすい)
    • 不要な情報、重複を避ける
  • 人間が読んで理解できるURI
    • むやみに省略形を使わない
      • ただし、標準化され「コード」として体系化されたもの(日本jp、日本語ja、日本航空JL、等)を除く
    • APIでよく使われている英単語を利用する
      • ProgrammableWeb等で複数のAPIを見比べて、最もよく使われている、最もしっくりくる単語を選ぶ
    • スペルミスをしない
  • 大文字小文字が混在していないURI
    • 基本はすべて小文字を使う
      • HTTPにおいてURIは「スキーマとホストを除いては大文字と小文字は区別される」(RFC 7230)仕様のため、大文字を含む場合は404エラーを返す
      • ホスト名の部分は大文字小文字は無視される仕様
  • 改造しやすい(Hackableな)URI
    • /items/:idのように、アイテムIDを変えると別のアイテムの情報にアクセスできることが予想できる、等
    • サーバ側の都合はサーバ内で処理をして、利用者にそれを意識させない
    • Martin FowlerのREST LEVEL3 - HATEOAS(hypermedia as the engine of application state)の概念はこの限りではない
      • 入口となるエンドポイントさえ知っていれば、提供されるAPIの機能にすべてアクセスできる
      • セキュリティなどの観点から、アクセスしてほしくないURIを想像しにくくしたい場合は有効
  • サーバ側のアーキテクチャが反映されていないURI
    • ⇔ SQLをラップしただけのもの、ソフトウェア・言語・ディレクトリ・システム構成がわかるもの(/xxx.php、等)
    • URIが反映すべきは機能やデータの構造と意味
  • ルールが統一されたURI
    • 単数形と複数形、URIのパスとクエリパラメータ、等
  • ホスト名にapiを入れるのが主流(api.example.com、等)
    • ホスト名を分けることでアクセスをDNSレベルで分割できるので管理がしやすい
    • サービスが提供されているドメインと同じドメイン名でAPIを提供した方がわかりやすい
  • LSUDs(large set of unknown developers)向けのAPIでは、なるべく汎用的で、わかりやすく使いやすいAPIの設計が最も重要
  • SSKDs(small set of known developers)向けのAPIでは、エンドユーザーにとってのユーザー体験が重要
    • 1画面に複数のAPIではなく、1つのAPIにする、等
      • 速度の問題とデータの整合性の問題への対応

2.2.1. リソースにアクセスするためのエンドポイントの設計の注意点

  • 複数形の名詞を利用する
    • データベースのテーブル名と同様に、「リソースの集合」を表す(users、friends、等)
  • 利用する単語に気をつける
    • 何かを探す場合はfindではなくsearch、場所の情報はvenue、項目はitem、写真はphoto、等
      • searchは動詞だが、検索用APIを強調するためには間違った考え方ではない
    • 自分の情報へのエイリアスとしてmeやselfを使い、ユーザーID指定の手間を省く
  • スペースやエンコードを必要とする文字を使わない
    • スペースの+変換、パーセントエンコーディングされたURIで利用できない文字、等
  • 単語をつなげる必要がある場合はハイフンを利用する
    • profile-image(スパイナルケース)/profile_image(スネークケース)/profileImage(キャメルケース)
    • アンダースコアが使えず、大文字小文字の区別がないホスト名のルールに統一する
    • 最もよいのは単語をつなぎ合わせることを極力避ける

2.3. HTTPメソッド

  • 1つのURIのエンドポイントに異なるメソッドでアクセスすることで、情報の取得・変更・削除とさまざまな操作を行う
    • エンドポイントはリソース、メソッドは操作
    • 「あるデータの集合」と「個々のデータ」をエンドポイントとして表現し、それに対してHTTPメソッドで操作を表していく
  • GET = リソースの取得
    • サーバ上のリソースが変更されることは基本的にありえない
  • POST = リソースの新規登録
    • 送信したデータは指定したURIに従属(/friendsに/friends/12345が登録される)
      • 既に存在する場合はエラー(PUTと区別)
  • PUT = 既存リソースの更新
    • 送信するデータでもともとのリソースを完全に上書き(/friends/12345のURIに変更なし)
      • まだ存在しない場合はエラー(POSTと区別)
  • DELETE = リソースの削除
  • PATCH = リソースの一部変更
  • PUT/DELETE/PATCHが利用できない環境のために、POSTで代替する
    1. X-HTTP-Method-Overrideヘッダ
    2. Content-Type: application/x-www-form-urlencoded + _methodパラメータ
    • これらはサーバ側のフレームワークやミドルウェアが標準サポートしている場合も多い

2.4. 検索とクエリパラメータの設計

  • page/per_pageとoffset/limitのクエリパラメータでページネーションを実現する
    • pageは1から(1-based)、offsetは0から(0-based)
    • 混在せず、どちらかに統一する
    • 絶対位置でデータを取得するパラメータも用意する
      • 最後のデータのIDや時刻から、「それよりも前のもの」「それよりも古いもの」を指定
      • 相対位置指定におけるパフォーマンスの問題や、データ追加・削除による不整合を防ぐ
  • 複数の項目での絞り込み検索は、項目をクエリパラメータで指定する
  • 絞り込み検索の項目が1つの場合は、クエリパラメータqを使用するケースが多い
    • qはquery、部分一致も許すイメージが強い
    • メインの項目をq、それ以外を別のパラメータにするケースもある
  • 検索項目をURI中のパスの部分に入れることも可能
    • 一意なリソースを表すのに必要な情報の場合はパスが適切
    • 省略可能な情報の場合はクエリパラメータが適切(page、offset、limit、等)

2.5. ログインとOAuth 2.0

  • ログイン時のエンドポイントは/oauth2/tokenが妥当
  • ユーザー名とパスワードをアプリ内で入力して認証する場合、Resource Owner Password CredentialsのGrant Typeを利用する
    1. Authorization Code
    2. Implicit
    3. Resource Owner Password Credentials
      • Content-Type: application/x-www-form-urlencoded、文字コードはUTF-8
      • grant_type=password&username=ユーザー名&password=パスワード&scope=スコープ
    4. Client Credentials

3. レスポンスデータの設計

3.1. データフォーマット

  • JSONにデフォルトとして対応し、需要や必要があればXMLに対応する
  • データフォーマットの指定方法は、1つだけを選ぶならURIでクエリパラメータを使う方法を、複数選ぶならメディアタイプをヘッダで指定する方法とクエリパラメータを使う方法を両方サポートする
    1. クエリパラメータを使う方法(format=xml、等)
    2. 拡張子を使う方法(user.json、等)
    3. リクエストヘッダでメディアタイプを指定する方法(Accept: application/json、等)
  • 必要であればJSONPに対応する
    • XHTTPRequestは同一生成元ポリシーの制限があるが、script要素は規制の対象外
      • ただし、GETしか使えず、HTTPヘッダを独自に設定できない
    • Javascriptのコールバック関数を返す
      • Javascriptのグローバル変数を返すことも可能だが、一般的ではない
      • コールバック関数の名前はクエリパラメータで指定(callbackが一般的)できるようにしておく
      • クエリパラメータにコールバック関数の指定があればJSONPを要求しているとみなす
    • Content-Typeヘッダにapplication/javascriptを指定する
    • エラーが発生しても200のステータスコードを返し、レスポンスボディでエラーの内容を表現する

3.2. データの内部構造

  • ユースケースに沿った形で、できるかぎり少ないアクセス回数ですむAPI設計を心がける
    • ⇔ Chatty API
  • クエリパラメータを使って、取得する項目を利用者が選択可能にする(fields=lang,url、等)
    • 省略した場合はすべての情報、あるいは最も利用頻度が高いと思われる組み合わせ
    • 項目のセットをレスポンスグループとして指定させてもよい
  • エンベロープは冗長な表現であるため、やるべきではない
    • ただし、JSONPを除く(HTTPのステータスコードやレスポンスヘッダにアクセスできないため)
  • データはなるべくフラットにして、必要な場合だけ階層化する
  • 配列はそのまま返さず、オブジェクトで配列を包む
    • レスポンスデータが何を示しているものかがわかりやすくなる
    • レスポンスデータをオブジェクトに統一することができる
    • セキュリティ上のリスク(JSONインジェクション)を避けることができる
  • ページネーションで続きがあるのかという情報はhasNextパラメータで返す
    • サーバ側の実装では、必要な件数+1件を取得してみる
    • 次のページのURIや、次のページの取得に必要なパラメータを返すというパターンもある

3.3. 各データのフォーマット

  • 多くのAPIで同じ意味に利用されている一般的な単語を用いる
  • なるべく少ない単語数で表現する
    • 何かをした時間を表すときはatを付けて表すケースが多い(updatedAt、等)
  • 複数の単語を連結する場合、その連結方法はAPI全体を通して統一する
    • JSONがベースとしているJavaScriptでは、キャメルケースの利用がルール付けされているケースが多い
  • 変な省略形は極力利用しない
  • 単数形/複数形に気をつける
  • 性別情報は生物学的な性別が必要な場合はsexを使い、そうでなければgenderを使う
    • genderを選んだ場合は、データ形式を文字列とする(male、female、等)
    • sexを選んだ場合は、基本的に男性・女性・不明の3種類のため、数値でもよい
  • 日付はRFC 3339を使う(2015-10-12T11:30:22+09:00、等)
    • HTTPヘッダで用いられるHTTP時間においてはUTCが採用されていることから、タイムゾーン+00:00を使うのがわかりやすい
  • 大きな数値は文字列として返す
    • 取り扱うシステムによる桁あふれや誤差の問題を回避するため
  • 返すデータ構造をオブジェクトとして定義すると、わかりやすくシンプルになる

3.4. エラーの表現

  • まずは適切なステータスコードを返す
    • 100番台:情報、200番台:成功、300番台:リダイレクト、400番台:クライアントサイドに起因するエラー、500番台:サーバサイドに起因するエラー
    • リクエストが成功した場合しか200番台のリクエストは返してはならない
    • ぴったりくるステータスコードが存在しなかった場合には200や400、500といった00で終わるステータスコードを付ける
  • エラーの詳細はレスポンスボディで返す
    1. HTTPのレスポンスヘッダ
    2. レスポンスボディ
      • 複数のエラーは配列で返す
  • 詳細コードはたとえば4桁の数字にして、1000番台は汎用的なエラー、2000番台はユーザー情報のエラーというように、カテゴリ分けをする
  • ウェブサーバの設定などもチェックして、APIの実装内部でエラーが発生したり、負荷が高くなったり、存在しないエンドポイントにアクセスした場合でも、きちんと適切なフォーマットでデータが返るようにしておく(HTMLが返ることを防ぐ)
  • メンテナンス時は、定期メンテナンス用のエラーコードとエラーメッセージを用意してそれを返すだけでなく、Retry-AfterというHTTPヘッダを使っていつメンテナンスが終わるのかを示す
  • ブロックした相手や悪意のあるユーザなど、意図的に不正確な情報や曖昧なエラーを返すほうが安全なケースもある

4. HTTPの仕様を最大限利用する

4.1. ステータスコード

  • HTTPではステータスコードの少なくとも1桁目に基づいて振る舞いを変えるべきとされている
  • 指定したデータの取得に成功した、あるいはリクエストした処理が成功した場合には200番台のステータスコードを返す
    • リクエストメソッドとしてPOSTが使われた場合は201(Created)を返す
    • リクエストした処理が非同期で行われ、処理は受け付けたけれど完了していない場合は202(Accepted)を返す
    • PUTやPATCHの場合は200(OK)とともに操作したデータを返す(204を返すほうが自然であるという意見もある)
    • DELETEの場合は204(No Content)を返す
  • あるURIへのアクセスに対して、目的の情報は別のURIで表示されることを伝える場合には300番台のステータスコードを返す
    • リダイレクトの場合はLocationというレスポンスヘッダにリダイレクト先の新しいURIが含まれる
    • リダイレクトが発生するとクライアントからのアクセスの回数も増えてしまうため、定常的にリダイレクトが発生するようなAPIを作ることはあまり好ましくない
      • 複数のデータを何らかの理由で1つに統合した場合で指定したリソースが別のURIで提供されている場合
      • ファイルストレージ系のサービスなどで指定したキーに対して複数のデータが存在する場合は300(Multiple Choices)を返す
    • 前回のデータ取得から更新されていない場合は304(Not Modified)を返す
      • クライアント側でデータのキャッシュを行い、キャッシュの情報を返してくれたときだけ
      • 304が返った場合はレスポンスボディは空になる
  • クライアントのリクエストに起因するエラーの場合は400番台のステータスコードを返す
    • 送られてきたパラメータに間違いがあって処理が続行できないときなどは400(Bad Request)を返す
    • APIにトークンなしにアクセスした場合には401(Unauthorized)を返す
    • 許可されていない権限でアクセスした場合には403(Forbidden)を返す
    • 存在しないデータや存在しないエンドポイントにアクセスした場合には404(Not Found)を返す
    • エンドポイントは存在しているがメソッドが許可されていない場合には405(Method Not Allowed)を返す
    • クライアントが指定してきたデータ形式にAPIが対応していない場合には406(Not Acceptable)を返す
    • リクエストをクライアントがサーバに送るのに時間がかかりすぎてサーバ側でタイムアウトを起こした場合は408(Request Timeout)を返す
    • 指定したIDなどのリソースの競合が発生した場合には409(Conflict)を返す
    • かつて存在したけれども存在しないデータや存在しないエンドポイントにアクセスした場合には410(Gone)を返す
      • ただし、個人情報保護の観点からは問題があるため404を使用する
    • リクエストボディが長すぎる場合には413(Request Entity Too Large)を返す
      • ファイルのアップロードを行うAPIにおいて、許容されるサイズ以上のデータが送られてきたとき、等
    • リクエストヘッダが長すぎる場合には414(Request-URI Too Long)を返す
      • GET時のクエリパラメータに長すぎるデータが指定されたとき、等
    • クライアントが送ってきたリクエストボディのデータ形式をサーバが対応していない場合には415(Unsupported Media Type)を返す
    • アクセス回数が許容範囲の限界(レートリミット)を超えた場合には429(Too Many Requests)を返す
  • サーバ側に問題があった場合は500番台のステータスコードを返す
    • サーバ側のコードにバグがあってエラーを吐いて処理が停止してしまった場合には500(Internal Server Error)を返す
    • 意図的にせよ(メンテナンス)、意図的でないにせよ、サーバが一時的に利用できない場合には503(Service Unavailable)を返す
  • キャッシュはユーザー体験にも両者のコストにも大きく影響を与えるため、可能なかぎり有効活用すべき
    • HTTPではキャッシュが利用可能な状態をfresh、そうでない状態をstaleと呼ぶ
  • Expiration Model(期限切れモデル)では、いつ期限が切れるかをサーバからのレスポンスに含めて返す
    1. Expiresレスポンスヘッダ
      • HTTP 1.0から定義、絶対時間でRFC 1123で定義された形式で表す
      • 特定の日時に更新されることがあらかじめわかっているデータに使用(最大1年後)
    2. Cache-Controlレスポンスヘッダ
      • HTTP 1.1から定義、現在時刻からの秒数で表す
      • Dateヘッダが基準(HTTPの仕様ではいくつかの例外を除き、必ず付ける必要がある)
      • 更新頻度がある程度限られているものや、更新頻度は低くないものの、あまり頻繁にアクセスしてほしくない場合に使用
    • ExpiresとCache-Controlを同時に利用した場合には、より新しい仕様であるCache-Controlが優先される
    • キャッシュをさせたくない場合は、Cache-Control: no-cacheを使う
      • 中継するプロキシサーバには保存をしてほしくない場合は、no-storeを使う
      • Expiresで過去の日付や、日付として正しくない値を指定して期限切れを表すことがあるが、クライアントの挙動に依存するため利用しない
    • Varyヘッダで、URI以外にどのリクエストヘッダ項目をデータを一意に特定するために利用するかを指定する
  • Validation Model(検証モデル)では、今持っているキャッシュが有効かどうかをサーバに問い合わせる
    1. Last-Modifiedレスポンスヘッダ
      • 最終更新日時
      • クライアントはIf-Modified-Sinceリクエストヘッダを使う
      • APIでは特定のリソースの場合はそのリソース自体の最終更新日付、複数のリソースの場合はその中で最後に更新されたリソースの最終更新日付を利用
    2. ETagレスポンスヘッダ
      • フィンガープリントである文字列(レスポンスデータのMD5ハッシュなど)
      • ダブルクォーテーションで囲まれた任意の文字列を使うことができる
      • クライアントはIf-None-Matchリクエストヘッダを使う
      • APIでは最終更新日付やデータ全体を衝突の少ない関数(MD5やSHA1など)でハッシュ化したものを利用
    • 大きなデータをやりとりするような性格のAPIであればあるほど、キャッシュの効果が高まる
  • サーバの更新頻度や状況などを参考に、クライアントがキャッシュの期限を自分で決める場合がある
    • = Heuristic Expiration(発見的期限切れ)
    • 基本的にはサーバがCache-ControlやExpiresなどの情報をきちんと返してあげる
    • キャッシュすべき期間を返さない場合でも、Last-Modifiedなどの更新関係の情報はきちんと発信しておく

4.3. メディアタイプ

  • Content-Typeヘッダを使って適切なメディアタイプを指定する
    • x-で始まるメディアタイプを利用する場合、IANA に登録されているx-で始まらないメディアタイプが存在していないかを調べる
      • application/x-www-form-urencodedは例外
    • 新たなサブタイプを定義する場合、ベンダツリーを使う(application/vnd.団体名.xxx、等)
      • 標準化されたデータ形式を使って、独自のデータ形式を定義する場合、+に続けて記述する(application/rss+xml、等)

4.4. CORS

  • ブラウザでの利用をターゲットにしたAPIではCORSに対応する
    • Originリクエストヘッダの値では大文字小文字は区別される
    • Access-Control-Allow-OriginレスポンスヘッダにOriginリクエストヘッダと同じ生成元を入れて返す(許可しない場合は403エラーを返す)
    • プリフライトリクエストをOPTIONSメソッドを使って送信する
      • サーバはステータスコード200でAccess-Control-Allow-Origin/Access-Control-Allow-Methods/Access-Control-Allow-Headers/Access-Control-Max-Ageヘッダを返す
    • CookieヘッダやAuthenticationヘッダを使ってユーザー認証情報をクライアントが送ってきた場合、サーバはAccess-Control-Allow-Credentials: trueヘッダを返す

4.5. 独自のHTTPヘッダ

  • HTTPヘッダを新しく定義する場合はX-という接頭辞を最初に付けて、次にサービスやアプリケーション、組織などの名前を付ける
    • HTTPヘッダは仕様的には大文字小文字は同一のものとして扱われるが、先頭や単語のつなぎを大文字にするパスカルケースを使い、単語間はハイフンでつなぐのが一般的
    • X-を付けるべきではないという議論もあるが、重要なのはサービスで方針を統一すること

5. 設計変更をしやすいWeb API

  • データの形式が変わったり、情報の検索に追加でパラメータが必要になったり、といった場合にはAPIの変更が必要になる(数値として公開されていたIDを文字列に変更した、項目を廃止した、項目を1つにまとめた、パラメータを変更した、セキュリティに対応した、等)
    • サービスのちょっとした見た目の変化や、コンテンツに影響があっても、コンテンツの形式そのものに影響がないケースではAPIは更新する必要がない(テキストとして返されるデータの内容がより詳細になる、内部のアルゴリズムのアップデートにより検索の精度がより向上した、等)
  • URIのパスの一番先頭にバージョン番号(v1、等)を埋め込む
    • バージョン番号は整数でカウントアップする
    • (バージョンをクエリ文字列に入れる方法や、メディアタイプで指定する方法もある)
    • 後方互換性を保つことが可能な変更は可能なかぎり同じバージョンでのマイナーバージョンアップで対応し、バージョンを上げるのは、どうしても後方互換性を保ったまま修正を行うことが難しい変更を加えなければならないときにのみ行う
      • 例えばレスポンスのgenderを1/2からmale/femaleに変える場合、genderは数値のまま残しておき、genderStrという新しい項目を付け加える、といった形の対応も可能(genderは廃止予定とマークする)
    • バージョンを指定しない場合に常に最新版を返すエイリアスは不要
      • エイリアスを提供する場合でも、バージョンを指定しないとサポートされる最も古いバージョンとみなされる、という仕様が理にかなっている
  • APIが公開終了になったとき、どういったことが起こるかをあらかじめ仕様に盛り込んでおく
    • 終了日時の継続的な告知やBlackout Testを行う
    • APIが公開終了した際にはステータスコード410(Gone)を返す
    • クライアント側にも組み込んでおく(強制アップデート、等)
    • 利用規約に保証(サポート期限、等)とサービス提供の範囲の明確化を明記する
  • サーバ側の汎用的なAPI(one-size-fits-all(OSFA))とクライアントの間にオーケストレーション層と呼ばれる、複数のAPIを1つにまとめたり、返すデータの量を調節したりするレイヤーを設けてもよい

6. 堅牢なWeb API

6.1. 通信データの暗号化

  • HTTPSを使うことで、APIの内容、エンドポイント、ヘッダに含められて送られるセッション情報などがすべて暗号化され、セッションハイジャックが行われる危険性をかなり少なくすることができる
    • 誰が行っても同じ結果が返る検索結果や特に隠すような情報がないAPIアクセスはHTTPを利用すると、速度の低下を抑えるのに有効

6.2. XSS対策

  • レスポンスヘッダに適切なContent-Typeを設定する
    • Content SniffingによりJSONをHTMLと解釈されることを防ぐには、IE8以降で実装されたX-Content-Type-Optionsレスポンスヘッダにnosniffを設定する
    • IE7以前のブラウザへの対策として、URIへの直接アクセスやSCRIPT要素での指定はリクエストヘッダを追加できないため、X-Requested-WithリクエストヘッダにXMLHttpRequestを設定し、サーバ側でチェックする
      • jQueryやRuby on RailsなどはX-Requested-Withに対応している
      • CORSの仕組みではX-Requested-Withを付けるとプリフライトリクエストが必要になる
    • IE7以前のブラウザへの対策として、JSON文字列をエスケープする(/ → /、< → \u003E、> → \u003C、+ → \u002B)

6.3. XSRF対策

  • サーバ側のデータが変化するようなアクセスに関しては、GETメソッドを利用せずPOSTやPUT、DELETEなどを用いる
  • XSRFトークンを発行し、そのトークンがないアクセスは拒否する
    • あらかじめトークンを取得させるような仕組みが必要
  • クライアントからX-Requested-Withなどの特別なリクエストヘッダを付けてもらう仕様を組み込み、そのヘッダが存在していなかった場合にはアクセスを拒否するという方法も利用できる

6.4. JSONハイジャック対策

  • JSONをSCRIPT要素では読み込めないようにするため、X-Requested-Withなどの特別なりクエストヘッダが付いているとき以外はアクセスを許可しない
  • JSONをブラウザが必ずJSONと認識するために、正しいメディアタイプをクライアントに返す
    • XSSと同様にContent Sniffingへの対策も必要
  • JSONをJavaScriptとして解釈不可能、あるいは実行時にデータを読み込めないようにするため、JSONの配列ではなくオブジェクトを返す
    • あるいは、JSONファイルの先頭に無限ループなど(for (;;);)を仕込んで読み込み時に処理が進まないようにする
      • JSONとして読み込む際にはこの部分を文字列操作により削除してから使う

6.5. パラメータの改ざん対策

  • 本来アクセスができないはずの情報はサーバ側できちんとチェックし、アクセスを禁止するようにしておく
  • レートリミットなどを利用してアクセスに制限をかけておく

6.6. リクエストの再送信対策

  • APIごとにそれが繰り返しアクセスされることによって問題が発生するかどうかを判断し、発生する可能性があるものについては状態を管理して、同じアクセスが何度も行われたらエラーにするなどのチェックを行う

6.7. セキュリティ関係のHTTPヘッダ

  • X-Content-Type-Options: nosniff
    • IE8で登場し、Content Sniffingを無効にする
    • IE9以降やChromeでは、SCRIPT要素で指定されたファイルにこのヘッダが設定してあり、さらにスクリプトを表すメディアタイプではなかった場合には、実行を行わずエラーになる
  • X-XSS-Protection: 1; mode=block
    • ブラウザが備えているXSSの検出、防御機能を有効にする
  • X-Frame-Options: deny
    • 指定したページがフレーム(FRAMEとIFRAME要素)内で読み込まれることを阻止する
  • Content-Security-Policy: default-src 'none'
    • APIの返すデータから他のリソースが読み込まれることを防ぐ
  • Scrict-Transport-Security: max-age=15768000
    • HTTPSでのアクセスの際にこのヘッダが送られてくると、ブラウザはmax-ageの期間中この情報を記録しておき、同じホストへのアクセスの際には必ずHTTPが指定されていても、HTTPSを使うようにする
  • Public-Key-Pins: max-age=2592000; pin-sha256="xxx"; pin-sha256="xxx"
    • SSL証明書が偽造されたものでないかをチェックする
  • Set-Cookie: session=xxx; Path=/; Secure; HttpOnly
    • Secure属性を付けると、そのクッキーはHTTPSでの通信の際のみにサーバに送り返される
    • HttpOnly属性はそのクッキーがHTTPの通信のみで使われ、ブラウザでJavaScriptなどのスクリプトを使ってアクセスすることができないものであることを示す

6.8. 大量アクセスへの対策

  • 単位時間あたりの最大アクセス回数(レートリミット)を決め、それ以上のアクセスがあった場合にエラーを返すようにする
    • レートリミットの単位は想定されるユースケースによって調整する
      • レートリミットを設ける理由はそもそも、短時間に大量のアクセスが行われることで負荷が増大し、サービスの継続が難しくなってしまうケースを想定してのこと
      • 正しくAPIを利用してくれる利用者が不便を感じないようにする
    • レートリミットの単位時間については、APIの内容によっても変わってくるが、現在公開されているAPIを見ると1時間くらいが多い
    • すべてのエンドポイントをまとめてのアクセス回数の上限を設けるのか、個々のエンドポイントで別々の上限を管理するのか
    • アクセス回数を制限する期間の開始時間をたとえば毎時0分のように決まった時間とするか、最初にAPIにアクセスをしたタイミングとするか(rolling window)
  • 制限値を超えてしまった場合、ステータスコード429(Too Many Requests)を返す
    • エラーの詳細をレスポンスに含めるべきである(SHOULD)
    • Retry-Afterヘッダを使って次のリクエストをするまでにどれくらい待てばよいかを指定してもよい(MAY)
  • レートリミットを行った場合は、現在のリミットアクセス数やどれくらいすでにアクセスしているのか、それがリセットされるのはいつかといった情報をユーザーに知らせる
    • レートリミットを取得する専用のエンドポイントを設ける
      • limit:一定期間でのアクセス可能回数
      • remaining:残りアクセス可能回数
      • reset:回数がリセットされる時間
    • レスポンスヘッダでレートリミットを渡す(デファクトスタンダード)
      • X-RateLimit-Limit:単位時間あたりのアクセス上限
      • X-RateLimit-Remaining:アクセスできる残り回数
      • X-RateLimit-Reset:アクセス数がリセットされるタイミング
  • 通常はRedisなどのKVSを使ってレートリミットを記録する
⚠️ **GitHub.com Fallback** ⚠️