REST原則と実装時の注意点 「REST WebAPI サービス 設計」 備忘録

RESTとは

REpresentational State Transferの略です。
分散型システムにおける設計原則群のことで、簡単に言うと設計ルールのことです。

REST制約の原則としては6種類あります。
※論文には7種類の記述がありますが、RESTの定義に直接的な関係はないため割愛いたします。

  • クライアント/サーバー
  • ステートレス
  • キャッシュ制御
  • 統一インターフェイス
  • 階層化システム
  • コードオンデマンド

REST制約

クライアント/サーバー

画面とデータという形で責務を分離させ、拡張性の向上と単純化を行います。

ステートレス

クライアントからサーバーへの要望は、その要望が満たされるために必要なすべての情報を含めるようにします。
サーバーはリクエストのみでその要望を理解できるということです。
サーバーセッションは使わず、クライアントで状態を保持し、リクエストの際にその状態をすべて含めてリクエストします。

キャッシュ制御

クライアント側でレスポンスをキャッシュできるようにすることを指します。
適切なキャッシュはUXの向上、リソース効率の向上、拡張性の向上が見込めます。

統一インターフェイス

あらかじめ定義・共有された方法でやり取りするということです。

リソースの特定

URIを用いてサーバーのデータを特定することを指します。
※「latest」や「famous」といった抽象的なものも含みます

表現を用いたリソース操作

レスポンスやPOSTデータといったリソースの断面を「表現」とし、それを利用してサーバー上のデータを操作することを指します。
また、認証情報などの追加情報も含めて考えます。
cache-controlでキャッシュの有無を明示しましょう。

自己記述メッセージ

メッセージの内容が何なのかがヘッダーに記述されていることを指します。
例: Content-Type

アプリケーション状態エンジンとしてのハイパーメディア(HATEOAS)

レスポンスの現状を踏まえ、関連のハイパーリンクが含まれることを指します。
※HATEOASはHypermedia as the Engine of Application Stateの略。

そのリンクを辿ることが、アプリケーションの状態遷移となります。
例えばECサイトで注文取消しの可否を判定するのに、「注文取消し用のURI」の情報がレスポンスに含まれているかどうかで判断するというのが挙げられます。

階層化システム

多層アーキテクチャ構成のことを指します。
責務を分けて独立させ役割を明確にすることで、進化と再利用の促進ができます。

コードオンデマンド

プログラムコードをサーバからダウンロードしてクライアント側で実行できるようにすることを指します。
サーバー主導で新規機能を追加することが可能になります。

REST API 成熟度レベル

REST の思想にどのくらい準拠できているかを表す指標のことです。
LEVEL 0~LEVEL 3があります。
LEVEL 2を満たしているシステムが多いです。

LEVEL 概要
LEVEL 0 HTTPを使用している状態。
LEVEL 1 リソースの概念が導入されている状態。
LEVEL 2 HTTPメソッドが適切に使用されている状態。
LEVEL 3 HATEOASの概念を導入されている状態。

REST API 作成時のhow to

URI設計

短く入力しやすく人間が読んで理解できるようにする

重複せず、シンプルで覚えやすい、人間が読んで理解できるものにします。

https://api.example.com/api/service/fds/search // ×:apiの重複。serviceは不要。fdsが何を指すかわからない。
https://example.com/foods/search // 〇

すべて小文字で、単語はハイフンでつなげる

小文字でルール化することで統一しやすくなります。
単語はハイフンでつなぐよう統一します。

https://example.com/famous_foods // ×
https://example.com/famous-foods // 〇

// さらに良いのは↓
https://example.com/foods/famous

可算名詞は複数形で

リソースの集合体となるため複数形で記述しましょう。

エンコードしなくて良い文字で

エンコードされてしまうと上記の「人間が読んで理解できるものにする」というものに反することになります。
ですので基本はエンコードしなくて良い英語で記述します。

サーバーのアーキテクチャの内容を反映させない

拡張子や使用しているWebサーバーの名称などをURIに使用しないようにしましょう。
悪意を持った利用者にサーバー側の情報を渡すことはセキュリティ上良くありません。
また、URIを読んで理解する際に不要な情報です。
そういった情報は含めないようにしましょう。

クエリパラメータとパスパラメータの使い分け

クエリパラメータを使うのは省略可能な場合です。
例えば複数項目での検索機能はクエリパラメータが向いています。

パスパラメータを使うのは一意のリソースであること、省略できない場合です。
例えばユーザーの詳細画面はパスパラメータが向いています。

HTTPメソッド

HTTPメソッドは、URIによって行われる処理によって使い分けを行います。

メソッド 対応する処理
GET リソースの取得
POST リソースの新規作成
PUT リソース全体の置き換え
DELETE 指定したリソースの削除
PATCH リソースの部分的変更
HEAD レスポンスヘッダのみ取得
OPTIONS リクエスト可能なHTTPメソッドの問い合わせ
CONNECT プロキシサーバに対し目的のWebサーバーへのトンネルを確立するよう依頼する
※接続先がHTTPSの場合に用いる
TRACE リクエスト内容をそのままクライアントに送り返す
デバッグなどに用いる

同じURIでもHTTPメソッドを変えることで処理を変えることができます。

URI設計とそれに対応するHTTPメソッドの例

ここでは実際にmovieをリソースとしてCRUD処理のURI設計とHTTPメソッドの割り当てを行ってみます。

処理 CRUD URI HTTPメソッド
アップロードする CREATE https://example.com/movies POST
movieの一覧を取得する READ https://example.com/movies GET
有名なmovieの一覧を取得する READ https://example.com/movies/famous GET
再生する(再生ページに移動する) READ https://example.com/movies/1 GET
更新する UPDATE https://example.com/movies/1 PUT
削除する DELETE https://example.com/movies/1 DELETE
特定の制作会社のmovieを削除する DELETE https://example.com/companies/1/movies DELETE

