最近サービスづいてきています。OAuthばっかりです。
今度はJavaでmixiにアクセスです!
まず、、、RESTful APIでアクセスするには
まずmixiアプリの登録が必要です。
http://developer.mixi.co.jp/appli/pc/pc_prepare/add_app_flowアプリケーションを登録できたら、
アプリケーションに対してConsumer KeyとConsumer Secretが発行されます。
これを元にOAuthアクセスを行います。
まずHMAC-SHA1と呼ばれる暗号化を行う為のインスタンスを生成します。
- byte[] secretyKeyBytes = secret.getBytes("UTF-8");
- secretKeySpec = new SecretKeySpec(secretyKeyBytes,hmac);
- mac = Mac.getInstance(hmac);
- mac.init(secretKeySpec);
byte[] secretyKeyBytes = secret.getBytes("UTF-8");
secretKeySpec = new SecretKeySpec(secretyKeyBytes,hmac);
mac = Mac.getInstance(hmac);
mac.init(secretKeySpec);
ここでのsecretはConsumer Secretに「&」を付与したものになります。
secretKeySpecはjavax.crypto.spec.SecretKeySpecになります。
macはjavax.crypto.Macですね。
URLに設定する引数を作成します。
- params.put("oauth_consumer_key", KEY);
- params.put("oauth_signature_method", "HMAC-SHA1");
- params.put("oauth_timestamp", String.valueOf(cal.getTimeInMillis()).substring(0,10));
- params.put("oauth_version", "1.0");
- params.put("oauth_nonce", nonce);
- params.put("xoauth_requestor_id", viewerId);
- params.put("format", "atom");
params.put("oauth_consumer_key", KEY);
params.put("oauth_signature_method", "HMAC-SHA1");
params.put("oauth_timestamp", String.valueOf(cal.getTimeInMillis()).substring(0,10));
params.put("oauth_version", "1.0");
params.put("oauth_nonce", nonce);
params.put("xoauth_requestor_id", viewerId);
params.put("format", "atom");
このparamsはURLを作成する為のオリジナルクラスのマップです。
oauth_consumer_keyに設定しているKEYはConsumer Keyですね。
oauth_signature_methodは暗号化を示す"HMAC-SHA1"を固定で指定します。
oauth_timestampは時刻を設定します。
oauth_versionはmixiに指定された文字列
oauth_nonceはリクエスト毎に違うランダムな文字列です。UUID.randomUUID().toString()などで生成します。
xoauth_requestor_idは対象のユーザIDです。
formatはatomを設定していますが、最終的に取得するデータの形式でjsonでも取得できます。
次にシグネチャを取得する為の文字列を取得します。
- String method = "GET";
- String http ="http://" + endpoint + requestURI ;
- String args = canonicalQS;
- String toSign;
- try {
- toSign = URLEncoder.encode(method,"UTF-8")+"&"+
- URLEncoder.encode(http,"UTF-8")+"&"+
- URLEncoder.encode(args,"UTF-8");
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException(e);
- }
String method = "GET";
String http ="http://" + endpoint + requestURI ;
String args = canonicalQS;
String toSign;
try {
toSign = URLEncoder.encode(method,"UTF-8")+"&"+
URLEncoder.encode(http,"UTF-8")+"&"+
URLEncoder.encode(args,"UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
endpointはmixiのエンドポイント、、、って公開して良いのかな?
requestURIは/people/{id}/@allです。{id}は取得したい人のIDを設定します。
canonicalQSは前に設定したマップから取得したURLの引数です。
これらの文字列をそれぞれURLエンコードして”&”で連結します。
その文字列を最初に作ったmacを元にしてシグネチャを作成します。
- String signature = null;
- byte[] data;
- byte[] rawHmac;
- try {
- data = stringToSign.getBytes(CHARSET);
- rawHmac = mac.doFinal(data);
- Base64 encoder = new Base64();
- signature = new String(encoder.encode(rawHmac));
- signature = signature.substring(0, hmac.length()-2);
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException(CHARSET + " is unsupported!", e);
- }
-
- try {
- signature = URLEncoder.encode(signature, CHARSET).replace("+", "%20")
- .replace("*", "%2A").replace("%7E", "~");
- } catch (UnsupportedEncodingException e) {
- }
String signature = null;
byte[] data;
byte[] rawHmac;
try {
data = stringToSign.getBytes(CHARSET);
rawHmac = mac.doFinal(data);
Base64 encoder = new Base64();
signature = new String(encoder.encode(rawHmac));
signature = signature.substring(0, hmac.length()-2);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(CHARSET + " is unsupported!", e);
}
try {
signature = URLEncoder.encode(signature, CHARSET).replace("+", "%20")
.replace("*", "%2A").replace("%7E", "~");
} catch (UnsupportedEncodingException e) {
}
この文字列には改行が入ってますので2文字削除しています。
そのあと、文字列の変換をかけてます。
で、、、作成されたシグネチャを元に、アクセスするURLを作成します。
- String url = "http://" + endpoint + requestURI + "?" + canonicalQS + "&oauth_signature=" + sig;
String url = "http://" + endpoint + requestURI + "?" + canonicalQS + "&oauth_signature=" + sig;
でここにアクセスするとそのユーザの情報がXML化されて取得できるってわけです。
但し、ここで問題が発生します。
アクセスすると401エラーが返ってきます。
だので、、、mixiアプリの事を調べていくと。。。
- 対象ユーザが対象のmixiアプリを起動していない、もしくは起動から一定時間が経過した後にRESTful APIにアクセスを行った際には、
- HTTPレスポンスコードとして「401 Unauthorized」が返却されます。
対象ユーザが対象のmixiアプリを起動していない、もしくは起動から一定時間が経過した後にRESTful APIにアクセスを行った際には、
HTTPレスポンスコードとして「401 Unauthorized」が返却されます。
ををを、、、そういう事なのね。
ようはしばらくmixiアプリを起動した人の情報を
xoauth_requestor_idに設定してあげないと
アクセスできないってわけです。
APIを使うにはいろいろ画面遷移を考えないときついっぽいですね。
しかしこれでアクセスできました。
さぁ問題は何を作るかですね。。。。。
OAuthの仕様は
http://oauth.googlecode.com/svn/spec/ext/consumer_request/1.0/drafts/1/spec.htmlにあります。
シグネチャがうまく取れるまで結構かかりました。
なのでここにある仕様(文字列)を元にテストを書きました。
実際のコードはその他サービスなどにアクセスしている為
複雑なクラスになっているのを平たく記述しているので
ペローンって感じのコードになっていますが、テストは通ります。
- package jp.co.ziro.surpre.helper;
-
- import static org.junit.Assert.*;
-
- import java.io.UnsupportedEncodingException;
- import java.net.URLEncoder;
- import java.util.Iterator;
- import java.util.Map;
- import java.util.TreeMap;
-
- import javax.crypto.Mac;
- import javax.crypto.spec.SecretKeySpec;
-
- import org.apache.commons.codec.binary.Base64;
- import org.junit.Test;
-
-
- public class OAuthServiceHelperTest {
-
- private static final String KEY = "dpf43f3p2l4k3l03";
- private static final String SECRET = "kd94hf93k423kf44";
- private static final String endpoint = "provider.example.net";
- private static final String HMAC = "HmacSHA1";
- private static final String REQUEST_URI = "/profile";
- private static final String NONCE = "kllo9940pd9333jh";
- private static final String REQUEST_METHOD = "GET";
-
- @Test
- public void testOAuthService() {
-
- Map<String, String> paramMap = new TreeMap<String, String>();
-
-
- paramMap.put("oauth_consumer_key", KEY);
- paramMap.put("oauth_signature_method", "HMAC-SHA1");
- paramMap.put("oauth_timestamp", "1191242096");
- paramMap.put("oauth_version", "1.0");
- paramMap.put("oauth_nonce", NONCE);
-
- String canonicalQS = canonicalize(paramMap);
-
- String method = REQUEST_METHOD;
- String http ="http://" + endpoint + REQUEST_URI ;
- String args = canonicalQS;
-
- String toSign;
- try {
- toSign = URLEncoder.encode(method,"UTF-8")+"&"+
- URLEncoder.encode(http,"UTF-8")+"&"+
- URLEncoder.encode(args,"UTF-8");
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException(e);
- }
-
- String hmac = hmac(toSign);
- hmac = hmac.substring(0, hmac.length()-2);
-
- assertEquals(hmac,"SGtGiOrgTGF5Dd4RUMguopweOSU=");
- }
-
- private String hmac(String stringToSign) {
- String signature = null;
- byte[] data;
- byte[] rawHmac;
- try {
- String secret = SECRET + "&";
- byte[] secretyKeyBytes = secret.getBytes("UTF-8");
- SecretKeySpec secretKeySpec = new SecretKeySpec(secretyKeyBytes,HMAC);
- Mac mac = Mac.getInstance(HMAC);
- mac.init(secretKeySpec);
- data = stringToSign.getBytes("UTF-8");
- rawHmac = mac.doFinal(data);
- Base64 encoder = new Base64();
- signature = new String(encoder.encode(rawHmac));
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- return signature;
- }
-
-
- private String percentEncodeRfc3986(String s) {
- String out;
- try {
- out = URLEncoder.encode(s, "UTF-8").replace("+", "%20")
- .replace("*", "%2A").replace("%7E", "~");
- } catch (UnsupportedEncodingException e) {
- out = s;
- }
- return out;
- }
-
- private String canonicalize(Map<String, String> sortedParamMap) {
- if (sortedParamMap.isEmpty()) {
- return "";
- }
- StringBuffer buffer = new StringBuffer();
- Iterator<Map.Entry<String, String>> iter = sortedParamMap.entrySet().iterator();
- while (iter.hasNext()) {
- Map.Entry<String, String> kvpair = iter.next();
- buffer.append(percentEncodeRfc3986(kvpair.getKey()));
- buffer.append("=");
- buffer.append(percentEncodeRfc3986(kvpair.getValue()));
- if (iter.hasNext()) {
- buffer.append("&");
- }
- }
- String cannoical = buffer.toString();
- return cannoical;
- }
-
-
- }
package jp.co.ziro.surpre.helper;
import static org.junit.Assert.*;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.junit.Test;
public class OAuthServiceHelperTest {
private static final String KEY = "dpf43f3p2l4k3l03";
private static final String SECRET = "kd94hf93k423kf44";
private static final String endpoint = "provider.example.net";
private static final String HMAC = "HmacSHA1";
private static final String REQUEST_URI = "/profile";
private static final String NONCE = "kllo9940pd9333jh";
private static final String REQUEST_METHOD = "GET";
@Test
public void testOAuthService() {
Map<String, String> paramMap = new TreeMap<String, String>();
//基本的な引数をすべて設定
paramMap.put("oauth_consumer_key", KEY);
paramMap.put("oauth_signature_method", "HMAC-SHA1");
paramMap.put("oauth_timestamp", "1191242096");
paramMap.put("oauth_version", "1.0");
paramMap.put("oauth_nonce", NONCE);
String canonicalQS = canonicalize(paramMap);
String method = REQUEST_METHOD;
String http ="http://" + endpoint + REQUEST_URI ;
String args = canonicalQS;
String toSign;
try {
toSign = URLEncoder.encode(method,"UTF-8")+"&"+
URLEncoder.encode(http,"UTF-8")+"&"+
URLEncoder.encode(args,"UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
String hmac = hmac(toSign);
hmac = hmac.substring(0, hmac.length()-2);
assertEquals(hmac,"SGtGiOrgTGF5Dd4RUMguopweOSU=");
}
private String hmac(String stringToSign) {
String signature = null;
byte[] data;
byte[] rawHmac;
try {
String secret = SECRET + "&";
byte[] secretyKeyBytes = secret.getBytes("UTF-8");
SecretKeySpec secretKeySpec = new SecretKeySpec(secretyKeyBytes,HMAC);
Mac mac = Mac.getInstance(HMAC);
mac.init(secretKeySpec);
data = stringToSign.getBytes("UTF-8");
rawHmac = mac.doFinal(data);
Base64 encoder = new Base64();
signature = new String(encoder.encode(rawHmac));
} catch (Exception e) {
throw new RuntimeException(e);
}
return signature;
}
private String percentEncodeRfc3986(String s) {
String out;
try {
out = URLEncoder.encode(s, "UTF-8").replace("+", "%20")
.replace("*", "%2A").replace("%7E", "~");
} catch (UnsupportedEncodingException e) {
out = s;
}
return out;
}
private String canonicalize(Map<String, String> sortedParamMap) {
if (sortedParamMap.isEmpty()) {
return "";
}
StringBuffer buffer = new StringBuffer();
Iterator<Map.Entry<String, String>> iter = sortedParamMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, String> kvpair = iter.next();
buffer.append(percentEncodeRfc3986(kvpair.getKey()));
buffer.append("=");
buffer.append(percentEncodeRfc3986(kvpair.getValue()));
if (iter.hasNext()) {
buffer.append("&");
}
}
String cannoical = buffer.toString();
return cannoical;
}
}
元のコードは
http://bit.ly/bVSEgtなども参考にしています。