Bulletproof API Design: 18のルール
Grace Collins
Solutions Engineer · Leapcell

1. Signature
APIインターフェースのデータを改ざんから保護するために、APIインターフェースに署名を実装することがよくあります。
APIリクエスターは、リクエストパラメータ、タイムスタンプ、および秘密鍵を文字列に連結し、MD5または他のハッシュアルゴリズムを使用して署名(sign
)を生成します。
この sign
は、リクエストパラメータまたはヘッダーに含められ、APIに送信されます。
APIゲートウェイサービス側では、ゲートウェイは sign
の値を取得し、同じリクエストパラメータ、タイムスタンプ、および秘密鍵を使用して、同じMD5アルゴリズムで別の sign
を生成します。次に、2つの sign
の値を比較します。
- 2つの
sign
の値が一致する場合、リクエストは有効であると見なされ、APIゲートウェイサービスはリクエストを適切なビジネスシステムに転送します。 - 2つの
sign
の値が一致しない場合、APIゲートウェイサービスは署名エラーを返します。
署名にタイムスタンプを含める理由
セキュリティを強化し、同じリクエストが繰り返し使用されるのを防ぐために、タイムスタンプが署名に含まれています。これにより、秘密鍵が解読される可能性も低くなります。各リクエストには、15分などの適切な有効期限が必要です。
したがって、リクエストは15分間有効です。15分を超えると、APIゲートウェイサービスはリクエストが期限切れになったことを示すエラーを返します。
現在、署名で使用される秘密鍵を生成する方法は2つあります。
- 固定秘密鍵(
privateKey
): 両当事者は、固定値を秘密鍵として合意します。 - AK/SKキーペア: APIプロバイダーは
AK/SK
ペアを割り当てます。SK
は署名で秘密鍵として使用され、AK
はリクエストヘッダーのaccessKey
として送信されます。 APIプロバイダーはAK
を使用してSK
を取得し、検証のために新しいsign
を生成します。
2. Encryption
場合によっては、APIインターフェースが高機密データ(ユーザーのログインパスワード、銀行カード番号、送金額など)を送信することがあります。このようなパラメータをプレーンテキストで公衆インターネット上に公開することは非常に危険です。
このリスクを軽減するために、暗号化を実装する必要があります。
たとえば、ユーザー登録インターフェースでは、ユーザーがユーザー名とパスワードを入力した後、パスワードを暗号化する必要があります。
一般的なアプローチは、AES対称暗号化を使用することです。
- フロントエンドでは、ユーザーのパスワードは公開鍵を使用して暗号化されます。
- 次に、登録APIは秘密鍵を使用してパスワードを復号化し、必要なビジネス検証を実行し、別の暗号化方法を使用して再度暗号化してから、データベースに保存します。
3. IP Whitelisting
APIセキュリティをさらに強化するために、IPホワイトリストを実装できます。署名または暗号化メカニズムが侵害された場合でも、攻撃者は承認されたIPアドレスからAPIをリクエストする必要があります。
解決策は、IPアドレスに基づいてAPIリクエストを制限することであり、ホワイトリストに登録されたIPからのリクエストのみを許可します。
- APIリクエストがホワイトリストに登録されたIPから発信された場合、通常どおりに処理されます。
- リクエストがホワイトリストに登録されていないIPからのものである場合、アクセスはすぐに拒否されます。
IPホワイトリストは、APIゲートウェイレベルで適用できます。
ただし、社内の内部アプリケーションサーバーも侵害され、攻撃者がネットワーク内からAPIリクエストを送信できるようになる可能性があります。
これに対抗するには、追加の保護のためにModSecurityなどのWebファイアウォールを導入する必要があります。
4. Rate Limiting
サードパーティプラットフォームがAPIを呼び出す場合、そのリクエスト頻度は制御不能になる可能性があります。
サードパーティが大量のリクエストを同時に送信すると、APIサービスが過負荷になり、ダウンタイムが発生する可能性があります。
したがって、レート制限を実装する必要があります。
一般的なレート制限戦略は3つあります。
-
IPごとのリクエストを制限する 例:単一のIPは、最大1分あたり10,000リクエストを行うことができます。
-
APIエンドポイントごとのリクエストを制限する 例:単一のIPは、特定のAPIエンドポイントに対して、最大1分あたり2,000リクエストを行うことができます。
-
ユーザー(AK / SK)ごとのリクエストを制限する 例:単一のAK / SKユーザーは、最大1分あたり10,000 APIリクエストを行うことができます。
実際のアプリケーションでは、Nginx、Redis、またはAPIゲートウェイを使用してレート制限を実装できます。
5. Parameter Validation
APIインターフェースは、次のようなパラメータ検証を適用する必要があります。
- 必須フィールドが空かどうかを確認します。
- フィールドタイプを検証します。
- フィールドの長さを検証します。
- 列挙された値が正しいことを確認します。
これにより、無効なリクエストをプロセスの早い段階でフィルタリングし、不要な処理を防ぐことができます。
たとえば、リクエストが許可された最大長を超えるフィールドにデータを挿入しようとすると、データベースはエラーをスローします。ただし、このような検証は、システムリソースを節約するために、データベース操作の前に処理する必要があります。
検証の落とし穴の例:
- 誤った金額: フィールドが正の数を格納することになっているにもかかわらず、負の値が受け入れられる場合、予期しない損失が発生する可能性があります。
- 無効なステータス値: システムがステータスフィールドを検証せず、不明な値が受信された場合、データベースが破損する可能性があります。
一般的な検証フレームワーク
Javaでは、最も一般的に使用される検証フレームワークはHibernate Validatorです。これには、次のようなアノテーションが含まれています。
@Null
@NotEmpty
@Size
@Max
@Min
これらのアノテーションにより、データ検証が簡単になります。
日付フィールドと列挙フィールドの場合、検証にはカスタムアノテーションが必要になる場合があります。
6. Unified Response Format
異なるレスポンスでJSON形式が一貫していないAPIに出会ったことがあります。例:
通常のレスポンス:
{ "code": 0, "message": null, "data": [{ "id": 123, "name": "abc" }] }
署名エラーレスポンス:
{ "code": 1001, "message": "Signature error", "data": null }
アクセス許可拒否レスポンス:
{ "rt": 10, "errorMgt": "No permission", "result": null }
なぜこれが問題なのですか?
APIが異なる形式でレスポンスを返す場合、インテグレーターに不要な混乱を引き起こします。
この問題は、次の場合によく発生します。
- APIゲートウェイには1つのレスポンス形式があります。
- ビジネスシステムには異なるレスポンス形式があります。
APIゲートウェイエラーが発生した場合、ある形式が返されます。 ビジネスシステムエラーが発生した場合、別の形式が返されます。
解決策:レスポンス構造の標準化
APIゲートウェイは統一されたレスポンス形式を適用する必要があります。
ビジネスシステムがエラーメッセージを含むRuntimeExceptionをスローした場合、APIゲートウェイはこの例外をキャッチし、標準化された形式で返す必要があります。
7. Unified Exception Handling
APIインターフェースは、一貫した例外処理を実装する必要があります。
データベースの問題(テーブルの欠落やSQL構文エラーなど)によりAPIリクエストが失敗し、レスポンスが未加工のSQLエラーを直接返す状況に遭遇したことはありませんか?
設計が不十分なAPIの中には、例外スタックトレース、データベースの詳細、エラーコード、さらにはレスポンスの行番号を公開するものもあります。
これは深刻なセキュリティリスクです。
悪意のあるアクターがこの情報を悪用してSQLインジェクション攻撃を実行したり、データベースを直接流出させたりして、システム侵害につながる可能性があります。
解決策:機密エラーの詳細をマスクする
未加工のエラーメッセージを公開する代わりに、すべてのAPI例外を次のような標準エラーレスポンスに変換する必要があります。
{ "code": 500, "message": "Internal Server Error", "data": null }
code
フィールドは500
(サーバーエラーの標準HTTPエラーコード)です。message
フィールドは、内部システムの詳細を公開しない一般的なエラーメッセージです。
デバッグ用の内部ロギング
問題をデバッグするには、内部ログに次の情報を記録する必要があります。
- 完全な例外スタックトレース
- データベースのエラーの詳細
- 正確なエラー行番号
これにより、内部チームは外部ユーザーに機密データを公開することなく、問題を診断するために必要な情報を確実に把握できます。
ゲートウェイレベルの例外インターセプト
例外処理はAPIゲートウェイレベルで適用できるため、すべてのエラーレスポンスが一貫したサニタイズされた形式に従うようになります。
8. Request Logging
リクエストログは、特にサードパーティプラットフォームの場合、API呼び出しの問題を診断する際に非常に重要です。
トレーサビリティを確保するために、次のAPIリクエストの詳細を記録します。
- リクエストURL
- リクエストパラメータ
- リクエストヘッダー
- HTTPメソッド
- レスポンスデータ
- レスポンス時間
traceId
を使用したリクエストトレース
traceId
はログに含める必要があり、特定のリクエストに関連するすべてのログをリンクできるようにします。これにより、トラブルシューティング時に無関係なログをフィルタリングできます。
サードパーティがログにアクセスできるようにする
場合によっては、サードパーティプラットフォームもリクエストログにアクセスする必要がある場合があります。
これを容易にするには:
- MongoDBやElasticsearchなどのデータベースにログを保存します。
- サードパーティユーザーがログを検索および表示できるUIダッシュボードを開発します。
これにより、セルフサービスデバッグが可能になり、外部ユーザーが軽微な問題についてサポートに連絡する必要がなくなります。
9. Idempotency Design
サードパーティプラットフォームは、システムにバグがあるために、またはAPIレスポンスが遅延または失敗した場合に再試行メカニズムが働くことで、非常に短い時間内に重複したAPIリクエストを送信する可能性があります。
APIが冪等性を処理しない場合、重複したリクエストは重複したレコードにつながり、データの一貫性がなくなる可能性があります。
解決策:重複したリクエストで重複したレコードが作成されないようにする
同じAPIリクエストが短期間に複数回受信された場合:
- 最初のリクエストは正常に処理され、データが挿入されます。
- 後続の同じリクエストでは、新しいデータは挿入されませんが、成功レスポンスが返されます。
実装アプローチ
-
データベースの一意制約
- リクエストパラメータに一意のインデックスを使用して、重複したエントリを防ぎます。
-
Redisを使用して重複排除
- リクエストパラメータとともに**
requestId
**をRedisに保存します。 - 同じ
requestId
を再度受信した場合は、リクエストを拒否します。
- リクエストパラメータとともに**
10. Limiting Record Count in Batch APIs
バッチ処理APIの場合、リクエストあたりのレコード数を制限することが非常に重要です。
なぜですか?
- リクエストあたりのレコード数が多すぎると、APIのタイムアウトと不安定につながる可能性があります。
- 大きなペイロードはサーバーの負荷を増やし、API全体のパフォーマンスを低下させます。
推奨される制限
- 単一のAPIリクエストで許可される最大レコード数は500です。
- 500を超えるレコードが送信された場合、APIはエラーを返す必要があります。
この制限は構成可能であり、ライブになる前にサードパーティユーザーと合意する必要があります。
大規模なクエリのページネーション
APIが大きなデータセットを返す必要がある場合は、1つのリクエストですべてのデータを返す代わりに、ページネーションを実装します。
11. Load Testing
APIを起動する前に、負荷テストは、そのQPS(1秒あたりのクエリ数)の制限を理解するために不可欠です。
レート制限が設定されている場合でも、APIが実際に予想される負荷を処理できるかどうかを確認する必要があります。
シナリオ例
- APIのレート制限は1秒あたり50リクエストに設定されています。
- ただし、実際のサーバー容量は1秒あたり30リクエストしか処理できない場合があります。
- つまりレート制限があっても、APIがクラッシュする可能性があります。
負荷テストツール
- JMeter
- Apache Bench (
ab
)
ストレステストを実行することで、安定したパフォーマンスを維持するために必要なサーバーノードの数を判断できます。
12. Asynchronous Processing
ほとんどのAPIは同期です。つまり、リクエストはすぐに処理され、レスポンスはリアルタイムで返されます。
ただし、複雑な操作、特にバッチ処理は、実行に時間がかかりすぎる可能性があります。
解決策:時間のかかるタスクを非同期処理に変換する
-
MQ(メッセージキュー)メッセージを送信する
- APIは、タスクをキューに入れた後、ただちに成功を返します。
- 個別のメッセージコンシューマーがタスクを非同期的に処理します。
-
サードパーティに処理結果を確認させる
- コールバックアプローチ: 処理が完了したらサードパーティAPIに通知します(決済APIで一般的)。
- ポーリングアプローチ: サードパーティはステータスチェックAPIを繰り返し呼び出して、進捗状況を追跡します。
13. Data Masking (Data Redaction)
一部のAPIレスポンスには、機密性の高いユーザーデータが含まれています。例:
- 電話番号
- 銀行カード番号
この情報がマスキングせずに公開されている場合、データ漏洩のリスクが高まります。
解決策:データマスキングを適用する
たとえば、銀行カード番号をマスクします。
- オリジナル:
5196123456781234
- マスク済み:
5196****1234
データが漏洩した場合でも、部分的に保護されたままとなり、セキュリティリスクが軽減されます。
14. Comprehensive API Documentation
適切に文書化されたAPIは、統合の労力を軽減し、コミュニケーションの誤解を最小限に抑えます。
APIドキュメントには、次の情報を含める必要があります。
- API URL
- HTTPメソッド(例:GET、POST)
- リクエストパラメータとフィールドの説明
- レスポンス形式とフィールドの説明
- エラーコードとメッセージ
- 暗号化と署名の例
- リクエストの例
- 追加の要件(例:IPホワイトリスト)
命名規則の標準化
一貫性を維持するには:
- フィールド名にはcamelCaseを使用します。
- フィールドタイプと長さを標準化します(例:
id
をLong
、status
をint
)。 - 統一された時間形式を定義します(例:
yyyy-MM-dd HH:mm:ss
)。
ドキュメントには、AK / SKキーの使用法とAPIドメイン名も指定する必要があります。
15. Request Methods
APIは、次のようなさまざまなリクエストメソッドをサポートしています。
- GET
- POST
- PUT
- DELETE
適切なメソッドを選択する
- GET: 読み取り専用リクエストに適しています(パラメータを送信する必要がない場合)。
- POST: パラメータが必要な場合にお勧めします(問題が発生しにくい)。
GETよりもPOSTを使用する理由
- POSTを使用すると、パラメータを簡単に拡張できます
- Feign API呼び出しでは、新しいパラメータを追加しても、既存のコードを変更する必要はありません。
- GETにはURLの長さ制限があります
- 最大5000文字ですが、POSTには制限はありません。
16. Request Headers
認証トークンやtraceIdなどの特定のパラメータは、クエリパラメータではなくリクエストヘッダーを介して送信する必要があります。
例:
- すべてのAPIリクエストでtraceIdをURLパラメータとして追加する代わりに、
- クライアントはリクエストヘッダーで送信する必要があります。
サーバーはインターセプターを使用してtraceIdを抽出できます。
17. Batch Processing
APIを設計する場合、データのクエリ、追加、更新、または削除のいずれの場合でも、バッチ処理を常に検討する必要があります。
バッチ処理が重要な理由
多くの場合、複数のレコードを一度にクエリする必要があります。 たとえば、注文の詳細を取得する場合:
- APIが一度に1つの注文のフェッチのみをサポートしている場合、注文ごとに個別のAPI呼び出しが必要です。
- APIがバッチ取得をサポートしている場合、1つのリクエストで複数の注文を取得できるため、効率が向上します。
同様に、データを追加する場合:
- APIがリクエストごとに1つのレコードの追加のみをサポートしており、バッチジョブで1,000個のレコードを挿入する必要がある場合、1,000回の個別のAPI呼び出しが必要になります。
- 代わりに、バッチ挿入APIを使用すると、1つのリクエストですべての1,000個のレコードを送信できるため、オーバーヘッドが削減されます。
設計の推奨事項:
- 可能であれば、APIは単一のレコードのみを処理する代わりにバッチ操作をサポートする必要があります。
- これにより、APIはさまざまなビジネスニーズに対応するためにより汎用的になり、スケーラブルになります。
18. Single Responsibility Principle
一部のAPI設計は過度に複雑であり、1つのリクエストで多数の条件をサポートしています。 その結果:
- サービス層(
Service
クラス)には、過剰なif...else
ステートメントが含まれています。 - レスポンスモデルには可能なすべてのフィールドが含まれており、プロパティの数が圧倒的になっています。
過度に複雑なAPIの問題:
- 保守が困難: 1年後には、元の開発者でさえ、特定のシナリオに必要なフィールドを思い出すのに苦労するかもしれません。
- 破壊的な変更のリスクが高い: シナリオAのロジックを変更すると、誤ってシナリオBに影響を与える可能性があります。
解決策:単一責任原則に従う
万能のAPIの代わりに、APIを小規模でシナリオ固有のエンドポイントに分割します。
例: 注文の配置APIの場合、2つのプラットフォーム(Webとモバイル)と2つの注文方法(標準と高速)があります。
すべてを処理する1つのAPIを設計する代わりに、それらを分離します。
- WebプラットフォームAPI
/web/v1/order/create /web/v1/order/fastCreate
- モバイルプラットフォームAPI
/mobile/v1/order/create /mobile/v1/order/fastCreate
これにより、4つの異なるAPIが得られ、それぞれが特定のユースケース専用になります。 メリット:
- ビジネスロジックは明確なままです。
- APIは保守が容易です。
- 変更を行う際に意図しない副作用のリスクが少なくなります。
Conclusion
これらのベストプラクティスに従うことで、APIインターフェースはより安全で、スケーラブルで、保守しやすくなります。これらの原則は、次のような場合に役立ちます。
- セキュリティの脆弱性を防止する(例:不正アクセス、SQLインジェクション)。
- パフォーマンスを最適化する(例:レート制限、バッチ処理)。
- 開発者のエクスペリエンスを向上させる(例:標準化されたレスポンス、明確なAPIドキュメント)。
内部システムまたはサードパーティ統合のためにAPIを設計する場合でも、これらのベストプラクティスを採用することで、より堅牢で将来性のあるアーキテクチャを確保できます。
We are Leapcell, your top choice for hosting backend projects.
Leapcell is the Next-Gen Serverless Platform for Web Hosting, Async Tasks, and Redis:
Multi-Language Support
- Develop with Node.js, Python, Go, or Rust.
Deploy unlimited projects for free
- pay only for usage — no requests, no charges.
Unbeatable Cost Efficiency
- Pay-as-you-go with no idle charges.
- Example: $25 supports 6.94M requests at a 60ms average response time.
Streamlined Developer Experience
- Intuitive UI for effortless setup.
- Fully automated CI/CD pipelines and GitOps integration.
- Real-time metrics and logging for actionable insights.
Effortless Scalability and High Performance
- Auto-scaling to handle high concurrency with ease.
- Zero operational overhead — just focus on building.
Explore more in the Documentation!
Follow us on X: @LeapcellHQ