2011年6月3日金曜日

Evernoteにアクセスする その4 HMAC-SHA1編


ここまでサンプルを元にEvernoteにアクセスしてきましたが、
SimpleOAuthRequestの中身を見てOAuthアクセスへの理解を
深めておきましょう。。。。って思ったんですけど、PLANTEXTでアクセスしているので
HMAC-SHA1でアクセスしてみましょう。

以下が元ソースです。
見られる場合は展開してください。


/**
* Copyright 2008 by EverNote Corporation. All rights reserved.
*/

package com.evernote.oauth.consumer;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/**
* This is a very simple implementation of an OAuth consumer request which can
* be used to ask an OAuth service provider for either a Request Token or
* an Access Token. It only handles PLAINTEXT authentication, and it only goes
* over a GET transport. As a result, it should only be used over SSL.
*
* @author Dave Engberg
*/
public class SimpleOAuthRequest {

/**
* Random number generator for creating OAuth nonces
*/
private static final Random random = new Random();

/**
* The URL of the OAuth service Provider that we should hit to request a
* token.
*/
private String providerUrl;

/**
* A mapping containing all of the OAuth parameters that will be passed in
* the reply.
*/
private Map parameters = new HashMap();

/**
* Constructs a request object that can be used to make token requests from
* an OAuth provider.
*
* @param providerUrl the base URL to request a Request or Access token
* @param consumerKey the OAuth consumer key, given by the Service Provider
* @param consumerSecret the OAuth consumer secret, given by the Provider
* @param tokenSecret if non-null, this is the previous oauth_token_secret
* that should be used in signing this request. If null, this will assume
* that this message does not include a token secret in its signature
*/
public SimpleOAuthRequest(String providerUrl, String consumerKey,
String consumerSecret, String tokenSecret) {
this.providerUrl = providerUrl;
setParameter("oauth_consumer_key", consumerKey);
String signature = consumerSecret + "&";
if (tokenSecret != null) {
signature += tokenSecret;
}
setParameter("oauth_signature", signature);
setParameter("oauth_signature_method", "PLAINTEXT");
setParameter("oauth_timestamp",
Long.toString(System.currentTimeMillis() / 1000));
setParameter("oauth_nonce",
Long.toHexString(random.nextLong()));
setParameter("oauth_version", "1.0");
}

/**
* Sets one of the query string parameters for the request that will be
* made to the OAuth provider. The value will be URL encoded before adding
* to the URL.
*
* @param name the name of the parameter to be set
* @param value the string value, unencoded
*/
public void setParameter(String name, String value) {
parameters.put(name, value);
}

/**
* Encodes this request as a single URL that can be opened.
*/
public String encode() throws IOException {
StringBuilder sb = new StringBuilder();
sb.append(providerUrl);
boolean firstParam = providerUrl.indexOf('?') < 0;
for (Map.Entry parameter : parameters.entrySet()) {
if (firstParam) {
sb.append('?');
firstParam = false;
} else {
sb.append('&');
}
sb.append(parameter.getKey());
sb.append('=');
sb.append(URLEncoder.encode(parameter.getValue(), "UTF-8"));
}
return sb.toString();
}

/**
* Sends the request to the OAuth Provider, and returns the set of reply
* parameters, mapped from name to decoded value.
*
* @throws IOException if a problem occurs making the request or getting the
* reply.
*/
public Map sendRequest() throws IOException {

HttpURLConnection connection =
(HttpURLConnection)(new URL(encode())).openConnection();
int responseCode = connection.getResponseCode();
if (responseCode != HttpURLConnection.HTTP_OK) {
throw new IOException("Server returned error code: " + responseCode +
" " + connection.getResponseMessage());
}
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(connection.getInputStream()));
String result = bufferedReader.readLine();
Map responseParameters = new HashMap();
for (String param : result.split("&")) {
int equalsAt = param.indexOf('=');
if (equalsAt > 0) {
String name = param.substring(0, equalsAt);
String value =
URLDecoder.decode(param.substring(equalsAt + 1), "UTF-8");
responseParameters.put(name, value);
}
}
return responseParameters;
}

}



