AWS SDK for Java v1でSIGV4の署名を作成してIAM認証付きAPI Gatewayのエンドポイントにリクエストする

2020.02.23

CX事業本部@大阪の岩田です。

現在関わっている案件でIAM認証を付けたAPI GWのエンドポイントにリクエストを発行する必要が出てきました。Node.jsに関しては先日佐藤が調べた通りなのですが、私の案件では諸々の理由からAWS SDK for Javaのv1を使う必要があったのでやり方を調べてみました。

Node.jsの場合はこちら↓

API GatewayでAPIにIAM認証をかけて、Node.jsでSigV4署名ヘッダを作成してリクエストしてみる

要件的には

  • 現在AWS SDK for Javaのv1を使ったJavaのプログラムが稼働しており、AWS SDK for Javaのv2には上げたくない
  • API GWのSDK生成機能は使いたくない
  • SIGV4の署名ロジックを独自実装するのではなく、実績のあるライブラリに乗っかりたい → 可能ならAWS SDK for Java v1でやりたい。

といった要件になります。わざわざSIGv4の署名を作らなくてもAPI GatewayのAPIキーで認証してしまえば楽なのですが、APIキーを認証目的で利用することは非推奨なので、頑張ってSIGv4の署名を作成します。

認証および API の認証の唯一の手段として API キーに依存しないようにしてください。まず、使用量プランに複数の API がある場合、その使用量プランの 1 つの API に対して有効な API キーを持つユーザーは、その使用量プランのすべての API にアクセスできます。代わりに、IAM ロール、Lambda オーソライザー、または Amazon Cognito ユーザープールを使用します。 API キーを使用する使用量プランの作成と使用

環境

  • Java SE 1.8.0_171
  • aws-java-sdk-core-1.11.727

やり方

com.amazonaws.auth.AWS4Signerクラスのsignメソッドを使うことでSIGv4の署名を作成できます。signメソッドの引数にはインターフェースSignableRequestを渡してやる必要があります。汎用的な具象クラスとしてcom.amazonaws.DefaultRequestが使えるので、こいつを使いましょう。

重要なのは以下のポイントです。

  • DefaultRequestのコンストラクタにexecute-apiを渡してやる
  • DefaultRequestsetEndpointでAPI Gatewayのエンドポイントを設定する
  • DefaultRequestsetResourcePathで実行したいAPIのリソースパスを設定する
  • DefaultRequestsetHttpMethodで実行したいAPIのHTTPメソッドを指定する

Main.java

package com.classmethod.awssdkexample;

import com.amazonaws.auth.AWS4Signer;
import com.amazonaws.auth.BasicSessionCredentials;
import com.amazonaws.AmazonWebServiceResponse;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.DefaultRequest;
import com.amazonaws.http.AmazonHttpClient;
import com.amazonaws.http.HttpMethodName;
import com.amazonaws.Response;

import java.util.Date;
import java.net.URI;


public class Main {

    public static void main(String[] args) {

        AWS4Signer signer = new AWS4Signer();

        String awsAccessKey = "<アクセスキー>";
        String awsSecretKey = "<シークレットキー>";
        String sessionToken = "<セッショントークン>";

        BasicSessionCredentials cred = new BasicSessionCredentials(
            awsAccessKey, awsSecretKey, sessionToken
        );

        DefaultRequest req = new DefaultRequest("execute-api");
        req.setEndpoint(URI.create("https://<API GWのエンドポイント>.execute-api.ap-northeast-1.amazonaws.com"));
        req.setResourcePath("<実行したいAPIのパス 例:/pets>");
        req.setHttpMethod(HttpMethodName.fromValue("<実行したいAPIのHTTPメソッド 例:GET>"));

        signer.setOverrideDate(new Date());
        signer.setRegionName("ap-northeast-1");
        signer.setServiceName("execute-api");
        signer.sign(req, cred);

        ClientConfiguration clientConfiguration = new ClientConfiguration();
        AmazonHttpClient client = new AmazonHttpClient(clientConfiguration);

        Response<AmazonWebServiceResponse<String>>  res = client.requestExecutionBuilder()
                .request(req)
                .execute(new StringResponseHandler());

        System.out.println(res.getAwsResponse().getResult());
    }
}

今回はAPIのレスポンスをそのまま標準出力に吐き出したいだけだったので、Stack Overflowの記事を参考にHttpResponseHandlerインターフェースを実装したクラスを作成してAPI呼び出し時のハンドラーに指定しています。今回の検証はSIGv4の署名を作成してIAM認証を通すまでがゴールなので、本来考慮すべき諸々の処理を省略しています。もし実際に本番利用される場合は適宜処理を修正/追加頂くようお願いします。

StringResponseHandler.java

package com.classmethod.awssdkexample;

import com.amazonaws.AmazonWebServiceResponse;
import com.amazonaws.http.HttpResponseHandler;
import com.amazonaws.util.IOUtils;

import java.io.IOException;

public class StringResponseHandler implements HttpResponseHandler<AmazonWebServiceResponse<String>> {

    @Override
    public AmazonWebServiceResponse<String> handle(com.amazonaws.http.HttpResponse response) throws IOException {

        AmazonWebServiceResponse<String> awsResponse = new AmazonWebServiceResponse<>();
        awsResponse.setResult(IOUtils.toString(response.getContent()));
        return awsResponse;
    }

    @Override
    public boolean needsConnectionLeftOpen() {
        return false;
    }

}

まとめ

Javaの経験が無いので大変でしたが、ドキュメントを見つつなんとか実装できて良かったです。

参考

Avoid reading closed stream consuming AWS Response