JDK 6でAmazon Product Advertising API をSOAP呼び出しする
SOAPでAPAAPIにアクセスする方法のメモ。仕事で以前つくったものが動かなくなっていたので、それの修正のために調べた。以前からあったAPIキーだけじゃなくて、秘密キーでシグネチャつくれって変更。
具体的にはWSセキュリティを利用せずに SOAP リクエストを処理する方法をJDK1.6に付属のJAX-WSで実装する方法。(BASE64用にcommons-codecもつかってる)
簡単にいうとAWSECommerceService#setHandlerResolverというのがあり、そこでSOAPHandlerを設定することによりSOAPドキュメントをごにょごにょできる。今回はsoap:HeaderにAWSAccessKeyIdとSignatureとTimestampを追加する
このページがいいとこまできてるんだけど、肝心なところが抜けてる
ここで抜けてるところがどんな内容なのかわかった。
ごにょごにょできるとこはhandleMessageという部分なんだけど、このメソッドは送信時、受信時ともに呼ばれるMessageContext.MESSAGE_OUTBOUND_PROPERTYの値を調べることによって、いま送信か受信かがわかる。
Signatureをつくるにはいま何のアクション(メソッド)をよんでいるかがわからないといけないんだけど、これはSOAP ACTION URIから推測した。handleMessage内でMessageContextの"javax.xml.ws.soap.http.soapaction.uri"の値を調べるhttp://soap.amazon.com/ItemSearchとなっているので/以下をとった。
あと日本に検索するときはAWSECommerceServicePortTypeのプロパティを変更する。
wsimportの例 -pで生成されるスタブのパッケージを指定する。
wsimport -p com.amazon.aws.ecommers -b binding.xml http://ecs.amazonaws.com/AWSECommerceService/AWSECommerceService.wsdl
SOAPヘッダーにAWSAccessKeyId,Timestamp, Signatureを入れるSOAPHandler
package com.amazon.aws.ecommerce; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Collections; import java.util.Date; import java.util.Set; import java.util.TimeZone; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.xml.namespace.QName; import javax.xml.soap.SOAPElement; import javax.xml.soap.SOAPEnvelope; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPMessage; import javax.xml.soap.SOAPPart; import javax.xml.ws.handler.MessageContext; import javax.xml.ws.handler.soap.SOAPHandler; import javax.xml.ws.handler.soap.SOAPMessageContext; import org.apache.commons.codec.binary.Base64; public class AmazonAuthenticationHandler implements SOAPHandler<SOAPMessageContext> { private Mac hmacSha_256; private String accessKey; public AmazonAuthenticationHandler(String accessKey, String secretKeyString) throws NoSuchAlgorithmException, InvalidKeyException { SecretKeySpec secretKey = new SecretKeySpec(secretKeyString.getBytes(),"HmacSHA256"); Mac hmac_sha_256 = Mac.getInstance("HmacSHA256"); hmac_sha_256.init(secretKey); this.accessKey = accessKey; this.hmacSha_256 = hmac_sha_256; } @Override public void close(MessageContext context) { } @Override public boolean handleFault(SOAPMessageContext context) { return false; } @Override public boolean handleMessage(SOAPMessageContext context) { Boolean outboundProperty = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY); if (outboundProperty) { return handleOutBound(context); } else { return handleInBound(context); } } protected boolean handleInBound(SOAPMessageContext context) { return false; } protected boolean handleOutBound(SOAPMessageContext context) { try { String opUri = (String) context.get("javax.xml.ws.soap.http.soapaction.uri"); String action = opUri.substring(opUri.lastIndexOf('/') + 1); SOAPMessage message = context.getMessage(); SOAPPart part = message.getSOAPPart(); SOAPEnvelope envelope = part.getEnvelope(); String timestamp; SOAPHeader header = envelope.addHeader(); SOAPElement ns = header.addNamespaceDeclaration("aws", "http://security.amazonaws.com/doc/2007-01-01/"); SOAPElement accessKey = header.addChildElement(ns.createQName( "AWSAccessKeyId", "aws")); accessKey.addTextNode(this.accessKey); SOAPElement timeStamp = header.addChildElement(ns.createQName( "Timestamp", "aws")); timeStamp.addTextNode((timestamp = generateTimestamp())); SOAPElement signature = header.addChildElement(ns.createQName( "Signature", "aws")); signature.addTextNode(generateSignature(action, timestamp)); } catch (SOAPException e) { e.printStackTrace(); } catch (ClassCastException e) { e.printStackTrace(); } return true; } private String generateSignature(String action, String ts) { byte[] data = (action + ts).getBytes(); byte[] hmac = hmacSha_256.doFinal(data); Base64 encoder = new Base64(); String signature = new String(encoder.encode(hmac)); return signature; } private String generateTimestamp() { Date d = new Date(); DateFormat f = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); f.setTimeZone(TimeZone.getTimeZone("UTC")); return f.format(d); } @Override public Set<QName> getHeaders() { return Collections.emptySet(); } }
mainクラス
package sample.apaapi; import java.net.MalformedURLException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; import javax.xml.ws.BindingProvider; import javax.xml.ws.handler.Handler; import javax.xml.ws.handler.HandlerResolver; import javax.xml.ws.handler.PortInfo; import javax.xml.ws.handler.soap.SOAPMessageContext; import com.amazon.aws.ecommerce.AmazonAuthenticationHandler; import com.amazon.aws.ecommers.AWSECommerceService; import com.amazon.aws.ecommers.AWSECommerceServicePortType; import com.amazon.aws.ecommers.ItemSearch; import com.amazon.aws.ecommers.ItemSearchRequest; import com.amazon.aws.ecommers.ItemSearchResponse; public class AmazonSample { private static final String SECRET_KEY = "YOUR_SECRET_KEY"; private static final String ACCESS_KEY = "YOUR_ACCESS_KEY"; public static void main(String... args) throws MalformedURLException, NoSuchAlgorithmException, InvalidKeyException{ AWSECommerceService service = new AWSECommerceService(); service.setHandlerResolver(new HandlerResolver() { @SuppressWarnings("unchecked") @Override public List<Handler> getHandlerChain(PortInfo portInfo) { List<Handler> list = new ArrayList<Handler>(); Handler<SOAPMessageContext> handler; try { handler = new AmazonAuthenticationHandler(ACCESS_KEY ,SECRET_KEY); list.add(handler); } catch (InvalidKeyException e) { } catch (NoSuchAlgorithmException e) { } return list; } }); AWSECommerceServicePortType port = service.getAWSECommerceServicePort(); ((BindingProvider)port).getRequestContext().put( BindingProvider.ENDPOINT_ADDRESS_PROPERTY, "https://ecs.amazonaws.jp/onca/soap?Service=AWSECommerceService"); ItemSearchRequest req = new ItemSearchRequest(); req.setSearchIndex("Books"); req.setKeywords("Java"); ItemSearch search = new ItemSearch(); search.getRequest().add(req); search.setAWSAccessKeyId(ACCESS_KEY); ItemSearchResponse res = port.itemSearch(search); System.out.println(res.getItems().get(0).getItem().size()); } }