Azureの小ネタ (改)

~Azureネタを中心に、色々とその他の技術的なことなどを~

Azure storage SDK for java V10 (その1)

Azure Storage SDK for Java に新しいバージョン(V10)が登場したので、少し調べてみた記録です。

https://github.com/Azure/azure-storage-java

しばらく気づかなかったのですが、以前からアナウンスされていたようです。

https://azure.microsoft.com/ja-jp/blog/build-richer-applications-with-the-new-asynchronous-azure-storage-sdk-for-java/

ざっくり説明すると、

  • ソフトウェアスタックの変更(Netty Httpとか)
  • RxJavaの非同期プログラミングモデル
  • Storage REST API と整合性がある低レベルAPI
  • 便利な高レベルAPI
  • スレッドセーフ
  • すべてのStorage SDKの一環したバージョン管理(各言語間なのか?

だそうです。

New SDK for BLOB 利用準備

Maven Repositoryにありますんで適当にプロジェクトを作って、以下を参照するようにしましょう。BLOB用は、現時点では、10.3.0となっておりPreviewは取れております。Queue用はまだPreviewみたいです(執筆時点)

https://mvnrepository.com/artifact/com.microsoft.azure/azure-storage-blob

<dependency>
    <groupId>com.microsoft.azure</groupId>
    <artifactId>azure-storage-blob</artifactId>
    <version>10.3.0</version>
</dependency>

新旧の比較

とりあえず能書きはいいのでコードを実行してみます。Java 11を利用してコード書いてます(ので var を使ってます)

コンテナの作成

以下は、コンテナを作成する旧コードです。接続文字列をパースして、CloudStorageAccountを作成して、そこからコンテナリファレンスを取得して、createIfNotExists()を呼び出す。旧SDKは基本同期メソッドしかないので、.NETのようにはいきませんが、慣れし親しんだコードです。

    var account = CloudStorageAccount.parse(connectionString);
    var client = account.createCloudBlobClient();
    var container = client.getContainerReference("v8container");
    container.createIfNotExists();

新SDKの場合は、以下です。サンプルをサクッともってきて実行してみました。github サンプルのコードは微妙にメソッドシグネチャが違っていたので動きませんでしたので、適当に修正してみます。おいおいちょっと待てよという長さになってしまいました。

    var creds = new SharedKeyCredentials(accountName, accountKey);
    var serviceURL = new ServiceURL(new URL("https://" + accountName + ".blob.core.windows.net"),
             StorageURL.createPipeline(creds, new PipelineOptions()));
    ContainerURL containerURL = serviceURL.createContainerURL("v10container");
    try {
        var response = containerURL.create().blockingGet();
        System.out.println("Container Create Response was " + response.statusCode());
    } catch (RestException e) {
        if (e instanceof RestException && ((RestException) e).response().statusCode() != 409) {
             throw e;
         } else {
             System.out.println("container already exists");
         }
     }

接続文字列をパースするクラスはなくなったみたいです。SharedKeyCredensitalsでアカウントとキーを設定します。SASとかOAuth トークンが使えるぽいです。ICredentials から3つのクラスが実装されてます。

ICredentials
  + AnonymouseCredentials
  + SharedKeyCredentials
  + TokenCredentils 

ServiceURLクラスが基点となる感じぽいのですが、URLとかハードコードしなくてはならないのか、どこかに設定されているのか、ユーティリティクラスがあるのか現時点では不明(ざっとソースみたけどそういうのは無いみたい)。

次に、ContainerURLを作成し、次にcreateメソッドでコンテナを作成しつつ、RxJavaのblockingGetで同期待ちです。409 が返ってきたらコンテナは存在するっぽい。

ファイルアップロード

ファイルのアップロードですが、例えばBlock BLOBならば、コンテナからBlock BLOBリファレンスを取得して、upload* メソッドを呼び出せば、ファイルからだったり、文字列、Streamなどからのアップロード可能です。非同期メソッドはありませんけど。

    var blob = container.getBlockBlobReference("test.txt");
    blob.uploadFromFile("c:/temp/test.txt");

新SDKでは以下の通り。ContainerURLから、BlockBlobURL を作成し、TransferManager経由でファイルをアップロードしてます。TransferManagerは、前述した便利な高レベルAPIクラスにあたるようです。

    BlockBlobURL blob = containerURL.createBlockBlobURL("test.txt");
    var fileChannel = AsynchronousFileChannel.open((new File("c:/temp/test.txt")).toPath());
    var res = TransferManager.uploadFileToBlockBlob(fileChannel, blob, 8 * 1024 * 1024, null).blockingGet();
    System.out.println(res.statusCode());

まとめ

使いやすいかというと、まだよくわからない感じです。旧SDKはNETと類似したクラス構造をもっていたしそんなに不便でもありませんでしたが、非同期メソッドがなかったのが致命的な感じだったのでしょうか。そこをRxJavaを使ってRewriteしたと。

でも、NETのクラス構造と乖離していくのはどうなんでしょうね?と思います。生粋のJavaerにはこっちのほうが学習コストが低いのでしょうか。NET側も追随するんでしょうか? Azure Management APIは、Javaのfluentが先行してNETも対応したように見えたので。

というわけで、今後、もう少し掘り下げたい感じです。

Easy Auth からグループのクレームを取得する

なんとなく続きます。

/.auth/me を呼び出すと自身のクレームが取得できることは前回に言及したと思いますが、この中には自身が属するグループの情報(クレーム)は含まれていません。Easy Authに限った話ではないのですが。

グループのクレームを取得する

Easy Authで設定すると、AADにアプリケーションが登録されまるので、このマニュフェストファイルを修正する必要があります。

マニュフェストを編集すると、groupMembershipClaims という項目がありますが、既定ではnullになっています。

f:id:StateMachine:20180712203113p:plain:w400

取り得る値としては、null/All/SecurityGroup なのですが、詳細は Azure Active Directory ID token reference | Microsoft Docs を参照してください。

引用すると以下の通りです。

アプリケーション マニフェストの ”groupMembershipClaims” プロパティを介して構成されます。 値が null の場合はすべてのグループが除外され、値が ”SecurityGroup” の場合は Active Directory セキュリティ グループのメンバーシップのみが含まれ、値が ”All” の場合はセキュリティ グループと Office 365 配布リストの両方が含まれます。

再度サインイン後に、/.auth/me をアクセスするとグループのクレームが取得できます。

{
typ: "groups",
val: "912a6e53-2690-4040-881e-1bfc1ccbb5b4"
},

がGUIDですので、グループ名を取得するにはGraph APIをたたく必要があります。Portalを見れば、なんのグループかはわかりますが。

f:id:StateMachine:20180712204752p:plain:w400

Easy Auth の Tokenで Graph APIを呼び出す

前回の記事 で Azure Web Apps の Easy Auth 情報をASP.NET Coreで受け取る - Azureの小ネタ (改) の続き的な記事で、Easy Authで受け取ったTokenでGraph APIをたたきたい場合です。

Easy Authで認証すると、X-MS-TOKEN-AAD-ACCESS-TOKEN にToken情報が入ってきますが、このTokenを使ってGraph APIは叩けません。

f:id:StateMachine:20180703122327p:plain

Graph APIを叩けるTokenを得るには、以下の操作をする必要があります。

準備

1) リソースエクスプローラ https://resources.azure.com/ を開く

