JWT認証によるSalesforce API連携手順について
本記事について
本記事は、Salesforce API連携を初めて試す方や、JWT認証を用いた接続方法を学びたい方向けに、JWT認証を用いたSalesforce API連携手順について解説します。
この記事で学べること
- JWT認証を使用したSalesforce API連携の基本
- KotlinスクリプトによるAPI接続の実装
- Salesforceからデータを取得する具体的な手順
想定するユースケース
Salesforce APIを活用することで、以下のような課題を解決できます
- Salesforceと社内システム間でデータを同期し、二重管理を防止
- Salesforceとサードパーティアプリを連携し、業務効率化や自動化を実現
また本記事では以下のような構成で、ローカル環境のKotlinスクリプトからAPI接続してSalesforce上のデータを取得する、という処理を作ることを目指します。
1. 接続準備
API接続するためのローカル環境、Salesforceでの準備作業について解説します。
1.1. アカウント作成
まずは開発者用のアカウントを準備します。開発者用ユーザーの場合は以下のリンクから作成することができます。
1.2. 秘密鍵・証明書の作成
ローカル環境とSalesforce間でAPI接続するための秘密鍵・証明書を作成します。
コンソールで以下を実行することで秘密鍵を発行します。
openssl genrsa -out server.key 2048
続いてコンソールで以下を実行することで証明書ファイルを発行します。
openssl req -new -x509 -key server.key -out server.crt -days 365
コマンド実行すると以下の入力を求められるので、それぞれ入力します。
-
Country Name (C): 国コード(例:
JP
) -
State or Province Name (ST): 都道府県(例:
Tokyo
) -
Locality Name (L): 市町村(例:
Minato
) -
Organization Name (O): 組織名(例:
ExampleCompany
) - Organizational Unit Name (OU): 部署名(任意)
-
Common Name (CN): ユーザー名やドメイン名(例:
example-dev
) - Email Address: 任意のメールアドレス
1.3. 外部クライアントアプリケーションを作成
Salesforceに外部クライアントアプリケーションを作成します。
OAuthポリシーは以下のように設定します。
- プラグインポリシー
- 許可されているユーザーを「管理者が承認したユーザーは事前承認済み」にする
- 「すべてのユーザーは自己承認可能」だと、API接続時も承認手続きが必要になる
- プロファイルを選択
- 「選択済みプロファイル」に接続したいユーザーのプロファイルを追加
- 1.1で作成した開発者用アカウントのプロファイルは「システム管理者」であるため、以下のスクリーンショットでは「システム管理者」を指定している
次にOAuth設定で「コンシューマー鍵」と「コンシューマーの秘密」を取得します。
以下画面で「コンシューマーと秘密」をクリックするとワンタイムコードがメール送信され、ワンタイムコードを入力すると「コンシューマー鍵」と「コンシューマーの秘密」を取得できます。
また、この画面の「OAuth範囲」で「選択したOAuth範囲」にapi, fullが含まれるようにします。
そして「フローの有効化」で「JWT ベアラーフローを有効化」を有効にします。
有効にすると「ファイルをアップロード」が表示されるので、ここから1.2で作成した証明書 server.crt
をアップロードします。
ここまでで、Salesforceでの準備作業が完了しました。次に、Kotlinを使ってSalesforce APIに接続し、実際にデータを取得する手順を解説していきます。
2. 接続手順
2.1. ディレクトリ構成とビルド設定
このセクションでは、プロジェクトのディレクトリ構成とビルド設定を紹介します。
ディレクトリ構成
以下の構成で、Kotlinを用いてSalesforce APIとの連携を実装します。
-
repository
-
build.gradle.kts
: 必要な依存関係とアプリケーション設定を定義。 -
main/kotlin
-
SalesforceJwt.kt
: メインのKotlinスクリプト。JWTの生成、アクセストークンの取得、Salesforceデータの取得を実装。
-
-
main/resources
-
server.key
: API接続で使用する秘密鍵ファイル。 -
server.crt
: API接続で使用する証明書ファイル。
-
-
build.gradle.kts
ビルド設定 以下は、必要なライブラリを含むGradle設定ファイルです。
plugins {
kotlin("jvm") version "1.9.0"
application
}
repositories {
mavenCentral()
}
dependencies {
// Apache HttpClient
implementation("org.apache.httpcomponents.client5:httpclient5:5.1")
// JWT (JSON Web Token)
implementation("io.jsonwebtoken:jjwt-api:0.11.2")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.2")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.2")
// ロギング用
implementation("org.slf4j:slf4j-simple:2.0.9")
// JSON処理用 (Jackson)
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.15.2")
}
application {
mainClass.set("SalesforceJwtKt")
}
- Apache HttpClient: HTTP通信を行うためのライブラリ。
- JWT: Salesforce認証で使用するJWTの生成に必要。
- Jackson: JSONデータの操作用。
2.2. JWTの作成と認証プロセスの概要
Salesforceと連携するためには、JWT認証を使用してアクセストークンを取得する必要があります。このセクションでは認証に必要なプロセスをSalesforceJwt.kt
中に実装する関数ごとに解説します。
loadPrivateKey
2.2.1 秘密鍵の読み込み 秘密鍵を使用してJWTを生成するために、main/resources/server.key
からRSA秘密鍵を読み込みます。
fun loadPrivateKey(path: String): PrivateKey {
val keyBytes: ByteArray
// リソースファイルから秘密鍵を読み込む
val resourceAsStream: InputStream = object {}.javaClass.getResourceAsStream(path)
?: throw IllegalArgumentException("Resource not found: $path")
resourceAsStream.use { inputStream ->
val keyString = inputStream.bufferedReader().use { it.readText() }
// 秘密鍵文字列から不要な部分を削除しデコード
val privateKeyPEM = keyString
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replace("\\s".toRegex(), "")
// Base64でデコードし、RSA鍵として読み込み。
keyBytes = Base64.getDecoder().decode(privateKeyPEM)
}
val spec = PKCS8EncodedKeySpec(keyBytes)
val keyFactory = KeyFactory.getInstance("RSA")
return keyFactory.generatePrivate(spec)
}
generateJWT
2.2.2. JWTの生成 JWTを生成し、Salesforceの認証エンドポイントに送信します。
// Salesforce の認証に使用する基本 URL
val baseUrl = "https://login.salesforce.com"
// JWT を生成する
fun generateJWT(consumerKey: String, username: String, privateKey: PrivateKey): String {
val now = Date()
val exp = Date(now.time + 300 * 1000) // 有効期限を5分に設定
return Jwts.builder()
.setIssuer(consumerKey) // クライアントID(コンシューマー鍵)を設定
.setSubject(username) // ユーザー名を設定
.setAudience(baseUrl) // Salesforce の認証エンドポイント
.setExpiration(exp) // トークンの有効期限
.signWith(privateKey, SignatureAlgorithm.RS256) // RSA-SHA256で署名
.compact()
}
getAccessToken
2.2.3. アクセストークンの取得 生成したJWTを使い、Salesforce APIの認証エンドポイントからアクセストークンを取得します。
// アクセストークンレスポンス用のデータクラス
data class AccessTokenResponse(
val id: String,
val access_token: String,
val scope: String,
val instance_url: String,
val token_type: String,
val api_instance_url: String,
)
// JSONレスポンスをデータクラスに変換する関数
fun jsonToAccessTokenResponse(json: String): AccessTokenResponse {
val mapper = jacksonObjectMapper()
return mapper.readValue(json)
}
// アクセストークンを取得する関数
fun getAccessToken(jwt: String): AccessTokenResponse {
val client: CloseableHttpClient = HttpClients.createDefault()
val url = "$baseUrl/services/oauth2/token"
val post = HttpPost(url)
// リクエストパラメータを設定
val params = listOf(
BasicNameValuePair("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"),
BasicNameValuePair("assertion", jwt)
)
post.entity = UrlEncodedFormEntity(params)
client.execute(post).use { response ->
return jsonToAccessTokenResponse(
response.entity.content.bufferedReader().use { it.readText() }
)
}
}
getData
2.3. Salesforceデータの取得 アクセストークンを使用してSalesforce REST APIを呼び出ます。SOQLクエリを実行しSalesforceデータを取得します。
SOQLとはSalesforce Object Query Languageの略で、Salesforceに保存されたオブジェクトのデータを検索するためのSQLライクなクエリ言語です。
SOQLについてはSalesforce Object Query Language (SOQL)も参考にしてください。
ここではSalesforce上の標準オブジェクトであるAccount(取引先)のデータをSOQLで取得しています。
なおAPIは2025/01/15現在で最新のバージョンであるv62.0を利用しています。
// JSONを整形して表示する
fun formatJson(json: String): String {
val mapper = jacksonObjectMapper()
val jsonNode = mapper.readTree(json)
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode)
}
// Salesforce に SOQL クエリを実行する
fun getData(accessToken: String, instanceUrl: String) {
val client: CloseableHttpClient = HttpClients.createDefault()
// 実行する SOQL クエリ
val soql = """
SELECT
Id,
Name
FROM Account
LIMIT 5
""".trimIndent()
// クエリを URL エンコード
val encodedSoql = URLEncoder.encode(soql, "UTF-8")
val url = "$instanceUrl/services/data/v62.0/query?q=${encodedSoql.replace(" ", "+")}"
val get = HttpGet(url)
get.addHeader("Authorization", "Bearer $accessToken") // アクセストークンをヘッダーに設定
get.addHeader("Content-Type", "application/json")
client.execute(get).use { response ->
val r = response.entity.content.bufferedReader().use { it.readText() }
println(formatJson(r)) // 結果を整形して出力
}
}
2.4. スクリプト全体の実行フロー
スクリプト全体をまとめると以下のようになります。
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
import org.apache.hc.client5.http.classic.methods.HttpPost
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient
import org.apache.hc.client5.http.impl.classic.HttpClients
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity
import org.apache.hc.core5.http.message.BasicNameValuePair
import java.io.InputStream
import java.security.KeyFactory
import java.security.PrivateKey
import java.security.spec.PKCS8EncodedKeySpec
import java.util.*
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import org.apache.hc.client5.http.classic.methods.HttpGet
import java.net.URLEncoder
// Salesforce の認証に使用する基本 URL
val baseUrl = "https://login.salesforce.com"
// RSA秘密鍵をロードする関数
fun loadPrivateKey(path: String): PrivateKey {
val keyBytes: ByteArray
// リソースファイルから秘密鍵を読み込む
val resourceAsStream: InputStream = object {}.javaClass.getResourceAsStream(path)
?: throw IllegalArgumentException("Resource not found: $path")
resourceAsStream.use { inputStream ->
val keyString = inputStream.bufferedReader().use { it.readText() }
// 秘密鍵文字列から不要な部分を削除しデコード
val privateKeyPEM = keyString
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replace("\\s".toRegex(), "")
keyBytes = Base64.getDecoder().decode(privateKeyPEM)
}
val spec = PKCS8EncodedKeySpec(keyBytes)
val keyFactory = KeyFactory.getInstance("RSA")
return keyFactory.generatePrivate(spec)
}
// JWT を生成する関数
fun generateJWT(consumerKey: String, username: String, privateKey: PrivateKey): String {
val now = Date()
val exp = Date(now.time + 300 * 1000) // 有効期限を5分に設定
return Jwts.builder()
.setIssuer(consumerKey) // クライアントID(コンシューマー鍵)を設定
.setSubject(username) // ユーザー名を設定
.setAudience(baseUrl) // Salesforce の認証エンドポイント
.setExpiration(exp) // トークンの有効期限
.signWith(privateKey, SignatureAlgorithm.RS256) // RSA-SHA256で署名
.compact()
}
// アクセストークンレスポンス用のデータクラス
data class AccessTokenResponse(
val id: String,
val access_token: String,
val scope: String,
val instance_url: String,
val token_type: String,
val api_instance_url: String,
)
// JSONレスポンスをデータクラスに変換する関数
fun jsonToAccessTokenResponse(json: String): AccessTokenResponse {
val mapper = jacksonObjectMapper()
return mapper.readValue(json)
}
// アクセストークンを取得する関数
fun getAccessToken(jwt: String): AccessTokenResponse {
val client: CloseableHttpClient = HttpClients.createDefault()
val url = "$baseUrl/services/oauth2/token"
val post = HttpPost(url)
// リクエストパラメータを設定
val params = listOf(
BasicNameValuePair("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"),
BasicNameValuePair("assertion", jwt)
)
post.entity = UrlEncodedFormEntity(params)
client.execute(post).use { response ->
return jsonToAccessTokenResponse(
response.entity.content.bufferedReader().use { it.readText() }
)
}
}
// JSONを整形して表示する関数
fun formatJson(json: String): String {
val mapper = jacksonObjectMapper()
val jsonNode = mapper.readTree(json)
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode)
}
// Salesforce に SOQL クエリを実行する関数
fun getData(accessToken: String, instanceUrl: String) {
val client: CloseableHttpClient = HttpClients.createDefault()
val soql = """
SELECT
Id,
Name
FROM Account
LIMIT 5
""".trimIndent()
// クエリを URL エンコード
val encodedSoql = URLEncoder.encode(soql, "UTF-8")
val url = "$instanceUrl/services/data/v62.0/query?q=${encodedSoql.replace(" ", "+")}"
val get = HttpGet(url)
get.addHeader("Authorization", "Bearer $accessToken") // アクセストークンをヘッダーに設定
get.addHeader("Content-Type", "application/json")
client.execute(get).use { response ->
val r = response.entity.content.bufferedReader().use { it.readText() }
println(formatJson(r)) // 結果を整形して出力
}
}
fun main() {
val consumerKey = "XXXX" // クライアントID, 1.3で作成したコンシューマーキーを指定する
val username = "example@example.com" // ユーザー名
val privateKeyPath = "/server.key" // 秘密鍵のパス
// 秘密鍵をロード
val privateKey = loadPrivateKey(privateKeyPath)
// JWT を生成
val jwt = generateJWT(consumerKey, username, privateKey)
// アクセストークンを取得
val accessTokenResponse = getAccessToken(jwt)
// アクセストークンレスポンスをパース
val accessToken = jsonToAccessTokenResponse(accessTokenResponse)
// Salesforce にデータ取得リクエストを実行
getData(accessToken.access_token, accessToken.instance_url)
}
このスクリプトを実行すれば、Salesforce APIから取引先(Account)オブジェクトのデータをJSON形式で取得できます。具体的には以下のような実行結果になります。
{
"totalSize" : 5,
"done" : true,
"records" : [ {
"attributes" : {
"type" : "Account",
"url" : "/services/data/v62.0/sobjects/Account/001F900001pJgsFIAS"
},
"Id" : "001F900001pJgsFIAS",
"Name" : "Grand Hotels & Resorts Ltd"
}, {
"attributes" : {
"type" : "Account",
"url" : "/services/data/v62.0/sobjects/Account/001F900001pJgsHIAS"
},
"Id" : "001F900001pJgsHIAS",
"Name" : "Express Logistics and Transport"
}, {
"attributes" : {
"type" : "Account",
"url" : "/services/data/v62.0/sobjects/Account/001F900001pJgsIIAS"
},
"Id" : "001F900001pJgsIIAS",
"Name" : "University of Arizona"
}, {
"attributes" : {
"type" : "Account",
"url" : "/services/data/v62.0/sobjects/Account/001F900001pJgsGIAS"
},
"Id" : "001F900001pJgsGIAS",
"Name" : "United Oil & Gas Corp."
}, {
"attributes" : {
"type" : "Account",
"url" : "/services/data/v62.0/sobjects/Account/001F900001pJgsMIAS"
},
"Id" : "001F900001pJgsMIAS",
"Name" : "sForce"
} ]
}
注意点
本記事は2025年1月時点のSalesforce APIのドキュメント、仕様に基づいた記事となっております。参考にされる際は最新のドキュメント、仕様を確認の上自己責任で参考にしてください。
権限について
権限セット・プロファイル単位の設定により、オブジェクト・項目単位で閲覧・更新権限を付与することができます。これにより柔軟な権限設定が可能ですが、場合によっては権限が不足して必要な情報が見れなかったり、不必要な情報が見れる権限にしてしまう、というリスクがあります。
リクエストの制限
プランごとに以下のようなリクエスト制限があります。
エディション | 1日あたりのリクエスト制限 |
---|---|
Enterprise Edition / Professional Edition | 1ユーザーあたり1,000リクエスト |
Unlimited Edition | 1ユーザーあたり5,000リクエスト |
Developer Edition | 1日あたり15,000リクエスト |
最新の情報は以下をご確認ください。
実装・利用方法によってはリクエストの制限を超えてしまい、利用できなくなってしまうリスクがあります。
おわりに
本記事ではJWT認証を用いたSalesforce API連携手順について解説しました。この後のステップとしては、Salesforce APIの他のエンドポイントやSOQLについて学習していけば様々なデータ連携にSalesforceを活用できるようになります。
Discussion