
REST APIでSalesforceのApexクラスを呼ぶ
自前のWebサーバーに設置したフォームで登録された情報をSaleforceに登録するにはどうしたらいいでしょうか?
・Webサーバー側でフォーム登録時に定型メールを作成し、Salesforceのメールサービス機能で連携する
・SalesforceのREST APIまたはSOAP APIを使い、Webサーバー側でフォーム登録時にAPI経由で登録する
・Salesforceのメールtoリード、メールtoケースの機能を利用する
・Pardotを導入する
どの方法でも可能ですが、フォームの見栄えは重視し、かつ、あまり費用を掛けずに実現するには、前の1つが候補となります。掛かる手間は同じくらいです。
今時、システム間の連携と言えば、REST APIを使うことが多いので、2番目の方法をご紹介したいと思います。
証明書を作成する
SalesforceのREST APIを使うには、事前に認証を行う必要があります。認証の方法はいくつかありますが、サーバー同士の連携を実現したいので、JWTを使ったOAuth連携を使います。
そのためには、デジタル証明書が必要です。openssl コマンドで自己証明書を作成できますので、それを利用します。
# 証明書を保存するフォルダを作成する
>> mkdir cert
>> cd cert
# RSA非公開鍵を生成する
>> openssl genrsa -des3 -passout pass:<<任意のパスワード>> -out server.pass.key 2048
Generating RSA private key, 2048 bit long modulus
........................+++
..................................................+++
e is 65537 (0x10001)
# 鍵ファイルを作成する
>> openssl rsa -passin pass:<<任意のパスワード>> -in server.pass.key -out server.key
writing RSA key
# RSA非公開鍵ファイルを削除する
>> rm server.pass.key
# 証明書を作成する
>> openssl req -new -key server.key -out server.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:Japan
string is too long, it needs to be less than 2 bytes long
Country Name (2 letter code) []:jp
State or Province Name (full name) []:iwate
Locality Name (eg, city) []:takizawa
Organization Name (eg, company) []:pitadigi
Organizational Unit Name (eg, section) []:.
Common Name (eg, fully qualified host name) []:.
Email Address []:.
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
# SSL証明書を作成する
>> openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt
Signature ok
subject=/C=jp/ST=iwate/L=takizawa/O=pitadigi
Getting Private key
# 作成したファイルを確認する
>> ls
server.crt server.csr server.key
Salesforceで設定する
接続アプリケーションを作成する
次にSalesforceで接続アプリケーションを作成します。設定-アプリケーション-アプリケーションマネージャーを開き、新規接続アプリケーションをクリックします。
OAuth設定の有効化
ON
コールバックURL
JWTの場合は不要ですが、必須項目なので「https://localhost:8443/RestTest/oauth/_callback」でOK
デジタル署名を使用
ONにして作成した証明書(server.crt)を選択
選択したOAuth範囲
・データのアクセスと管理
・ユーザに代わっていつでも要求を実行
Webサーバフローの秘密が必要
ON
接続アプリケーションを設定する
作成した接続アプリケーションの設定を変更します。設定-アプリケーション接続アプリケーション-接続アプリケーションを管理するを選択します。
作成した接続アプリケーションを開き、以下の設定を行います。
許可されているユーザ
サーバー同士の連携で利用しますので、使用するユーザは限定した方がいいため、管理者が承認したユーザのみで利用できるようにします。
「管理者が承認したユーザは事前承認済み」を選択します。
プロファイル
使用するユーザが属しているプロファイルを選択します。ここではシステム管理者としていますが、より特定するには専用のプロファイルを作成した方がいいです。
Apexクラスを呼び出す側のプログラムを準備する
REST APIでApexクラスを呼び出す手順は以下の通りです。
・作成した秘密鍵を使ってJWTを作成する
・作成したJWTを使ってSalesforceに認証を要求しアクセストークンを取得する
・取得したアクセストークンを使ってApexクラスを呼び出す
今回はPHPで実装してみます(全く使ったことはありませんが・・・)
JWTを作成する
JWTを作成するためのライブラリは、lcobucci/jwtを使います。
必要なライブラリをインストールする
composerを使って、必要なライブラリをインストールします。
>> composer require lcobucci/jwt
JWTを作るコードを書く
JWTを作るコードを書きます。
SalesforceのヘルプにJWTに関するものがあります。ここを見るとJWTのペーロードに何を設定するかが書いてあります。
これを見ながらJWTを作成します。ログインURLは使用する環境によって異なります。
<?php
require_once './vendor/autoload.php';
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Signer\Rsa\Sha256;
// ログインURL
// 本番: https://login.salesforce.com
// Sandbox: https://test.login.salesforce.com
// スクラッチ組織: https://test.saleforce.com
define('LOGIN_URL', 'https://test.salesforce.com');
// コンシューマ鍵
define('CLIENT_ID', <<接続アプリケーションのコンシューマ鍵>>);
// ユーザID
define('USER_ID', 'xxxxx@example.com');
function createjwt() {
$signer = new Sha256();
$privateKey = new Key('file://cert/server.key');
$time = time();
$token = (new Builder())->issuedBy(CLIENT_ID) // iss: コンシューマ鍵
->permittedFor(LOGIN_URL) // aud: SalesforceログインURL
->relatedTo(USER_ID) // sub: SalesforceユーザID
->expiresAt($time + 3 * 60) // exp: 3分以内
->getToken($signer, $privateKey);
return $token;
}
$jwt = createjwt();
echo $jwt;
Salesforceにアクセスする
認証を行う
作成したJWTを使ってSalesforceに認証を投げるコードを書きます。
REST APIを呼び出すにはいくつかの認証がサポートされています。JWTを使うメリットは以下の通りです。
・ユーザのパスワードを使わないため、セキュアな情報を渡す必要がない
・証明書を使うため、パスワードよりセキュア
・標準的なテクノロジーを使うので、他のAPIを使う時に役立つ
これで、アクセスするURLと使用するBearerトークンが取得できます。
<?php
require_once './vendor/autoload.php';
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Signer\Rsa\Sha256;
// ログインURL
// 本番: https://login.salesforce.com
// Sandbox: https://test.login.salesforce.com
// スクラッチ組織: https://test.saleforce.com
define('LOGIN_URL', 'https://test.salesforce.com');
// 認証URL
define('AUTH_URL', LOGIN_URL . '/services/oauth2/token');
// コンシューマ鍵
define('CLIENT_ID', <<接続アプリケーションのコンシューマ鍵>>);
// ユーザID
define('USER_ID', 'xxxxxxx@example.com');
// 認証タイプ
define('GRANT_TYPE', 'urn:ietf:params:oauth:grant-type:jwt-bearer');
function createjwt() {
$signer = new Sha256();
$privateKey = new Key('file://cert/server.key');
$time = time();
$token = (new Builder())->issuedBy(CLIENT_ID) // iss: コンシューマ鍵
->permittedFor(LOGIN_URL) // aud: SalesforceログインURL
->relatedTo(USER_ID) // sub: SalesforceユーザID
->expiresAt($time + 3 * 60) // exp: 3分以内
->getToken($signer, $privateKey);
return $token;
}
function auth() {
$jwt = createjwt();
$post = array(
'grant_type' => GRANT_TYPE,
'assertion' => $jwt,
);
$curl = curl_init();
curl_setopt( $curl, CURLOPT_URL, AUTH_URL );
curl_setopt( $curl, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt( $curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 );
curl_setopt( $curl, CURLOPT_POSTFIELDS, $post );
$buf = curl_exec( $curl );
if ( curl_errno( $curl ) ) {
exit;
}
curl_close( $curl );
$json = json_decode( $buf );
$accinfo = array(
// アクセスするためのURL
'instance_url' => $json->instance_url,
// アクセスするために使用するBearerトークン
'access_token' => $json->access_token,
);
return $accinfo;
}
$accinfo = auth();
Apexクラスを呼び出す
取引先責任者に登録するApexクラスを作成します。
先頭で@RestResource(urlMapping='/resttest/*')と宣言します。これで認証で取得したURL+@RestResource(urlMapping='/resttest/*')でアクセスできます。
呼び出すメソッドは@HttpPostアノテーションで宣言します。パラメータは1項目ずつがを独立して宣言します。REST APIで呼び出す時は、JSONでパラメータを受け渡します。
{
"firstname": "苗字",
"lastname": "名",
"email": "Eメールアドレス"
}
// アクセスするURL(/services/apexrest/resttest/)
@RestResource(urlMapping='/resttest/*')
global with sharing class RestTest {
// POSTで呼び出すメソッド
// パラメータはjsonで渡す
@HttpPost
global static String doPost(
String firstname, // 名
String lastname, // 苗字
String email // Eメール
) {
// 取引先オブジェクトを作成する
Contact con = new Contact();
// 指定されたパラメータを設定する
con.FirstName = firstname;
con.LastName = lastname;
con.Email = email;
// 取引先責任者を保存する
try {
upsert con;
}
catch(DmlException e) {
return e.getMessage();
}
return 'OK';
}
}
Apexクラスを呼び出すには、認証で取得したURLとアクセストークンを使います。
ヘッダー
Content-Type: application/json;charset=UTF-8
Authorization: Bearer 取得したアクセストークン
URL
取得したURL /services/apexrest/resttest/
$url = $accinfo['instance_url'] . '/services/apexrest/resttest/';
$data = array(
'firstname' => 'Koji',
'lastname' => 'Matae',
'email' => 'test@test.jp',
);
$header = array(
'Content-Type: application/json;charset=UTF-8',
'Authorization: Bearer ' . $accinfo['access_token'],
);
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true );
curl_setopt($curl, CURLOPT_VERBOSE, true);
$result = curl_exec($curl);
curl_close($curl);
echo var_dump($result);
これで、REST API経由で取引先を登録するApexクラスを呼び出す事ができます。
REST API経由でSOQLを実行してデータを取得、更新することも可能です。
SOQLを直接呼び出すと、REST API経由のアクセスのバリエーションが必要以上に増え、メンテナンス性が低下する可能性があります。
こうした状況を避けるためにも、Apexクラスを作成し、その中で必要な処理を書いてREST APIで呼び出すことをオススメします。
最後に、PHPの全ソースです。
<?php
require_once './vendor/autoload.php';
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Signer\Rsa\Sha256;
// ログインURL
// 本番: https://login.salesforce.com
// Sandbox: https://test.login.salesforce.com
// スクラッチ組織: https://test.saleforce.com
define('LOGIN_URL', 'https://test.salesforce.com');
// 認証URL
define('AUTH_URL', LOGIN_URL . '/services/oauth2/token');
// コンシューマ鍵
define('CLIENT_ID', <<コンシューマ鍵>>);
// ユーザID
define('USER_ID', 'test@example.com');
// 認証タイプ
define('GRANT_TYPE', 'urn:ietf:params:oauth:grant-type:jwt-bearer');
function createjwt() {
$signer = new Sha256();
$privateKey = new Key('file://cert/server.key');
$time = time();
$token = (new Builder())->issuedBy(CLIENT_ID) // iss: コンシューマ鍵
->permittedFor(LOGIN_URL) // aud: SalesforceログインURL
->relatedTo(USER_ID) // sub: SalesforceユーザID
->expiresAt($time + 3 * 60) // exp: 3分以内
->getToken($signer, $privateKey);
return $token;
}
function auth() {
$jwt = createjwt();
$post = array(
'grant_type' => GRANT_TYPE,
'assertion' => $jwt,
);
$curl = curl_init();
curl_setopt( $curl, CURLOPT_URL, AUTH_URL );
curl_setopt( $curl, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt( $curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 );
curl_setopt( $curl, CURLOPT_POSTFIELDS, $post );
$buf = curl_exec( $curl );
if ( curl_errno( $curl ) ) {
exit;
}
curl_close( $curl );
$json = json_decode( $buf );
$accinfo = array(
// アクセスするためのURL
'instance_url' => $json->instance_url,
// アクセスするために使用するBearerトークン
'access_token' => $json->access_token,
);
return $accinfo;
}
function callapex($accinfo){
$url = $accinfo['instance_url'] . '/services/apexrest/resttest/';
$data = array(
'firstname' => 'Koji',
'lastname' => 'Matae',
'email' => 'test@test.jp',
);
$header = array(
'Content-Type: application/json;charset=UTF-8',
'Authorization: Bearer ' . $accinfo['access_token'],
);
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true );
curl_setopt($curl, CURLOPT_VERBOSE, true);
$result = curl_exec($curl);
curl_close($curl);
echo var_dump($result);
}
$accinfo = auth();
callapex($accinfo);