これをHMAC-SHA1で処理を行う場合は、oauth_signatureの指定に
・HTTPメソッド
・リクエストURL
・リクエストパタメータ
これらをURLエンコードして、&で結合してHMAC-SHA1で16進のダイジェスト値を作って
Base64エンコード後、URLエンコードした値をoauth_signatureに指定します。

オリジナルでEvernoteRequestクラスを作成してアクセスしてみましょう。
※動作確認用なので汚いのは許して><

以下がHMAC-SHA1でアクセスしたソース


/**
* Copyright 2008 by EverNote Corporation. All rights reserved.
*/

package jp.co.ziro.evernote.controller;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;
import java.util.logging.Logger;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;
import org.slim3.util.ApplicationMessage;

/**
* This is a very simple implementation of an OAuth consumer request which can
* be used to ask an OAuth service provider for either a Request Token or an
* Access Token. It only handles PLAINTEXT authentication, and it only goes over
* a GET transport. As a result, it should only be used over SSL.
*
* @author Dave Engberg
*/
public class EvernoteRequest {

@SuppressWarnings("unused")
private static Logger logger = Logger.getLogger(EvernoteRequest.class.getName());
private static final String consumerKey = ApplicationMessage
.get("evernote.api.key");
private static final String consumerSecret = ApplicationMessage
.get("evernote.api.secret");
private static final String urlBase = ApplicationMessage
.get("evernote.api.baseUrl");
private static final String requestUrl = urlBase
+ ApplicationMessage.get("evernote.api.requestTokenUrl");
/**
* ベースのURL
* @return
*/
public static String getBaseUrl() {
return urlBase;
}

/**
* Random number generator for creating OAuth nonces
*/
private static final Random random = new Random();

/**
* A mapping containing all of the OAuth parameters that will be passed in
* the reply.
*/
private Map parameters = new TreeMap();

/**
* Constructs a request object that can be used to make token requests from
* an OAuth provider.
*/
public EvernoteRequest() {
setParameter("oauth_consumer_key", consumerKey);
//暗号化を行う
setParameter("oauth_signature_method", "HMAC-SHA1");
setParameter("oauth_timestamp", getTimestamp());
setParameter("oauth_nonce", Long.toHexString(random.nextLong()));
setParameter("oauth_version", "1.0");
}
/**
* タイムスタンプを作成
* @return
*/
private String getTimestamp() {
return Long.toString(System.currentTimeMillis() / 1000);
}

/**
* Sets one of the query string parameters for the request that will be made
* to the OAuth provider. The value will be URL encoded before adding to the
* URL.
*
* @param name
* the name of the parameter to be set
* @param value
* the string value, unencoded
*/
public void setParameter(String name, String value) {
parameters.put(name, value);
}

/**
* Encodes this request as a single URL that can be opened.
*/
private String encode() {
return requestUrl + "?" + join();
}

/**
* HTTP引数の連結
* @return
*/
private String join() {
StringBuilder sb = new StringBuilder();
boolean firstParam = true;
for (Map.Entry parameter : parameters.entrySet()) {
if (firstParam) {
firstParam = false;
} else {
sb.append('&');
}
sb.append(parameter.getKey());
sb.append('=');
try {
sb.append(URLEncoder.encode(parameter.getValue(), CHARSET));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("エンコード時の例外", e);
}
}
return sb.toString();
}

/**
* 文字コード
*/
private static final String CHARSET = "UTF-8";
/**
* キー作成用のGETメソッド名
*/
private static final String REQUEST_METHOD = "GET";

/**
* Sends the request to the OAuth Provider, and returns the set of reply
* parameters, mapped from name to decoded value.
*
* @throws IOException
* if a problem occurs making the request or getting the reply.
*/
public Map sendRequest(String tokenSecret) throws IOException {
// 署名対象のテキストを作成
String secret = consumerSecret + "&";
if (tokenSecret != null) {
secret += tokenSecret;
}

String hmac = "HmacSHA1";

String encodeUrl = URLEncoder.encode(requestUrl,CHARSET);
String encodeJoin = URLEncoder.encode(join(),CHARSET);

String signatureBaseString = REQUEST_METHOD + "&" + encodeUrl + "&" + encodeJoin;
logger.info(signatureBaseString);

byte[] secretyKeyBytes = secret.getBytes(CHARSET);
SecretKeySpec secretKeySpec = new SecretKeySpec(secretyKeyBytes,hmac);
Mac mac;
try {
mac = Mac.getInstance(hmac);
mac.init(secretKeySpec);
} catch (NoSuchAlgorithmException e1) {
throw new RuntimeException("暗号化インスタンス取得失敗",e1);
} catch (InvalidKeyException e1) {
throw new RuntimeException("暗号化インスタンス取得失敗",e1);
}

String signature = null;
byte[] data;
byte[] rawHmac;
try {
data = signatureBaseString.getBytes(CHARSET);
rawHmac = mac.doFinal(data);
Base64 encoder = new Base64();
signature = new String(encoder.encode(rawHmac));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(CHARSET + " is unsupported!", e);
}
setParameter("oauth_signature", signature);

HttpURLConnection connection = (HttpURLConnection) (new URL(encode())).openConnection();
int responseCode = connection.getResponseCode();
// リクエストが正常じゃない場合
if (responseCode != HttpURLConnection.HTTP_OK) {
throw new IOException("Server returned error code: " + responseCode
+ " " + connection.getResponseMessage());
}
return createResponseMap(connection);
}

/**
* レスポンスの値を取得
*
* @param connection
* @return
*/
private Map createResponseMap(HttpURLConnection connection) {

BufferedReader bufferedReader;
String result;
try {
bufferedReader = new BufferedReader(new InputStreamReader(
connection.getInputStream()));
result = bufferedReader.readLine();
} catch (IOException e) {
throw new RuntimeException("読み込み時の例外", e);
}

Map responseParameters = new HashMap();
for (String param : result.split("&")) {
int equalsAt = param.indexOf('=');
if (equalsAt > 0) {
String name = param.substring(0, equalsAt);
String value;
try {
value = URLDecoder.decode(param.substring(equalsAt + 1),
CHARSET);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("エンコード時の例外", e);
}
responseParameters.put(name, value);
}
}
return responseParameters;
}
}



