Amazon SNS 终端节点 Java Servlet 代码示例 - Amazon Simple Notification Service
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅中国的 Amazon Web Services 服务入门

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

Amazon SNS 终端节点 Java Servlet 代码示例

重要

以下代码段可帮助您了解用于处理 Amazon SNS HTTP POST 请求的 Java servlet。在生产环境中实现这些代码段之前,您应确保这些代码段的所有部分都适用于您的用途。例如,在生产环境中,为了帮助防止欺诈攻击,您应验证收到的 Amazon SNS 消息的标识是否来自 Amazon SNS。您可以通过检查 DNS 名称值 (DNS 名称 = SNS .us-west-2.amazonaws.com在 us-west-2;这将因地区而异)主题备用名称字段中显示的 Amazon SNS 证书中显示的字段对于接收的 Amazon SNS 消息是相同的。有关验证服务器标识的更多信息,请参阅3.1. 服务器身份RFC 2818。另请参阅验证 Amazon SNS 消息签名

以下方法为 Java servlet 中的 HTTP POST 请求实现处理程序示例,对来自 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()); verifyMessageSignatureURL(msg, url); 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); } } private static void verifyMessageSignatureURL(Message msg, URL endpoint) { URI certUri = URI.create(msg.getSigningCertURL()); if (!"https".equals(certUri.getScheme())) { throw new SecurityException("SigningCertURL was not using HTTPS: " + certUri.toString()); } if (!endpoint.equals(certUri.getHost())) { throw new SecurityException( String.format("SigningCertUrl does not match expected endpoint. " + "Expected %s but received endpoint was %s.", endpoint, certUri.getHost())); } }

以下 Java 方法示例将共同操作,为 Amazon SNS 消息创建待签字符串。getMessageBytesToSign 方法将根据消息类型调用适当待签字符串的方法,并以字节数组的形式运行待签字符串。buildNotificationStringToSignbuildSubscriptionStringToSign 方法将按照 验证 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; }