読者です 読者をやめる 読者になる 読者になる

WonderPlanet DEVELOPER BLOG

ワンダープラネットの開発者ブログです。モバイルゲーム開発情報を発信。

GatlingでWeb APIのシグネチャ検証に対応する

サーバーエンジニアの原 @zetta1985 です。

認証済みで使用されるWeb APIの多くは、リクエストURLやパラメータをベースに生成したHMACをHTTPリクエストヘッダーに指定することで、サーバーサイドでリクエストの改ざんを検知する仕組みになっています。

応用が効かない負荷テストツールでは、この手のシグネチャ生成をサポートするのが手間な事があるのですが、 GatlingではScalaによってシグネチャ生成処理を自由に書くことができます。

Gatlingは、Java製の非同期HTTPクライアントとして有名なAsyncHttpClient を使用しています。 AsyncHttpClientはシグネチャ生成のためのフック処理をサポートしており、GatlingにおいてもAsyncHttpClientが提供しているSignatureCalculator を実装することで、リクエストの直前にシグネチャを生成し、任意のHTTPリクエストヘッダーに値を設定する事が可能となっています。

SignatureCalculator は以下のような定義になっています。

public interface SignatureCalculator {
  void calculateAndAddSignature(Request request, RequestBuilderBase<?> requestBuilder);
}

第一引数の request には、Gatling DSLで構築したURLやリクエストヘッダー/ボディが設定されています。 この request の値を使用して、シグネチャを生成し、第二引数の requestBuilder に対してシグネチャ用のHTTPリクエストヘッダーを設定することになります。

以下に、一例を示します。SignatureCalculator はsingle method interfaceなので、ここでは関数として定義します。

import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import com.hunorkovacs.koauth.service.Arithmetics

object Utils {

  // シグネチャ生成用秘密鍵
  val secret = sys.env.getOrElse("API_SECRET", "your secret")

  /** シグネチャ生成およびHTTPリクエストヘッダーへの設定 */
  def generateAndAddSignature(request: Request, rb: RequestBuilderBase[_]): Unit = {
    val token = request.getHeaders().get("Authorization").replace("Bearer ", "")
    val signingKey = s"$token&$secret"

    val method = request.getMethod()
    val url = request.getUrl()

    val encodedUrl = Arithmetics.urlEncode(url)
    // getByteDataでbodyのバイト配列が取得できないためボディの文字列からバイト配列を取得
    val body = Option(request.getStringData()).map(_.getBytes()).map(new String(_)).getOrElse("")
    val encodedBody = Arithmetics.urlEncode(body)
    val baseStr = s"$method&$encodedUrl&$encodedBody".toUpperCase()

    val mac = Mac.getInstance("HmacSHA256")
    val key = new SecretKeySpec(signingKey.getBytes("utf-8"), "HmacSHA256")
    mac.init(key)
    val signature = mac.doFinal(baseStr.getBytes("utf-8"))
    // bytes to Hex
    val sigHex = signature.map("%02x" format _).mkString
    rb.addHeader("X-Signature", sigHex)
    rb.addHeader("X-Signature-Method", "HMAC-SHA256")
  }

このgenerateAndAddSignature は、以下のようにして使用します。

  val getPlayerStatus = http("player status")
    .get("/v1/players/me")
    .header("Authorization", "Bearer " + authToken)
    .signatureCalculator(generateAndAddSignature _)  // eta-expansionでメソッドを値にして、signatureCalculatorメソッドに渡す
    .check(status.in(200 to 210))

まとめ

今回は、Gatlingでのシグネチャ検証にどのように対応するかをHMACの生成の一例とともにご紹介しました。

Scalaで自由にアルゴリズムを実装できるのは、Gatlingの強みです。 負荷テスト用にシグネチャ検証をバイパスするのではなく、シグネチャ検証も含めた負荷テストを実施するように心がけましょう。