・・・本当に汚い、、、
で、これを利用してindex時に


EvernoteRequest oauthRequestor = new EvernoteRequest();
String thisUrl = request.getRequestURL().toString();
String cbUrl = thisUrl.substring(0, thisUrl.lastIndexOf('/') + 1);
cbUrl = cbUrl + "callback";

oauthRequestor.setParameter("oauth_callback", cbUrl);
Map reply = oauthRequestor.sendRequest(null);

String requestToken = reply.get("oauth_token");
String tokenSecret = reply.get("oauth_token_secret");
String authorizationUrl = authorizationUrlBase + "?oauth_token=" + requestToken;

sessionScope("tokenSecret",tokenSecret);


という風にします。
すべての引数を指定した後にoauth_signatureを生成するので
sendRequest()にtokenSecretを持ってきています。(コールバック時に利用)

リクエストトークンとともにtokenSecretは取得できます。
アクセストークンを取得する際に使用するtokenSecretをセッションに残しています。
で以下がコールバックの処理。


EvernoteRequest oauthRequestor = new EvernoteRequest();

String requestToken = requestScope("oauth_token");
String verifier = requestScope("oauth_verifier");
String tokenSecret = sessionScope("tokenSecret");

oauthRequestor.setParameter( "oauth_token", requestToken);
oauthRequestor.setParameter( "oauth_verifier", verifier);

//取得する
Map reply = oauthRequestor.sendRequest(tokenSecret);


こちらはセッションからtokenSecretを取得して指定しているだけですね。

んーAuthrorization Headerでアクセスしたいですよねー。
アクセスもPOSTでもないですし。。。本物はそうしてみようっ。と。

次回、OAuthを読んでいくか、APIアクセスをもう少し行ってみるか悩んでます。
それを記述するのはOAuthの仕様なだけなのでそっち読めば良いかなぁ、、、と。

0 件のコメント: