登山情報共有サイト ヤマレコ のAPIで遊んでみた。OAuth認証編。

先日、個人的によく使っている登山情報共有サイト「[ヤマレコ](http://www.yamareco.com)」のAPIで[遊んでみました](http://qiita.com/shuetsu@github/items/392ee5889b2efc5a4a98)。が、その時はOAuth認証を必要としない（つまり何の権限もない人でも呼び出せる）APIしか使っていませんでした。というわけで今回はOAuth認証を試してみます。

Google App Engineにデプロイしました。
http://shuetsu512-yamareco.appspot.com

ソースコードはこちら
https://github.com/shuetsu/yamareco_api

# OAuthとは

今更ですがOAuthとは、あるサービスに対する操作を行うAPIを利用する権限を、ユーザの同意に基づいて別のサービスへと渡すための仕組みです。

WEBサービスでよく見かけるようになった、「twitterでログイン」とか「facebookでログイン」は、これを使っているわけです。今回は「ヤマレコでログイン」することになります。

# 事前の手続き

まず、あらかじめ[Web API利用手続き](https://github.com/shuetsu/yamareco_api)を行います。申し込んで数日で対応していただきました。

これにより、
・クライアントID
・クライアントシークレット
を発行してもらうことができます。さらに、
・認可コードを受け取るリダイレクトURL
をヤマレコ側に登録してもらいます。

# 認証の手順

ヤマレコWebAPIページにわかりやすく説明されています。
https://sites.google.com/site/apiforyamareco/api/oauth

※以降、エラー処理は割愛してます。

## 認可コードの取得

まずは認可コードを取得するために、ヤマレコの認証ページへ飛ぶリンクを設置します。このリンクには、クライアントIDおよびリダイレクトURLをパラメータに含めます。

今回はページが開かれた時に、javascriptからログイン中のユーザ情報を取得しに行き、失敗したら（＝未ログイン状態なら）、この認証のためのリンクを表示するようにしました。

また、ログインされているならばユーザ名を表示し、そのユーザの山行記録を取得する機能の呼び出しボタンを表示するようにしています。

``` example.js
	$.ajax({
		dataType: 'json',
		url: "/userinfo",
		cache: false,
		success: function(data){
			if (data.err == 0){
				uid = data.userinfo.uid;
				$('#login_user').html('こんにちは ' + data.userinfo.uname + ' さん<br/><input type="button" value="山行記録の読み込み" onclick="get_reclist(uid);"/>');
			}else{
				$('#login_user').html('<a href="https://api.yamareco.com/api/v1/oauth?client_id=<クライアントID>&redirect_uri=<リダイレクトURL>&response_type=code&scope=all">ヤマレコにログイン</a>');
			}
		}
	});
```

ユーザにはこのリンクからヤマレコへ移動し、ログインおよびこちらのアプリケーションがヤマレコの情報へアクセスすることへの同意を行ってもらいます。無事に同意が得られると、リダイレクトURLが認可コード付きで呼ばれます。

## アクセストークンの取得

リダイレクトURLには、認可コードを受け取るサーブレット(RecvCodeServlet)をマッピングしておきます。このサーブレットは、アクセストークンを取得するために、クライアントID、クライアントシークレット、認可コードをヤマレコにPOSTします。

``` RecvCodeServlet.java
public class RecvCodeServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		String code = req.getParameter("code"); // 認可コードを取得
		if (code != null){
			String param = "";
			param += "client_id=<クライアントID>&";
			param += "client_secret=<クライアントシークレット>&";
			param += "redirect_uri=<リダイレクトURL>&";
			param += "code=" + code + "&";
			param += "grant_type=authorization_code";
			Map<?, ?> r = JSON.decode(Util.apiPost("oauth/access_token", param, null));
			HttpSession session = req.getSession(true);
			session.setAttribute("ACCESS_TOKEN", r.get("access_token"));
		}
		req.getRequestDispatcher("/index.jsp").forward(req, resp);
	}
}
```

アクセストークンは以下のようなJSON形式のデータで送られてきます。

```
{"access_token":"<アクセストークン>","error":0,"error_message":""}
```

ここでは[JSONIC](http://jsonic.osdn.jp)を利用してパースしました。得られたアクセストークンは、セッションに保存しておきます。

## APIの利用

以降は、このアクセストークンをリクエストのauthorizationヘッダに付けてAPIを呼べばオーケーです。

以下のように、GET/POSTメソッドでAPIを呼ぶためのクラスを定義しておきます。

``` Util.java
public class Util {
	
	private static final String URL = "https://api.yamareco.com/api/v1/";
	
	public static String apiGet(String path, String accessToken){
		String ret = "";
		BufferedReader r = null;
		HttpURLConnection con = null;
		try {
		    URL url = new URL(URL + path);
		    con = (HttpURLConnection)url.openConnection();
		    con.setRequestMethod("GET");
		    if (accessToken != null){
		    	con.setRequestProperty("Authorization", "OAuth " + accessToken);
		    }
		    r = new BufferedReader(new InputStreamReader(con.getInputStream()));
		    String l;
		    while ((l = r.readLine()) != null) {
		        ret += l;
		    }
		} catch (Exception e1) {
			ret = "{\"err\":1}";
		} finally {
		    if (r != null){
		    	try{r.close();}catch(IOException e2){}
		    }
		    if (con != null){con.disconnect();}
		}
		return ret;
	}
	
	public static String apiPost(String path, String param, String accessToken){
		String ret = "";
		BufferedReader r = null;
		HttpURLConnection con = null;
		try {
		    URL url = new URL(URL + path);
		    con = (HttpURLConnection)url.openConnection();
            con.setRequestMethod("POST");
            con.setDoOutput(true);
		    if (accessToken != null){
		    	con.setRequestProperty("Authorization", "OAuth " + accessToken);
		    }
		    
		    OutputStream out = con.getOutputStream();
		    out.write(param.getBytes());
		    out.flush();
		    out.close();
		    
		    r = new BufferedReader(new InputStreamReader(con.getInputStream()));
		    String l;
		    while ((l = r.readLine()) != null) {
		        ret += l;
		    }
		} catch (Exception e1) {
			ret = "{\"err\":1}";
		} finally {
		    if (r != null){
		    	try{r.close();}catch(IOException e2){}
		    }
		    if (con != null){con.disconnect();}
		}
		return ret;
	}

}
```

例えば、この手順の最初で呼び出されていたユーザ情報を取得するサーブレットは、以下のような実装になります。

``` UserInfoServlet.java
public class UserInfoServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		resp.setContentType("application/json; charset=utf-8");
		PrintWriter w = resp.getWriter();
		HttpSession session = req.getSession(true);
		String accessToken = (String)session.getAttribute("ACCESS_TOKEN");
		if (accessToken != null){
			w.print(Util.apiGet("getUserInfo/", accessToken));
		}else{
			w.print("{\"err\":1}");
		}
		w.close();
	}

}
```