1. やってみたこと
- Salesforceのサーバ間インテグレーション用の OAuth 2.0 JWT ベアラーフローで認証を行う
- 適当なSalesforce APIにアクセスしてデータ連携を行う
2. バージョンや背景など
- Summer '23 Patch 10.7 (2023/7/2時点)
- sfdx-cli/7.206.6 darwin-x64 node-v18.15.0
% sfdx version
sfdx-cli/7.206.6 darwin-x64 node-v18.15.0
- スクラッチ組織で試す
- JWTの作成にJava, Mavenを使用します
- Java
% java --version
openjdk 17.0.7 2023-04-18
OpenJDK Runtime Environment Temurin-17.0.7+7 (build 17.0.7+7)
OpenJDK 64-Bit Server VM Temurin-17.0.7+7 (build 17.0.7+7, mixed mode, sharing)
- Maven
% mvn --version
Apache Maven 3.9.3 (21122926829f1ead511c958d89bd2f672198ae9f)
3. 試したこと
API インテグレーション用の OAuth 設定の有効化
サーバ間インテグレーション用の OAuth 2.0 JWT ベアラーフロー
安全な Salesforce API ユーザの作成
こちらを参照しながら試していきます
3.1. スクラッチ組織を作成する
以下のようなコマンドでスクラッチ組織を作成します
デフォルトのDevHub組織が既に作成および認証済みとします
sfdx org create scratch --edition developer --alias demo
Your scratch org is ready.
というメッセージが表示され、sfdx org list
で作成したスクラッチ組織を確認できます
以下のコマンドで作成したスクラッチ組織をブラウザで表示します
sfdx org open -o demo
3.2. サーバ間インテグレーション用の OAuth 2.0 JWT ベアラーフローの接続アプリケーションを設定する
サーバ間インテグレーション用の OAuth 2.0 JWT ベアラーフローの接続アプリケーションを作成します
API インテグレーション用の OAuth 設定の有効化
こちらを参照しながら作成します
3.2.1. 署名証明書を作成する
JWTベアラーフローのための秘密鍵、署名証明書を作成します
ここでは自己署名証明書を作成します
openssl genrsa 2048 > demo.pem
openssl req -new -key demo.pem -out demo.csr
openssl x509 -req -days 365 -in demo.csr -signkey demo.pem -out demo.crt
署名証明書は、接続アプリケーションの設定に使用します
3.2.2. 新規接続アプリケーションの作成
-
設定 > アプリケーション > アプリケーションマネージャ
で新規接続アプリケーション
をクリックします
3.2.3. 基本情報の入力
基本情報を入力します
-
接続アプリケーション名
,API 参照名
,取引先責任者 メール
を設定します
3.2.4. OAuth設定の入力
OAuth設定を入力します
-
OAuth 設定の有効化
にチェックします -
コールバック URL
を入力します- JWTベアラーフローでは使用しない設定のため、任意のもので大丈夫です。ここでは
https://localhost:19999
としました
- JWTベアラーフローでは使用しない設定のため、任意のもので大丈夫です。ここでは
-
デジタル署名を使用
をチェックします-
ファイルを選択
をクリックして、3.2.1. 署名証明書を作成する
で作成した自己証明書を設定します - ここでは
demo.crt
になります
-
-
選択した OAuth 範囲
を選択します- 今回はSalesforce APIを利用するため、
API を使用してユーザデータを管理 (api)
を選択します -
いつでも要求を実行 (refresh_token, offline_access)
を選択します
- 今回はSalesforce APIを利用するため、
-
Web サーバフローの秘密が必要
をチェックします -
更新トークンフローの秘密が必要
をチェックします -
すべてのトークンを調査
をチェックします
3.2.5. 設定の保存
-
保存
します
3.3. 接続ユーザーの作成
接続ユーザーはAPI限定ユーザーで作成します
組織でインテグレーションの目的にのみ使用する特別なユーザを作成します。こうすることで、実際のユーザが組織を離れた場合でも、いつでも適切な権限を持つユーザが存在することになります。
3.3.1. API限定ユーザーの作成 (1)
「Salesforce Integration」ライセンスの「Salesforce API Only System Integrations」 というプロファイルでAPI限定ユーザーを作成することができます
このライセンスを利用するとSalesforceライセンスを消費することがありません
-
設定 > ユーザ > ユーザ
で新規ユーザ
をクリックします - ユーザライセンスに
Salesforce Integration
を設定します - プロファイルに
Salesforce API Only System Integrations
が自動的に設定されます -
保存
して、ユーザを作成します
3.3.2. API限定ユーザーの作成 (2)
以下の方法でもAPI限定ユーザーを作成することができます
この場合、Salesforceライセンスを消費しますので、注意が必要です
3.3.2.1. API限定ユーザープロファイルの作成
-
設定 > ユーザ > プロファイル
で新規プロファイル
をクリックします -
既存のプロファイル
を選択します- 新しく作成するユーザープロファイルの基となるものを選択します
- ここでは
標準ユーザ
を選択します
-
プロファイル名
を入力します- ここでは
apionly
とします
- ここでは
-
編集
をクリックします -
システム管理者権限 > API 限定ユーザ
をチェックします -
保存
します
3.3.2.2. ユーザーの作成
-
設定 > ユーザ > ユーザ
で新規ユーザ
をクリックします - ユーザライセンスに
Salesforce
を設定します - プロファイルに
3.3.2.1. API限定ユーザープロファイルの作成
で作成したプロファイルを設定します -
保存
して、ユーザを作成します
3.4. 接続アプリケーションのポリシーを設定
接続アプリケーションで認証できるプロファイルを設定します
-
設定 > アプリケーション > アプリケーションマネージャ
の該当アプリケーションの右端▼からManage
をクリックします -
ポリシーを編集
をクリックします -
OAuth ポリシー > 許可されているユーザ
を管理者が承認したユーザは事前承認済み
に設定します-
このオプションを有効にすると、現在このアプリケーションを使用しているすべてのユーザがアクセスを拒否されます。アプリケーションを使用中のユーザを確認するには、接続アプリケーションの OAuth 利用状況レポートを参照してください。
というダイアログが表示されるので、OKをクリックします
-
-
保存
します -
プロファイル > プロファイルを管理する
で、3.3. 接続ユーザーの作成
で設定したプロファイルを設定します
3.5. アクセストークンを要求する
サーバ間インテグレーション用の OAuth 2.0 JWT ベアラーフロー
こちらを参照しながら試していきます
3.5.1. client_idの確認
-
設定 > アプリケーション > アプリケーションマネージャ
の該当アプリケーションの右端▼から参照をクリックします -
API (OAuth 設定の有効化) > コンシューマキーと秘密のコンシューマの詳細を管理
をクリックします - 確認コードを要求されたら入力して、
検証
をクリックします -
コンシューマ鍵
とコンシューマの秘密
が表示されます -
コンシューマ鍵
がclient_id
、コンシューマの秘密
がclient_secret
に該当します -
コンシューマ鍵
のみをコピーして控えます
3.5.2. JWTの作成
- JWT ヘッダーを次の形式で作成します
{"alg":"RS256"}
- JWT の JSON 要求セットを作成します
- iss: client_id (コンシューマ鍵)
- sub: 接続するユーザー名
- aud: 認可サーバーの URL
-
https://login.salesforce.com
またはhttps://test.salesforce.com
- 本番環境では前者、テスト環境(SandboxやScratch)では後者になります
-
- exp: 有効性は 3 分以内のアサーションの有効時間である必要があります (UTC で測定された 1970-01-01T0:0:0Z からの秒数として表記)
JSON 要求セットは以下のような形式になります
{"iss": "3MVG99OxTyEMCQ3gNp2PjkqeZKxnmAiG1xV4oHh9AKL_rSK.BoSVPGZHQ
ukXnVjzRgSuQqGn75NL7yfkQcyy7",
"sub": "my@email.com",
"aud": "https://login.salesforce.com",
"exp": "1333685628"}
今回は、JavaでJWTを作成します
3.5.2.1. 秘密鍵をDERフォーマットに変換
Javaで秘密鍵を読み取るために、3.2.1. 署名証明書を作成する
で作成した秘密鍵をDERフォーマットに変換します
openssl pkcs8 -topk8 -inform PEM -outform DER -in demo.pem -out demo.der -nocrypt
3.5.2.2. JavaでJWTを作成する
今回、JWTを作成するライブラリとして以下を使用することとします
auth0/java-jwt
また、Javaのjshellスニペットで処理を記述したいと思います
johnpoth/jshell-maven-pluginを使用して、ライブラリの依存性解決を行います
以下に続くファイルを作成します
JWTを作成するために必要な情報を環境変数に設定します
export JWT_ISS="******"
export JWT_SUB="******@******.***"
export JWT_AUD="https://test.salesforce.com"
export JWT_PRIVATEKEY_PATH=demo.der
jshell-maven-plugin
、およびライブラリjava-jwt
を使用するために以下のファイルを作成します
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>com.github.johnpoth</groupId>
<artifactId>jshell-maven-plugin</artifactId>
<version>1.3</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
jshellのスニペットファイルを作成します
import java.time.OffsetDateTime
// Definitions
var jwtHeader = """
{"alg":"RS256"} """
var iss = System.getenv("JWT_ISS")
var sub = System.getenv("JWT_SUB")
var aud = System.getenv("JWT_AUD")
var privateKeyPath = System.getenv("JWT_PRIVATEKEY_PATH")
var exp = OffsetDateTime.now().plusMinutes(3).toInstant()
// generate RSAKey from private_key file
import java.nio.file.Paths
import java.nio.file.Files
import java.security.KeyFactory
import java.security.spec.PKCS8EncodedKeySpec
import java.security.interfaces.RSAKey
var privateKeyFile = Paths.get(privateKeyPath)
var privateKeyContent = Files.readAllBytes(privateKeyFile)
var keySpec = new PKCS8EncodedKeySpec(privateKeyContent)
var keyFactory = KeyFactory.getInstance("RSA")
var rsaPrivateKey = keyFactory.generatePrivate(keySpec)
// Generate JWT
import com.auth0.jwt.algorithms.Algorithm
import com.auth0.jwt.JWT
var algo = Algorithm.RSA256((RSAKey)rsaPrivateKey)
var jwtBuilder = JWT.create()
jwtBuilder.withIssuer(iss)
jwtBuilder.withSubject(sub)
jwtBuilder.withAudience(aud)
jwtBuilder.withExpiresAt(exp)
jwtBuilder.withHeader(jwtHeader)
var jwt = jwtBuilder.sign(algo)
// Print generated JWT
System.out.println(jwt)
/exit
上記の準備ができたら、以下のコマンドを実行します
source env
mvn jshell:run -Djshell.scripts=demo.jshell-snipet
コマンドが正常に実行されたら、JWTが出力されます
3.5.3. アクセストークンの取得
以下のようなコマンドで、アクセストークンを取得することができます
assertion
には3.5.2. JWTの作成
で作成したJWTを設定します
curl -X POST -H 'Content-Type: application/x-www-form-urlencoded' 'https://MyDomainName.my.salesforce.com/services/oauth2/token' --data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer' --data-urlencode 'assertion=*********'
上記のhttps://MyDomainName.my.salesforce.com
の部分はhttps://test.salesforce.com
でも代替可能です
そうすると以下のようなレスポンスが返ってきます
access_token
の値を使用してAPIにアクセスを行うことができます
また、instance_url
の値はユーザーの組織のインスタンスを示すURLになります
{
"access_token": "******",
"scope": "api",
"instance_url": "https://MyDomainName.my.salesforce.com",
"id": "https://test.salesforce.com/id/******/******",
"token_type": "Bearer"
}
JWTベアラーフローでは、リフレッシュ トークンは発行されません
セッションタイムアウト内で取得されるアクセストークンは同じものになります
4. Appendix
4.1. OAuth 範囲
にrefresh_token
を設定しなかった場合は?
アクセストークンを取得しようとすると以下のようなレスポンスが返ってきます
refresh_token
が必要だというエラーメッセージがあり、refresh_token
の設定は必要なことが分かります
{
"error": "invalid_request",
"error_description": "refresh_token scope is required and the connected app should be installed and preauthorized."
}
4.2. JWTのaudにhttps://MyDomainName.my.salesforce.com
を設定したら?
アクセストークンを取得しようとすると以下のようなレスポンスが返ってきます
aud
が正しくないことが分かります
{
"error": "invalid_grant",
"error_description": "audience is invalid"
}
4.3. JWTのexpに設定したアサーションの有効時間を超えてアクセストークンを取得しようとしたら?
アクセストークンを取得しようとすると以下のようなレスポンスが返ってきます
有効期限切れであることが分かります
{
"error": "invalid_grant",
"error_description": "expired authorization code"
}
4.4. 接続アプリケーションに設定したデジタル証明書の有効期限を超えたら?
TODO
4.5. API限定ユーザーでWebログインしようとしてみたら?
以下のように表示され、Webログインが拒否されたことが分かります
4.6. 正しくない秘密鍵でJWTを作成してアクセストークンを取得しようとしたら?
アクセストークンを取得しようとすると以下のようなレスポンスが返ってきます
client credentials
が正しくないことが分かります
{
"error": "invalid_client",
"error_description": "invalid client credentials"
}
参考
API インテグレーション用の OAuth 設定の有効化
サーバ間インテグレーション用の OAuth 2.0 JWT ベアラーフロー
安全な Salesforce API ユーザの作成
おわり。