2009年7月19日日曜日

AS3 in Amazon ☆ Product Advertising APIのHMAC-SHA256署名への対応

Python側での対応が終わったので、次はFlexアプリケーションにてAmazonの署名リクエストを行うように対応。

私の検索方法が悪かったのか、参考になるWebページがあまり見つからなくて困った(>_<) 結局、一番分かり易い、というかクリティカルなところを解決してくれたのはAmazonのコミュニティだった。
やっぱり大事なんだな、こういうところ。

AS3でHMAC-SHA256署名を行うにあたり、まずは以下のパッケージを導入する必要がある。
as3crypto

zipファイルをダウンロードして解凍。
com以下を作業用ディレクトリにコピペ。

後は、このパッケージのHMAC、SHA256、Base64クラスをインポートして、HMAC-SHA256でハッシュ化した後、Base64エンコードする。
この辺の手順については、RHC2104の仕様とか全然読んでない;;
Pythonの時にお世話になった以下のページを参考にハッシュ化とエンコードを行った。
Amazon Product Advertising APIの署名認証をPythonでやってみる

以下、参考までにAS3のソースを公開。

/*** Import ***/
import com.hurlant.crypto.hash.HMAC;
import com.hurlant.crypto.hash.SHA256;
import com.hurlant.util.Base64;

/*** Field Variables ***/
private var requestUri:String = "ecs.amazonaws.jp"
private var requestPath:String = "/onca/xml"
private var amazonSecretKey:String = "AWSの秘密鍵"

/*** AWS Access Method ***/
private function sendAmazonRequest(var1:int, asin:String):void{
var amazonloader:URLLoader = new URLLoader()

// タイムスタンプの作成:ISOフォーマット、UTC(世界標準時)
// make Timestamp: ISO format, UTC
var timestamp:String = makeTimeStamp()

// バイトコード順にソートしつつ、クエリを連結
// クエリをそれぞれescape関数でURLエンコードしておく(×escapeMultiByte)
// sort query by the byte code ascending, and join them
// query is urlencoded by 'escape' method (not escapeMultiByte)
var query:String = "AWSAccessKeyId=Amazonへのアクセスキー&"
+ "ItemId=" + asin + "&"
+ "Operation=ItemLookup&"
+ "ResponseGroup=" + escape("Medium,Images,EditorialReview,Reviews") + "&"
+ "Service=AWSECommerceService&"
+ "Timestamp=" + escape(timestamp) + "&"
+ "Version=" + "2008-08-19";

// 署名の生成
// make Signature
var signatureText:String = ["GET", requestUri, requestPath, query].join("\n") // 署名する文書 Document for signature
var signature:String = makeSignature(signatureText, amazonSecretKey)

// URLエンコードした署名をクエリの最後に追加
// リクエストURLの完成
// add urlencoded Signature to query tail
// complete request Url
query += "&Signature=" + signature.replace(/\+/g,'%2B').replace('=', '%3D')
var requestUrl:String = "http://" + requestUri + requestPath + "?" + query

// amazonへアクセス(onAmazonRequestCompleteにてダウンロード完了後の処理を行う)
// access to Amazon (after download complete, onAmazonRequestComplete() executed)
amazonloader.addEventListener(Event.COMPLETE, onAmazonRequestComplete(var1, amazonloader));
amazonloader.load(new URLRequest());
}


private function makeTimeStamp():String{
// ISOフォーマットでタイムスタンプを生成 YYYY-MM-DDTHH:mm:ss
// 世界標準時(UTC)を採用
var timestamp:String = timeStamper.getUTCFullYear() + "-"
+ to2size(timeStamper.getUTCMonth() + 1) + "-"
+ to2size(timeStamper.getUTCDate()) + "T"
+ to2size(timeStamper.getUTCHours()) + ":"
+ to2size(timeStamper.getUTCMinutes()) + ":"
+ to2size(timeStamper.getUTCSeconds())
return timestamp
}

private function to2size( data:Number ):String{
// 日付や時分秒が10未満の場合、2桁となるよう左に0を挿入
var strData:String = data.toString() // 返却する文字列
if (data < 10) {
strData = "0" + strData
}
return strData
}

private function makeSignature(signatureText:String, key:String):String{
// HMAC-SHA256署名の作成
// 署名する文書と、ハッシュキーを引数として受取り、String型の署名データを返す
// generate HMAC-SHA256 signature
// accept "document for signature(String)" and "signature key(String)" as variables
// return signature(String)

//HMAC-SHA256署名を行うクラス
var hmac256:HMAC = new HMAC(new SHA256());

// 署名する文書のバイトデータ ByteArray for signatured document
var signatureTextBytes:ByteArray = new ByteArray();
signatureTextBytes.writeUTFBytes(signatureText)
// 署名用のハッシュキーのバイトデータ ByteArray for signature key (AWS secret key)
var keyBytes:ByteArray = new ByteArray();
keyBytes.writeUTFBytes(key)

// HMAC-SHA256署名を行い、ダイジェストを生成
// generate Digest (HMAC-SHA256)
var digest256:ByteArray = hmac256.compute(keyBytes, signatureTextBytes);

// ダイジェストをBase64でString型にエンコード
// encode Digest(ByteArray) to String by Base64encoding
var signature:String = Base64.encodeByteArray(digest256)

return signature
}


BeteArrayをnewしてから、writeUTFBytesで中身を書き込むのが味噌。
as3cryptoのテストコードではHexというクラスをnewして、Hex.toArray("署名したいドキュメント")でByteArray型の値を生成していたんだけど、こちらを使うとSignatureが不正になってしまった。

SignatureのURLエンコードはreplaceメソッドで手作業。。。
最初はescape関数を使ってたんだけど、+が上手く%2Bに変換されないんだよね。
エラー続発につき、とりあえず+と=だけ変換するように改良。

ちなみに、ダウンロード終了後に呼び出すイベントハンドラ、onAmazonRequestCompleteメソッドには複数の引数を与えてる。


amazonloader.addEventListener(Event.COMPLETE, onAmazonRequestComplete(var1, amazonloader));


これは、以下のように、イベントハンドラの中で、さらにfunctionをreturnすることで可能となる。


// AmazonのXMLのNamespaceをnewしておく。リクエスト時の"Version"との一致が必須
// make Amazon XML Namespace instance. Namespace Version needs to be as same as "Version" at request
private var ns:Namespace = new Namespace("http://webservices.amazon.com/AWSECommerceService/2008-08-19");

// ダウンロード終了イベントを受け取るメソッド
// Method accepting download complete event
private function onAmazonRequestComplete(var1:int, amazonloader:URLLoader):Function{
// 実際にイベントを処理するfunctionを返す
// return function which handles Event actually
return function(e:Event):void{
default xml namespace = ns;
var response : XML = new XML(amazonloader.data);

// 以下、XML操作...
// And read and write XML...
}
}


難産だった。。。

2 件のコメント:

しかじろう さんのコメント...

同じ事で悩んでたので助かりました!ありがとう!

co さんのコメント...

ちんぷんかんぷんだった、AWSの利用方法ですが、とても参考になりましたー!
ありがとうございました!