ひとまずのまとめ
事前準備部分
- Amazonアソシエイトプログラムにアカウントを作る
- Access Key ID と Secret Key を Amazon アソシエイトのサイトから取得する
- アカウント作成時に一緒に生成されるアソシエイトIDを取得する
コーディング部分(準備段階)
- パラメータを用意(TimestampとSignature以外)
- パラメータに Timestamp を付与
- リクエスト部分とパラメータに対して Signature を HMAC-SHA256 で準備(参考: Swift 4で HMAC-SHA256)
- Signature を base64 でエンコードした後に Signature を再度エンコード
コーディング部分(通信段階)
- 準備段階で用意した URL を使ってダウンロードを行う
- XMLで検索結果が返ってくるのでパースして結果を利用する
1. Amazonアソシエイトプログラムにアカウントを作る
これは Query を投げる際にアカウントが無いと AccessKey が affiliate.amazon.co.jp で認識できないと判定されて、Query できなくなっている。あと、Required 指定されている(2017-10-02現在) AssociateTag を指定するために必要。調査段階で先に aws.amazon.com にも同じメールアドレスを使ってアカウント作ってしまったので、そちらのアカウントも必須かどうかまでは未確認です。
API Version=2013-08-01 API Reference (公式)
後述のパラメータの意味や指定方法なども上記サイトからたどれます。が、サンプルは必ずしも正しいとは限らないので参考情報と思った方が良いかもしれない。
2. Access Key ID と Secret Key を Amazon アソシエイトのサイトから取得する
[ツール] → [Product Advertising API] → [認証キーの管理] からアクセスキーIDとシークレットキーを取得。アクセスキーIDは AWSAccessKeyID に指定する値となり(URLのQueryに指定する)、シークレットキーは HMAC-SHA256 を計算する際のキーになる(内部計算用の秘密鍵なので、URLに指定するものでは無い)。
3. アカウント作成時に一緒に生成されるアソシエイトIDを取得する
Amazon アソシエイトのサイトにログインすると、画面右上の方に表示されている。この値は AssociateTag に指定する値になる。
4. パラメータを用意(TimestampとSignature以外)
パラメータを追加する順番があるので気をつける。わからなくなったら以下公式のツールを使って、どのようにオーダーされるか確認していその通りに合わせると楽。パラメータを Array で格納してバイトオーダーでソートできれば、並び順は気にしなくても良いかもしれない。
AWSAccessKeyId= | Amazon アソシエイトで作ったアクセスキー |
AssociateTag= | Amazon アソシエイトでアカウント作成時に生成されたアソシエイトID |
IdType=ISBN | Operation=ItemLookupの場合に指定。ItemLookupではない場合は除外
ISBNを指定する場合。 |
ItemId=4568504287 | Operation=ItemLookupの場合に指定。ItemLookupではない場合は除外
実際のISBN値。 |
Operation=ItemSearch | TitleやAuthorのパラメータを使う場合はItemSearch |
ResponseGroup=ItemAttributes,Images | 結果を受け取る際の項目内容。署名する前に “,” (カンマ)はエンコードする必要がある。 |
SearchIndex=Books | 書籍を検索する場合の指定。他のジャンルを指定したい場合はここを変更して、他の検索用パラメータを調整する |
Service=AWSECommerceService | サービス名。固定値 |
Timestamp=2017-01-01T00:00:00Z | 署名前のタイムスタンプ。”:” (コロン)は署名する前にエンコードする必要がある。 |
Title= | 書籍をタイトルで検索する場合。当然日本語はエンコードする必要がある。並び順は Timestamp より後ろ。 |
5. パラメータに Timestamp を付与
var args = Array<String>()
でパラメータをひとまず Array でまとめる場合のサンプル。
private var timestamp: DateFormatter {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(identifier: "GMT")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
return formatter
}
args.append(String(format: "Timestamp=%@", self.timestamp.string(from: Date()).urlAWSQueryEncoding()))
.urlAWSQueryEncoding() で、タイムスタンプの中の “-” (ハイフン)はそのままで “:” (コロン)はエンコードする。
extension String {
func urlAWSQueryEncoding() -> String {
var allowedCharacters = CharacterSet.alphanumerics
allowedCharacters.insert(charactersIn: "-")
if let ret = self.addingPercentEncoding(withAllowedCharacters: allowedCharacters ) {
return ret
}
return ""
}
}
6. リクエスト部分とパラメータに対して Signature を HMAC-SHA256 で準備
HMAC 計算部分だけ抜粋。このタイミングで base64encode もしてしまう。
extension String {
func hmac(algorithm: CryptoAlgorithm, key: String) -> String {
var result: [CUnsignedChar]
if let ckey = key.cString(using: String.Encoding.utf8), let cdata = self.cString(using: String.Encoding.utf8) {
result = Array(repeating: 0, count: Int(algorithm.digestLength))
CCHmac(algorithm.HMACAlgorithm, ckey, ckey.count-1, cdata, cdata.count-1, &result)
} else {
fatalError("Nil returned when processing input strings as UTF8")
}
return Data(bytes: result, count: result.count).base64EncodedString()
}
}
エンコード対象はHTTPサーバに対するクエリ部分とパラメータをまとめたものを指定する。このタイミングで手順2で取得したシークレットキーを使って HMAC-SHA256 の値を求める。
var params: String = ""
for arg in args {
params += (params.count == 0 ? "": "&") + arg
}
let signTarget = String(format: "GET\nwebservices.amazon.co.jp\n/onca/xml\n%@", params)
let signature = signTarget.hmac(algorithm: .SHA256, key: self.secretkey)
コード的には上記で視覚的には以下が署名対象の文字列のイメージ。イメージです。
GET webservices.amazon.co.jp /onca/xml AWSAccessKeyId=01234567890&AssociateTag=0123456789(---以下略---)
このタイミングでは https とかは気にしない。
7. Signature を base64 でエンコードした後に Signature を再度エンコード
base64エンコードした時点で “=” (イコール)などがくっついてくるので、それらをエンコードする。パラメータを全部揃えてからエンコードだと、本来必要な “=” とかもエンコードされてしまうので、Signature 部分に個別に行う。
以下は準備したパラメータ群(params)を “&” で連結したものの後ろに Signature をくっつける時にエンコードしてしまう。
ret = String(format: "?%@&Signature=%@", params, signature.urlNumericEncoding())
ここまでが URL のパラメータ部分。後のために “?” も先頭につけてしまう。
8. 準備段階で用意した URL を使ってダウンロードを行う
実際に投げる場合は https でクエリする。アプリ側から http でクエリするとセキュリティ的にアウトエラーで、実行時にエラーとなります。
https://webservices.amazon.co.jp/onca/xml%@
%@の部分は Signature もひっくるめたクエリになります。手順7の ret の値。
https でクエリをかければ、返ってくる内容(例えばイメージファイルのURLなど)のURLも全て https になって返ってきます。http でブラウザで投げると返ってくる内容も http で記述されて返されます。
URL を作る段階で、署名した時に使ったドメイン(webservices.amazon.co.jp)と URL 内で指定するドメインは同じである必要があります。異なると問い合わせ時に Signature の検算エラー扱いになります。
実際のURLは以下。投げてもエラーになります。
https://webservices.amazon.co.jp/onca/xml?AWSAccessKeyId=AKIAJ3SKECER6QTBW3YA&AssociateTag=foobarbuz-999&Operation=ItemSearch&ResponseGroup=ItemAttributes%2COffers%2CImages%2CReviews&SearchIndex=Books&Service=AWSECommerceService&Timestamp=2017-10-02T06%3A45%3A25Z&Title=%E3%83%80%E3%83%B3%E3%82%B8%E3%83%A7%E3%83%B3%E9%A3%AF&Signature=48x1Jg6uerLQpOj7F2rASmADQIkDYPQI8K4xxF50%2Bgg%3D
上記 Access key は削除済み。Associate tag はダミーです。参考まで。
9. XMLで検索結果が返ってくるのでパースして結果を利用する
これから実装。
to be continued…