Amazon Simple Notification Service
開発者ガイド (API バージョン 2010-03-31)

Amazon SNS エンドポイント Java Servlet のコード例

重要

以下のコードスニペットは Amazon SNS HTTP POST リクエストを処理する Java servlet を理解するために役立ちます。運用環境にスニペットを実装する前に、スニペットのいずれの部分も目的に適していることを確認します。たとえば、本稼働環境でなりすまし攻撃を避けるには、受信した Amazon SNS メッセージが Amazon SNS からのものであることを確認する必要があります。そのためには、Amazon SNS 証明書に表示された [Subject Alternative Name] フィールドの DNS 名の値(us-west-2 の DNS Name=sns.us-west-2.amazonaws.com、リージョンによって異なる)が、受信した Amazon SNS メッセージと同じであることを確認します。サーバーの識別情報の確認の詳細については、「3.1 Server Identity」(RFC 2818) を参照してください。「Amazon SNS メッセージの署名の確認」も参照してください。

以下のメソッドでは、Java servlet で Amazon SNS からの HTTP POST リクエストに対するハンドラーの例を実装します。

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException, SecurityException { //Get the message type header. String messagetype = request.getHeader("x-amz-sns-message-type"); //If message doesn't have the message type header, don't process it. if (messagetype == null) { return; } // Parse the JSON message in the message body // and hydrate a Message object with its contents // so that we have easy access to the name/value pairs // from the JSON message. Scanner scan = new Scanner(request.getInputStream()); StringBuilder builder = new StringBuilder(); while (scan.hasNextLine()) { builder.append(scan.nextLine()); } Message msg = readMessageFromJson(builder.toString()); // The signature is based on SignatureVersion 1. // If the sig version is something other than 1, // throw an exception. if (msg.getSignatureVersion().equals("1")) { // Check the signature and throw an exception if the signature verification fails. if (isMessageSignatureValid(msg)) { log.info(">>Signature verification succeeded"); } else { log.info(">>Signature verification failed"); throw new SecurityException("Signature verification failed."); } } else { log.info(">>Unexpected signature version. Unable to verify signature."); throw new SecurityException("Unexpected signature version. Unable to verify signature."); } // Process the message based on type. if (messagetype.equals("Notification")) { //Do something with the Message and Subject. //Just log the subject (if it exists) and the message. String logMsgAndSubject = ">>Notification received from topic " + msg.getTopicArn(); if (msg.getSubject() != null) { logMsgAndSubject += " Subject: " + msg.getSubject(); } logMsgAndSubject += " Message: " + msg.getMessage(); log.info(logMsgAndSubject); } else if (messagetype.equals("SubscriptionConfirmation")) { //You should make sure that this subscription is from the topic you expect. Compare topicARN to your list of topics //that you want to enable to add this endpoint as a subscription. //Confirm the subscription by going to the subscribeURL location //and capture the return value (XML message body as a string) Scanner sc = new Scanner(new URL(msg.getSubscribeURL()).openStream()); StringBuilder sb = new StringBuilder(); while (sc.hasNextLine()) { sb.append(sc.nextLine()); } log.info(">>Subscription confirmation (" + msg.getSubscribeURL() + ") Return value: " + sb.toString()); //Process the return value to ensure the endpoint is subscribed. } else if (messagetype.equals("UnsubscribeConfirmation")) { //Handle UnsubscribeConfirmation message. //For example, take action if unsubscribing should not have occurred. //You can read the SubscribeURL from this message and //re-subscribe the endpoint. log.info(">>Unsubscribe confirmation: " + msg.getMessage()); } else { //Handle unknown message type. log.info(">>Unknown message type."); } log.info(">>Done processing message: " + msg.getMessageId()); }

以下の例の Java メソッドは、リクエストボディで送信されたデータが含まれる Message オブジェクトからの情報を使って署名を作成し、メッセージの Base64 でエンコードされた元の署名 (Message オブジェクトからも読み取られる) に対してその署名を確認します。

private static boolean isMessageSignatureValid(Message msg) { try { URL url = new URL(msg.getSigningCertURL()); InputStream inStream = url.openStream(); CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate)cf.generateCertificate(inStream); inStream.close(); Signature sig = Signature.getInstance("SHA1withRSA"); sig.initVerify(cert.getPublicKey()); sig.update(getMessageBytesToSign(msg)); return sig.verify(Base64.decodeBase64(msg.getSignature())); } catch (Exception e) { throw new SecurityException("Verify method failed.", e); } }

以下の Java メソッドの例では、連携して Amazon SNS メッセージに署名するための文字列を作成します。getMessageBytesToSign メソッドは、メッセージタイプに基づいて適切な署名対象の文字列メソッドを呼び出し、バイト配列として署名する文字列を実行します。buildNotificationStringToSign および buildSubscriptionStringToSign メソッドは、「Amazon SNS メッセージの署名の確認」に説明している形式に基づいて、署名する文字列を作成します。

private static byte [] getMessageBytesToSign (Message msg) { byte [] bytesToSign = null; if (msg.getType().equals("Notification")) bytesToSign = buildNotificationStringToSign(msg).getBytes(); else if (msg.getType().equals("SubscriptionConfirmation") || msg.getType().equals("UnsubscribeConfirmation")) bytesToSign = buildSubscriptionStringToSign(msg).getBytes(); return bytesToSign; } //Build the string to sign for Notification messages. public static String buildNotificationStringToSign(Message msg) { String stringToSign = null; //Build the string to sign from the values in the message. //Name and values separated by newline characters //The name value pairs are sorted by name //in byte sort order. stringToSign = "Message\n"; stringToSign += msg.getMessage() + "\n"; stringToSign += "MessageId\n"; stringToSign += msg.getMessageId() + "\n"; if (msg.getSubject() != null) { stringToSign += "Subject\n"; stringToSign += msg.getSubject() + "\n"; } stringToSign += "Timestamp\n"; stringToSign += msg.getTimestamp() + "\n"; stringToSign += "TopicArn\n"; stringToSign += msg.getTopicArn() + "\n"; stringToSign += "Type\n"; stringToSign += msg.getType() + "\n"; return stringToSign; } //Build the string to sign for SubscriptionConfirmation //and UnsubscribeConfirmation messages. public static String buildSubscriptionStringToSign(Message msg) { String stringToSign = null; //Build the string to sign from the values in the message. //Name and values separated by newline characters //The name value pairs are sorted by name //in byte sort order. stringToSign = "Message\n"; stringToSign += msg.getMessage() + "\n"; stringToSign += "MessageId\n"; stringToSign += msg.getMessageId() + "\n"; stringToSign += "SubscribeURL\n"; stringToSign += msg.getSubscribeURL() + "\n"; stringToSign += "Timestamp\n"; stringToSign += msg.getTimestamp() + "\n"; stringToSign += "Token\n"; stringToSign += msg.getToken() + "\n"; stringToSign += "TopicArn\n"; stringToSign += msg.getTopicArn() + "\n"; stringToSign += "Type\n"; stringToSign += msg.getType() + "\n"; return stringToSign; }