JMeter +JSR223 Sampler(Java)でCognito認証を通してみた

#記事について
JMeter + JSR223 Sampler(Java)でCognito認証を通す処理を行った際の備忘録的なもの

JMeterでAWS SDK for Javaを呼び出して処理をすると行った記事がなかなかなかったので、今後の自分や同じような箇所で躓いた人のために書き残しておこうと思い記事にした。

# 必要になった経緯
JMeterでAPIのパフォーマンステストを実施してするためにログイン + その後のAPI実行の処理のシナリオを設定していた。
ログインの認証処理の箇所にCognitoが使用されており、APIを実行するためにCognitoの認証を行いIDトークンを取得する必要があった。
テスト当初はaws-cliで認証処理を実装していたシェルスクリプトを`OS Process Sampler`で呼び出していたが、ちょっと強い負荷を与えるために1,000スレッドに増やして実行したところ、IDトークンの取得処理に30秒以上かかったり本体のメモリが不足したり(一応16GB積んでいるマシン)でテストにならなかった。マシンがメモリ不足となりフリーズしてしまい、電源を強制終了してマシンを再起動するはめになったりと色々と散々な状態であった。。
JMeterはJavaのスレッドを使用しているが、各スレッドがそれそれにaws-cliを実行するためにOSコマンドを呼び出しているために本体のメモリの消費が激しい可能性を考えた。たしかJavaでOSコマンドを呼び出すと別スレッドが立ち上がってJVMの外にも別のリソースが確保されるはず、、(理解がアイマイ・ω・)
そうであればIDトークン取得をaws-cliからJavaに変更して動かしたら実行速度とメモリの消費が改善されるのではないかと思い、`JSR223 Sampler`でJavaのコードを呼び出して取得するようにした。

## JMeterとは
※ここでは詳細な説明は割愛
みんなが知りたいことはTECHSCOREさんが大体書いてくれているので参考まで
https://www.techscore.com/tech/Java/ApacheJakarta/JMeter/index/

簡単に言うと負荷テストを行うためのメジャーなツールの一つ。Javaで実装されており、JVM上で動くために使い勝手がよい

## JMeterの追加設定
### aws-sdkの追加
JSR223 Samplerよりaws-sdkを呼び出せるようにするためにaws-sdk(今回はcognitoが使用できればよいので最小限のjarファイルを取得)を所定のパスへ配置する。
まず
https://jar-download.com/artifacts/com.amazonaws/aws-java-sdk-cognitoidp
より対象のSDKファイルをダウンロードする。
![スクリーンショット 2021-02-11 17.04.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/391896/6b5b2611-4057-07fd-7cc5-edc2a240723f.png)


上記でダウンロードしたSDKファイルをJMeterの所定の箇所に配置する。
配置する箇所は
`$JMETER_HOME/libexec/lib/ext`となる。
$JMETER_HOMEはJMeterのインストール方法により異なるため注意
自分は`brew install jmeter`でインストールしたため`/usr/local/Cellar/jmeter/jmeterのバージョン(執筆時点では5.4.1)`となる

![スクリーンショット 2021-02-11 17.12.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/391896/288f6588-3041-4fed-295e-b12a62ee1681.png)


最低限配置する必要があるファイルは
・aws-java-sdk-core-1.11.952.jar
・aws-java-sdk-cognitoidp-1.11.952.jar
・joda-time-2.8.1.jar
であるが念の為ダウンロードしたファイルをすべて配置しておくとよい
JMeterをすでに起動している場合は再起動によりjarファイルが反映される

### サンプラーの登録

もともとのIDトークン取得方法は
`get_idtoken.sh`

```
#!/bin/bash

aws cognito-idp admin-initiate-auth --user-pool-id ${USER_POOL_ID} \
--client-id ${CLIENT_ID} --auth-flow ADMIN_NO_SRP_AUTH --auth--parameters "USERNAME=${USER_EMAIL},PASSWORD=${PASSWORD}" \
--query "AuthenticationResult.idToken" --output text
```
といった感じでaws-cliでIDトークンを取得していたものを`OS Process Sampler`で呼び出していた。

`OS Process Sampler`を`JSR223 Sampler`に置き換えるためにとりあえずシェルをJavaのコードに置き換える。