2) 以下までTreeを展開する

3) additionalLoginParams を探して、以下のように定義し、POST(保存)します。

{
    "additionalLoginParams": ["response_type=code id_token", "resource=https://graph.windows.net"],
}

再度、アクセスすると、X-MS-TOKEN-AAD-ACCESS-TOKEN の値がJWTぽく変わっていることがわかります。

f:id:StateMachine:20180703124528p:plain

https://jtw.ms/ あたりでデコードしてみると、audience に Graph API のURLが確認できます。

f:id:StateMachine:20180703124659p:plain

Graph API を graph.microsoft.com にすることもできます。ただし、同時に複数のAudienceを指定することはできません。

アクセス例

以下は、↑と違って、graph.microsoft.com にアクセスする例です。Graph API SDKをNugetでインストールしておいてください。

        public async Task<IActionResult> GetMeAsync()
        {
            if (!this.HttpContext.Request.Headers.ContainsKey("X-MS-TOKEN-AAD-ACCESS-TOKEN"))
            {
                return Ok();
            }

            var graphServiceClient = new GraphServiceClient(new DelegateAuthenticationProvider(requestMessage => {
                requestMessage
                    .Headers
                    .Authorization = new AuthenticationHeaderValue("bearer", this.HttpContext.Request.Headers["X-MS-TOKEN-AAD-ACCESS-TOKEN"]);

                return Task.FromResult(0);
            }));

            var user = await graphServiceClient.Me.Request().GetAsync();
            return new JsonResult(user);
        }

ブラウザで直接APIをたたくと、User情報のJSONが取得できると思います。

以上