Microsoft Graph を 標準Javaで使う

# 標準JavaパッケージでMicrosoft Graph

[Microsoft SDK for Java](https://github.com/microsoftgraph/msgraph-sdk-java) を使うといいのですが、これを使うと関連Jarファイルがたくさん必要です。本質的にやっていることを理解するために、標準JavaパッケージだけでMicrosoft Graphの問い合わせを書いてみます。


やることは、
1. Azure AD にアプリケーションを登録してクライアントIDとシークレットを作る（手順は[チュートリアル](https://docs.microsoft.com/ja-jp/graph/tutorials/java?tutorial-step=2)を参考）
2. Azure AD からOAuth2でアクセストークンをもらう。JSONで返ってくる。
3. Microsoft Graph API のエンドポイントに問い合わせする。JSON返ってくる。
だけです。

# ひとまずCurlコマンドで書くとこうなります


### アクセストークンを取得する。
OAuthの手続きに従い、まずはアクセストークンを　Azure Active Directoryから取得する。
取得する先のURL（エンドポイント）は、

**https://login.microsoftonline.com/テナントID/oauth2/v2.0/token**

テナントIDはAzure ADのテナント概要にあり。
ここに向かって作成したクライアントIDとシークレットをPOST。
scopeはURLをエスケープしているので%3Aや%2Fとなっています。どんなのが指定できるか詳しくは[こちら](https://docs.microsoft.com/ja-jp/azure/active-directory/develop/v2-permissions-and-consent)。grant_typeはOAuthのパラメータで固定値です。

```
curl -d "client_id=クライアントID" \
 -d "scope=https%3A%2F%2Fgraph.microsoft.com%2F.default" \
 -d "client_secret=クライアントシークレット" \
 -d "grant_type=client_credentials" \
 -H "Content-Type: application/x-www-form-urlencoded" \
 -X POST https://login.microsoftonline.com/テナントID/oauth2/v2.0/token
```
アクセストークンが取得できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33575/3eee94c8-450c-9aea-e79c-ecc7bf3d7bba.png)



### アクセストークンを使ってMicrosoft Graph APIにお問い合わせ
先のアクセストークンをコピーして今度はMicrosoft Graph APIに問い合わせや操作を行います。例えばグループメンバーを取得したかったらこんな感じになります。
エンドポイントはこちら。

**https://graph.microsoft.com/v1.0/groups/{group-id}/members**

エンドポイントがどこかや形式は[Graph Explorer](https://developer.microsoft.com/ja-jp/graph/graph-explorer)を使って確認します。
省略しますが、グループIDはAzure ADにログインし「すべてのグループ」一覧に表示されているIDを事前に取得しています。

```
curl -H GET 'https://graph.microsoft.com/v1.0/groups/グループID/members'\
 -H 'Content-Type: application/json;charset=utf-8'\
 -H 'Authorization: Bearer アクセストークン'
```

結果のスクリーンショットは省略してますが、JSON形式で値が返ってくるのがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33575/87a202dd-4a1c-a3fb-8c4a-a3afe0f7d6ab.png)

# このHTTPの処理をJavaで書くと

今回はJava８で記述しています。
またエラー処理は省略してます。ネットワークがプロキシ配下の場合はJava環境変数でプロキシ設定を行ってください。

```App.java
import java.io.*;
import java.net.*;

//標準パッケージと言いつつここだけjavax追加ライブラリを使いました。
//ユニコードエスケープされた文字もデコードしてくれます
//Jarファイルはこちら
//https://repo1.maven.org/maven2/org/glassfish/javax.json/1.1.4/javax.json-1.1.4.jar
import javax.json.*;

public class App {

	public static void main(String[] args) throws Exception {
		
		String tenant_id = "テナントID";
		String client_id = "クライアントID";
		String client_secret = "クライアントシークレット";
		String group_id = "グループID";
		
		HttpURLConnection conn = null;
		InputStream input = null;
		HttpURLConnection conn2 = null;
		InputStream input2 = null;
		try {
			//①
			//Oauth2でアクセストークンを取得
			
			//Java標準のHTTPリクエスト処理
			URL url = new URL("https://login.microsoftonline.com/" + tenant_id + "/oauth2/v2.0/token");
			conn = (HttpURLConnection) url.openConnection();
			conn.setConnectTimeout(5000);// 接続にかかる時間　ミリ秒
			conn.setReadTimeout(5000);// データの読み込みにかかる時間　ミリ秒
			conn.setRequestMethod("POST");// HTTPメソッド
			conn.setUseCaches(false);// キャッシュ利用
			conn.setDoOutput(true);// リクエストのボディの送信を許可(GETのときはfalse,POSTのときはtrueにする)
			conn.setDoInput(true);// レスポンスのボディの受信を許可
			conn.addRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
			String data = //ポストするデータ
				  "client_id=" + client_id
				+ "&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default"  //java.net.URLEncoderでエスケープしてもよい
				+ "&client_secret=" + client_secret
				+ "&grant_type=client_credentials";
			conn.getOutputStream().write(data.getBytes("utf-8"));
			conn.getOutputStream().close(); // send
			
			//結果を受け取ってJsonオブジェクトに変換
			//JSONライブラリを使わない場合は戻ってきたテキストを自力でパースするだけです
			//Curlで取得していた文字列と同じようなものが返ってきています
			int code = conn.getResponseCode();
			input = (code == 200 ? conn.getInputStream() : conn.getErrorStream());
			JsonReader jsonReader = Json.createReader(new BufferedReader(new InputStreamReader(input, "utf-8")));
			JsonObject json = jsonReader.readObject();
			jsonReader.close();
			conn.disconnect();
			
			//アクセストークンが取り出せました！
			String access_token = json.getString("access_token");
			
			
			//②
			//次はMicrosoft Graph APIにグループメンバーを問い合わせ
			URL url2 = new URL("https://graph.microsoft.com/v1.0/groups/" + group_id + "/members");
			conn2 = (HttpURLConnection) url2.openConnection();
			conn2.setConnectTimeout(5000);// 接続にかかる時間　ミリ秒
			conn2.setReadTimeout(5000);// データの読み込みにかかる時間　ミリ秒
			conn2.setRequestMethod("GET");
			conn2.setUseCaches(false);// キャッシュ利用
			conn2.setDoOutput(false);// リクエストのボディの送信を許可(GETのときはfalse,POSTのときはtrueにする)
			conn2.setDoInput(true);// レスポンスのボディの受信を許可
			conn2.setRequestProperty("Authorization", "Bearer " + access_token); //取得したアクセストークン
			conn2.setRequestProperty("Accept", "application/json"); //これ重要です！！！　ハマったポイント
			conn2.connect();
		
			int code2 = conn2.getResponseCode();
			input2 = (code2 == 200 ? conn2.getInputStream() : conn2.getErrorStream());
			JsonReader jsonReader2 = Json.createReader(new BufferedReader(new InputStreamReader(input2, "utf-8")));
			JsonStructure json2 = jsonReader2.read();
			jsonReader2.close();
			conn2.disconnect();
		
			JsonArray members = json2.asJsonObject().getJsonArray("value");
			
			//グループのメンバーが取れました！
			System.out.println(members);
		}
		catch(Error e) {
			e.printStackTrace();
		}
		finally {
			if(input != null) try { input.close(); } catch(Exception e){}
			if(conn != null) try { conn.disconnect(); } catch(Exception e){}
			if(input2 != null) try { input2.close(); } catch(Exception e){}
			if(conn2 != null) try { conn2.disconnect(); } catch(Exception e){}
		}
	}
}
```

コンパイルして実行するとちゃんと取得できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33575/19c49803-9889-d067-d123-6ce7e54abe19.png)

# まとめ

SDKを使わなくても単純な取得は可能で、複数の追加Jarファイルがいらないのと、サーバとの手続きを隠蔽化されていないので本質を掴むことはできます。
一方、SDKを使えばトークン有効期限切れや、エンドポイントのURLを意識することなくコーディングに集中でき、また、Graph Explorerがコードスニペットを出力してくれるので生産性は上がるのではないかと思います。