ステータスコード

ステータスコードは、処理結果の概要を通知するものです。
下記では大まかな分類と大まかな説明をします。それぞれ細かなステータスコードこちらで確認してください。

ステータスコード 説明
100番台 情報
200番台 成功
300番台 リダイレクト
400番台 クライアント側のエラー
500番台 サーバー側のエラー

レスポンスデータ構造について

必要なデータだけを返す

レスポンスボディには必要な情報のみ返しましょう。
ステータスコードのようにレスポンスヘッダーに含まれるような内容は、重複を避けるためにもレスポンスボディには記述しないようにしましょう。

オブジェクトはネストせず階層を浅くする

構造が複雑になるとデータ容量の増加と可読性の低下を引き起こします。
可能な限りフラットにしましょう。

ページネーションのための情報を返す

HATEOASのようにリンクを渡す方法を採用すると、同時刻でほかのユーザーがリソースを追加・削除していた場合に、 同じ情報が表示されたり表示したい情報が消えていたりする不都合があります。
それを解決するためには次ページが存在することを示すフラグと次ページはどこから表示すればよいかキーとなるものを用いる方法を使います。

日付はRFC3339形式を使う

RFC3339 2024-01-06T17:00:00+09:00

大きな数字は文字列で扱う

通常の整数は32bit整数で64bit整数は処理しきれないので、大きな数字は文字列で扱いましょう。

エラーの扱い

エラー詳細の扱い

ステータスコードのみだとどういったエラーなのか分からないため、エラーの詳細はレスポンスボディに入れましょう。
ですが、詳細に伝えすぎるとセキュリティ面で脅威を生む可能性があるので、概要のみ渡すようにしましょう。

エラー時にHTMLを渡さないようにする

渡す情報はXMLJSONなど定まった形式で返すようにしましょう。

サービスを閉じている際のエラー表現

ステータスコードは503でRetry-Afterにシステム再開日時を設定し、いつ再開するかを提示してあげるようにしましょう。

レートリミット(大量アクセス対策)

短期間の大量アクセスからサーバーを守る手段として使用します。
時間あたりのアクセス数を制御することで、悪意のあるボットや開発時に紛れ込んだ予期せぬ大量アクセスの対策になります。

ただ、一時的に大量アクセスを許容するような場合は一時的にレートリミットを緩和する必要があることもあります。

キャッシュ制御

キャッシュ制御に利用するヘッダーは有効期限による制御と検証による制御があります。

有効期限による制御

  1. Expires ヘッダー

  2. 利用期限を指定

  3. 過去日の指定で「有効期限切れ」を表現可能
  4. Cache-Control が指定されている場合は無視される

  5. Cache-Control と Date

  6. キャッシュの可否と期限を指定可能
可否の設定値 説明
public 通信経路上のどこからでも保存可能
private クライアント端末のみ保存可能
no-cache 有効性の確認を行ったうえで保存可能
クライアント側で保存します
no-store 保存不可
クライアント側で保存されないため常に通信が発生します
期限の設定値 説明
max-age=<秒> Dateヘッダーから何秒後に無効になるかを設定します

検証による制御

Last-Modified + ETag

  • Last-Modifiedヘッダーにリソースの最終更新日時を指定します
  • ETagヘッダーに特定バージョンを示す文字列を指定します(コンテンツハッシュ、バージョン番号、最終更新日時のハッシュなど)

キャッシュ単位

  • Varyヘッダーで他ヘッダー名を指定し、そのヘッダーごとにキャッシュします

セキュリティ

XSS

クロスサイトスクリプティング
悪意のあるユーザーが正規サイトに不正なスクリプトを挿入し、ユーザー情報の不正に取得・操作をすること。

対策

レスポンスヘッダーの追加
  • X-XSS-Protection : "1"でXSSフィルタリングを有効化する
  • X-Frame-Options : "DENY"でframeタグ呼び出しを拒否する

CSRF

クロスサイトリクエストフォージリ
Webアプリケーションのセッション管理の脆弱性を利用した攻撃方法で、Webサーバーに不正なリクエストを送信します。

対策

アクセス元を制限する
  • X-API-Key : システム単位で実行可否判断を行う
  • Authentication : ユーザー単位で実行可否判断を行う
    攻撃者から推測されにくいトークンの発行と照合処理を実装する

    *X-CSRF-TOKEN : トークンを使って実行可否判断を行う

JSON Web Token(JWT)

JWTとは

読み:ジョット
RFC 7519で標準化されています。
JSON形式で表したURL-safeなデータで、署名を用いてデータが改ざんされているかをチェックするために使われます。
認証結果をクライアントサイドで保持するような場面で利用されます。

構造はbase64UrlEncoeした、「ヘッダー」「ペイロード」「署名」を「.(ピリオド)」で結合した形式になっています。

部分 説明
ヘッダー 署名に用いるアルゴリズムなどを定義します。
ペイロード 保存するデータを格納します。発行・利用システムの識別子や有効期限などを指定します。
署名 改ざん防止に用いる署名部分です。ヘッダーのalgに指定したアルゴリズムや、アルゴリズムに合わせた鍵を指定します。

JWTにおけるセキュリティについて

クライアント側で内容の確認と編集ができるため、サーバー側での検証が不十分であれば改ざんされたデータを使用して不正アクセスなどを可能にしてしまいます。

対策

  • ヘッダーのalgにnone以外を指定し、署名を暗号化する
  • ペイロード部分のaudに想定する利用者を指定して受信時に検証を行う