背景と適用シナリオ
Salesforce 統合エンジニア (Integration Engineer) として、私は日常的に Salesforce と外部システム間のデータ連携を設計・構築しています。これらの連携において、最も重要な課題の一つが「認証」です。特に、夜間のバッチ処理や、バックエンドサービスからのデータ同期など、ユーザーが直接介在しないサーバー間 (server-to-server) の通信では、従来のユーザー名とパスワードを用いた認証方法は多くの問題点を抱えています。例えば、パスワードのハードコーディングによるセキュリティリスク、多要素認証 (MFA) への非対応、パスワード変更時のメンテナンスコストなどが挙げられます。
このような課題を解決するために Salesforce が提供しているのが、OAuth 2.0 JWT Bearer Flow です。JWT は JSON Web Token の略で、電子署名された安全な認証情報をやり取りするためのオープンスタンダード (RFC 7519) です。このフローを利用することで、クライアントアプリケーション(連携先の外部システム)は、ユーザーのパスワードを一切扱うことなく、事前に発行したデジタル証明書を用いて Salesforce への認証を行い、API アクセストークンを取得できます。
具体的な適用シナリオ:
- 夜間バッチ処理: 外部の ERP システムが、夜間に Salesforce の商談 (Opportunity) データを取得し、在庫情報で更新する。
- CI/CD パイプライン: Jenkins や Azure DevOps といったツールが、Salesforce のサンドボックス環境へ自動的にメタデータをデプロイする。
- データ同期サービス: 外部のデータウェアハウス (DWH) が、定期的に Salesforce から最新の取引先 (Account) や取引先責任者 (Contact) データを抽出する。
- マイクロサービスアーキテクチャ: バックエンドのマイクロサービスが、ユーザーの操作をトリガーとして、非同期で Salesforce 上のカスタムオブジェクトを更新する。
これらのシナリオに共通するのは、「非対話型 (non-interactive)」であるという点です。つまり、ユーザーがログイン画面でIDとパスワードを入力するプロセスが存在しません。JWT Bearer Flow は、まさにこのような自動化されたサーバー間連携のために設計された、セキュアで堅牢な認証メカニズムなのです。
原理説明
JWT Bearer Flow の仕組みを理解するためには、まず JWT そのものの構造を把握する必要があります。JWT は、ピリオド (`.`) で区切られた3つの Base64URL エンコードされた文字列から構成されています。
[ヘッダー].[ペイロード].[署名]
1. ヘッダー (Header)
ヘッダーは、トークンのタイプと、署名の生成に使用されるアルゴリズムを定義する JSON オブジェクトです。Salesforce の JWT Bearer Flow では、署名アルゴリズムとして RS256 (RSA 署名 with SHA-256) が必須です。
例:{"alg":"RS256","typ":"JWT"}
2. ペイロード (Payload)
ペイロードには、クレーム (Claims) と呼ばれる、認証に関する情報が含まれています。これは、誰が、誰に対して、どのような目的で認証を要求しているかを示す情報群です。Salesforce への認証で必須となる主要なクレームは以下の通りです。
- iss (Issuer): 発行者。Salesforce の接続アプリケーション (Connected App) のコンシューマーキー (Consumer Key) を指定します。これにより、Salesforce はどのアプリケーションからの要求かを識別します。
- sub (Subject): 主体。認証を要求している Salesforce ユーザーのユーザー名を指定します。このユーザーとして API が実行されます。
- aud (Audience): 対象者。トークンの発行先を示します。本番環境の場合は
https://login.salesforce.com
、サンドボックス環境の場合はhttps://test.salesforce.com
を指定します。 - exp (Expiration Time): 有効期限。トークンが無効になる時刻を UNIX タイムスタンプで指定します。セキュリティのため、非常に短い期間(通常は3分以内)に設定することが推奨されます。
3. 署名 (Signature)
署名は、JWT の完全性と信頼性を担保する最も重要な部分です。以下の手順で生成されます。
- Base64URL エンコードされたヘッダーとペイロードをピリオドで連結する。
- その文字列を、クライアントアプリケーションが保持する秘密鍵 (Private Key) を用いて、ヘッダーで指定されたアルゴリズム (RS256) で署名する。
Salesforce 側は、接続アプリケーションに登録された公開鍵証明書 (Public Certificate) を使ってこの署名を検証します。署名が正しければ、その JWT が改ざんされておらず、かつ正規の秘密鍵を持つクライアントから送信されたものであることが証明されます。
認証フロー全体像
- 事前準備:
- クライアント側で、秘密鍵と公開鍵証明書のペアを生成します (例:OpenSSL を使用)。
- Salesforce で接続アプリケーションを作成し、「デジタル署名を使用」を有効にして、生成した公開鍵証明書をアップロードします。
- API を実行するユーザーが、この接続アプリケーションを「事前承認」するようにプロファイルまたは権限セットを設定します。
- トークン要求:
- クライアントアプリケーションは、必要なクレーム(iss, sub, aud, exp)を含む JWT を生成します。
- 保持している秘密鍵で JWT に署名します。
- Salesforce のトークンエンドポイント (
/services/oauth2/token
) に、生成した署名済み JWT を `assertion` パラメータとして POST リクエストを送信します。
- Salesforce 側の検証と応答:
- Salesforce はリクエストを受け取ると、`iss` クレームから接続アプリケーションを特定します。
- その接続アプリケーションに登録されている公開鍵証明書を取得し、JWT の署名を検証します。
- 署名が有効であれば、`aud` や `exp` などのクレームが正しいか、`sub` のユーザーが事前承認済みかなどをチェックします。
- すべての検証が成功すると、Salesforce は API へのアクセスに使用できるアクセストークン (Access Token) を返却します。
- API 実行:
- クライアントアプリケーションは、取得したアクセストークンを HTTP ヘッダーの `Authorization: Bearer [アクセストークン]` に含めて、Salesforce の各種 REST API や SOAP API を呼び出します。
サンプルコード
以下の Apex コードは、Salesforce が外部サービスを呼び出す際に JWT を生成する例です。これは、外部サービスから Salesforce を呼び出すプロセスとは逆ですが、JWT の構成要素(ヘッダー、ペイロード)と署名の方法を理解するための優れた公式サンプルです。外部システムで JWT を生成する場合も、全く同じ構造の JWT を作成することになります。
このコードでは、Salesforce 内の `Auth.JWT` クラスと `Auth.JWS` クラスを使用して JWT をプログラムで構築し、Salesforce に保存されている証明書(秘密鍵を含む)で署名しています。
// JWTオブジェクトを初期化します。 Auth.JWT jwt = new Auth.JWT(); // Subjectクレームを設定します。これは、認証を要求するユーザーの識別子に相当します。 // ここでは、連携先システムが要求するユーザーIDなどを設定します。 jwt.setSub('test@example.com'); // Audienceクレームを設定します。これは、このJWTがどのサービス向けのものかを示します。 // Salesforceを呼び出す場合は 'https://login.salesforce.com' となります。 jwt.setAud('https://my-external-service.com/token'); // Issuerクレームを設定します。これは、JWTの発行者を示します。 // Salesforceを呼び出す場合は、接続アプリケーションのコンシューマーキーになります。 jwt.setIss('3MVG99OxTyEMCQ3gN3Yg02_x3yXE25y8Vd22b252qjb5sIE9T9omDXgTjgr39dEVTj3aN2aW8b2v2dJk2i3y9'); // 追加のカスタムクレームを設定することも可能です。 // これは、連携先サービスが独自の情報を要求する場合に使用します。 Map<String, Object> claims = new Map<String, Object>(); claims.put('custom_claim_1', 'some_value'); jwt.setAdditionalClaims(claims); // 署名に使用する証明書の名前を指定します。 // この証明書は、Salesforceの「証明書と鍵の管理」で事前に作成しておく必要があります。 String certDevName = 'MySigningCert'; // Auth.JWSクラスを使用して、JWTに署名します。 // このメソッドは、指定された証明書の秘密鍵を使ってRS256アルゴリズムで署名を行います。 // 結果は「header.claims.signature」という形式のJWS (JSON Web Signature) 文字列になります。 String signedJwt = Auth.JWS.sign(jwt, certDevName); // 生成された署名済みJWT文字列をデバッグログに出力します。 // この文字列を外部サービスの認証エンドポイントに送信します。 System.debug('Signed JWT: ' + signedJwt);
注意事項
JWT Bearer Flow を実装・運用する際には、いくつかの重要な点に注意する必要があります。
1. 接続アプリケーション (Connected App) の設定
- デジタル署名の使用: 必ず「OAuth 設定の有効化」と共に「デジタル署名を使用」チェックボックスをオンにし、クライアントの公開鍵証明書 (.crt ファイル) をアップロードしてください。
- OAuth スコープ: 連携に必要な最小限のスコープ(例: `api`, `refresh_token`)を選択してください。不要な権限を与えないことがセキュリティの基本です。
- ユーザーの事前承認: このフローは非対話型のため、ユーザーが手動でアクセスを許可する画面は表示されません。管理者が事前に、「ポリシーを編集」画面から「許可されているユーザー」ポリシーを「管理者が承認したユーザーは事前承認済み」に設定し、対象のプロファイルや権限セットを関連付ける必要があります。これを忘れると `error: user_not_approved` というエラーが発生します。
2. 証明書の管理 (Certificate Management)
- 秘密鍵の厳重な保管: 秘密鍵は認証の要です。クライアントアプリケーションのサーバー上で、ファイルパーミッションを厳格に設定し、必要最低限のプロセスしかアクセスできないように保管してください。決してバージョン管理システム (Git など) にコミットしてはいけません。Azure Key Vault や AWS Secrets Manager のようなシークレット管理サービスを利用することが強く推奨されます。
- 証明書の有効期限: デジタル証明書には有効期限があります。期限切れになる前に新しい証明書ペアを生成し、Salesforce の接続アプリケーションとクライアントアプリケーションの両方を更新する「証明書ローテーション」のプロセスを確立しておく必要があります。期限切れは、突然の連携停止を引き起こす深刻なインシデントに繋がります。
3. クロック・スキュー (Clock Skew)
JWT の `exp` (有効期限) クレームは、時刻に依存します。クライアントサーバーのシステム時刻が Salesforce のサーバー時刻と大きくずれている(クロック・スキュー)場合、トークンが発行直後にもかかわらず「期限切れ」と判断されたり、逆に未来の時刻として拒否されたりする可能性があります。NTP (Network Time Protocol) などを利用して、サーバーの時刻を正確に同期させてください。
4. エラーハンドリング (Error Handling)
トークン取得時に返されるエラーメッセージは、問題解決の重要な手がかりです。代表的なエラーとその原因を把握しておくことが重要です。
- `invalid_grant`: 最も一般的なエラーの一つ。原因は多岐にわたります。
- JWT の署名が正しくない(秘密鍵と公開鍵が一致しない)。
- `sub` で指定されたユーザーが存在しない、または無効になっている。
- `sub` で指定されたユーザーが接続アプリケーションを事前承認していない。
- `iss`, `aud`, `exp` などのクレームの値が正しくない。
- `invalid_client_id`: `iss` クレームに指定したコンシューマーキーが間違っています。
- `user_not_approved`: `sub` で指定されたユーザーが、接続アプリケーションの利用を許可されていません。
まとめとベストプラクティス
OAuth 2.0 JWT Bearer Flow は、Salesforce とのサーバー間連携を自動化・セキュア化するための業界標準のソリューションです。一度正しく設定すれば、パスワード管理の煩雑さから解放され、堅牢で信頼性の高いインテグレーションを構築できます。
統合エンジニアとして成功裏にこのフローを導入するためのベストプラクティスを以下にまとめます。
- 専用の連携ユーザーを作成する: API 連携専用の Salesforce ユーザーを作成し、最小権限の原則 (Principle of Least Privilege) に従って、連携に必要なオブジェクトや項目へのアクセス権のみを持つプロファイルや権限セットを割り当てます。
- 環境ごとに異なる接続アプリケーションと証明書を使用する: 開発、ステージング (UAT)、本番といった各環境で、それぞれ独立した接続アプリケーションと証明書ペアを用意します。これにより、環境間の設定ミスやセキュリティリスクを低減できます。
- 秘密鍵の管理を徹底する: 前述の通り、秘密鍵の管理にはシークレット管理サービスの利用を第一に検討します。これが利用できない場合でも、ファイルシステムレベルでの厳格なアクセス制御は必須です。
- JWT の有効期限は短く設定する: `exp` クレームで設定する有効期限は、トークンが生成されてから Salesforce に送信されるまでの時間だけあれば十分です。通常、2〜3分に設定することで、万が一トークンが漏洩した場合のリスクを最小限に抑えられます。
- 詳細なログを記録する: 連携処理の開始・終了、成功・失敗、そして Salesforce から返されたエラーコードなどを詳細にログとして記録します。ただし、セキュリティ上の理由から、完全な JWT や取得したアクセストークンそのものをログに出力することは避けるべきです。
これらのプラクティスを遵守することで、Salesforce と外部システム間の連携はよりセキュアで、かつ運用しやすいものになります。JWT Bearer Flow をマスターすることは、現代の Salesforce 統合エンジニアにとって不可欠なスキルと言えるでしょう。
コメント
コメントを投稿