```
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.cognitoidp.AWSCognitoIdentityProvider;
import com.amazonaws.services.cognitoidp.AWSCognitoIdentityProviderClient;
import com.amazonaws.services.cognitoidp.AWSCognitoIdentityProviderClientBuilder;
import com.amazonaws.services.cognitoidp.model.AdminInitiateAuthRequest;
import com.amazonaws.services.cognitoidp.model.AdminInitiateAuthResult;
import com.amazonaws.services.cognitoidp.model.AuthFlowType;

import java.util.HashMap;
import java.util.Map;

        BasicAWSCredentials awsCred = new BasicAWSCredentials("${aws_access_key_id}", "${aws_secret_access_key}");

        AWSCognitoIdentityProvider userPoolClient = AWSCognitoIdentityProviderClientBuilder.standard()
                .withCredentials(new AWSStaticCredentialsProvider(awsCred))
                .withRegion(Regions.AP_NORTHEAST_1)
                .build();

        Map authParam = new HashMap();
        authParam.put("USERNAME", "${email}");
        authParam.put("PASSWORD", "${password}");
        AdminInitiateAuthRequest request = new AdminInitiateAuthRequest();
        request.withAuthFlow(AuthFlowType.ADMIN_NO_SRP_AUTH)
                .withUserPoolId("${user_pool_id}")
                .withClientId("${user_client_id}")
                .withAuthParameters(authParam);
        AdminInitiateAuthResult authResult = userPoolClient.adminInitiateAuth(request);
        return "idToken = " + authResult.getAuthenticationResult();

```
↑
※JSR223 SamplerではジェネリクスでMapを使用するとなぜかエラーになるため使用せず
使用できたといった経験があった人はご教示頂けるとありがたいです(*´ω｀*)

これをJSR223 Samplerで動くようにする

Jmeterを立ち上げて
Test Plan -> thread Group で右クリックをして `JSR223 Sampler`を追加する

![スクリーンショット 2021-02-11 17.38.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/391896/70509f43-0362-3ee1-5b85-13e8529ffdcd.png)

追加できたら
Laguage : Java
に設定してScriptのところに上記のJavaコードを記載する
![スクリーンショット 2021-02-11 17.42.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/391896/fdfd50b0-1097-e2b7-24db-93414ac77ae7.png)


環境変数の設定を行う
Test Plan -> thread Group で右クリックをして `User Defined Variables`を追加する

![スクリーンショット 2021-02-11 17.45.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/391896/4b4ecf1f-9df0-3d7c-4511-04f698adfe43.png)

追加できたら Addで環境変数を追加する
今回使用している環境変数は

```
- ${aws_access_key_id} : Cognitoへの権限があるAWS_ACCESS_KEY_ID
- ${aws_secret_access_key}  : 上記AWS_ACCESS_KEY_IDのAWS_SECRET_ACCESS_KEY
- ${email} : Cognitoへ登録済みのユーザーアカウントのemail
- ${password} : 上記ユーザーアカウントのパスワード
- ${user_pool_id} : 上記ユーザーアカウントが所属するuser_pool_id
- ${user_client_id} : 上記user_pool_idのuser_client_id

```

となる。

認証の成否を確認できるようにするためにリスナーも追加しておく

応答データを確認するためにView Result Treeを設定する。
![スクリーンショット 2021-02-11 17.58.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/391896/dc0cb98c-c670-5136-37d8-11ede2d09981.png)


上記を設定してJmeterよりスタートボタンを実行する。
認証に成功するとSampler Resultの応答データにidToken = IDトークンの値
が表示される。

![スクリーンショット 2021-0 2-11 18.01.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/391896/251704e6-fb06-7299-e462-382b27bc6e36.png)

こいつを正規表現抽出なんかで取得するとログイン後のAPIを使用するシナリオなんかを再現できる感じになる。

# まとめ
JMeterでaws-sdk使用している記事あんまりなさそうだったんだけど実装できるのかな、などと思いながらはじめた設定であったが無事に設定ができてホッとしている。
`OS Process Sampler`でIDトークンを取得していたときは1,000スレッド実行すると30秒以上かかったりしてタイムアウトになる場合も多かったが、`JSR223 Sampler`に置き換えると1,200スレッド飛ばしても最大で1.5秒の時間で取得できたりと、大幅なレスポンス改善につながった。
JavaでOSコマンドを呼び出す処理は極力しないほうが良さそう。※やっても非同期で別サーバーで実行だったりとかかな。