Azureの小ネタ (改)

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

Azure Cosmos DB Spring Boot Starter を使う

Azure Spring Boot Starter ライブラリの一つに、Cosmos DBへアクセスするライブラリが存在するのですが、今回はこれについて。

Docsの解説記事としては以下の通りです。

docs.microsoft.com

初期設定

Web Application でなくてもいいので、普通にCommandLineRunnerを使って簡易に実装しつつ、挙動を確かめるのがお手軽かと思います。

ライブラリは以下の者を使います。

mvnrepository.com

以下のような依存をつければよいでしょう(執筆時点)

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.microsoft.azure</groupId>
            <artifactId>azure-cosmosdb-spring-boot-starter</artifactId>
            <version>2.3.5</version>
        </dependency>

CosmosDBの用意

現在、AzureサブスクリプションにはFreeでCosmosDBを1つだけ作れるのでこれを利用すると良いと思います。

azure.microsoft.com

ここで適宜DBとコンテナを作成すると以下の情報が手に入るとおもいます。

  • URL
  • キー
  • DB名
  • コンテナ名

サンプルなので、パーティションキーは /id にしてもらっても問題ないでしょう。

Azure Cosmos DB でのパーティション分割 | Microsoft Docs

構成ファイル

構成は、application.propertiesに設定しますが、yamlでも構いません。先ほど取得した情報のうちURI、キー、DB名を定義します。

azure.cosmosdb.uri=
azure.cosmosdb.key=
azure.cosmosdb.database=

POJOクラスの定義

POJOクラスを定義します。めんどくさいGetter/Setter的なコードはlombokにお任せします。注目すべきは、クラスに指定したDocumentアノテーションです。これにコレクション(コンテナ)を指定します。 あと、PartitionKeyアノテーションはDBを作成したときのパーティションキーに指定します。

package com.example.demo;

import com.microsoft.azure.spring.data.cosmosdb.core.mapping.Document;
import com.microsoft.azure.spring.data.cosmosdb.core.mapping.PartitionKey;

import org.springframework.data.annotation.Id;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
@Document(collection = "Container1")
public class Pet {
    
    @Id
    @PartitionKey
    private String id;
    private String owner ;
    private String name;
    private PetKind kind;
}

リポジトリの定義

リポジトリは同期、非同期に二つが用意されています。該当パッケージのJavadocは以下の通りです。

docs.microsoft.com

同期で利用したいならCosmosRepository<T,ID> 、非同期で利用したいならReactiveCosmosRepository<T,K> を使います。

リポジトリはインタフェースを継承して中身は空、実装クラスも用意しなくて問題ないです。面倒くさいところはライブラリがやってくれます。

@Repository
public interface PetRepository extends CosmosRepository<Pet, String> {
}

CRUD

CRUDはリポジトリ経由で操作できます。DIされるので、それを利用します(実装いらず)。以下は新規作成例です。

    @Autowired
    private PetRepository repository;

    @Override
    public void run(String... args) throws Exception {

        // Crete
        var pet1 = repository.save(new Pet(UUID.randomUUID().toString(), "suzuki-san", "pochi", PetKind.Doc));
        var pet2 = repository.save(new Pet(UUID.randomUUID().toString(), "tanaka-san", "tama", PetKind.Cat));

        System.out.println(pet1);
        System.out.println(pet2);
        
    }

実行後、ポータルを参照するとItemが作られていることが確認できます。

提供されている操作自身は、Spring Bootのリポジトリインタフェースを継承しているので、それらが利用出来ると思います。

f:id:StateMachine:20200930115850p:plain

以上

Azure SDK for Java におけるログなど

Azure SDK for JavaのことがBlogで記事になっていたのでザックリ内容を確認しました。

devblogs.microsoft.com

原題は「in Azure Functions」とはなっていますが、Functionsに限らないSDK Loggingの話だと捉えた方がよさそうです。気をつけなくてはいけないのは、JavaのFunctionsでは、ロガーインスタンスはDIされないので、Contextオブジェクトから取得することになるくらいですちなみに、context.getLogger().info("hoge") みたいになります。

話をSDKに戻しますと、SDKのログを取得できるのは内部動作の理解にも繋がりますし、デバッグ時にも大変有効な手段です。依然は、SDK毎にバラバラだった気がしますが、最近のAzure SDKはガイドラインに沿って作られているせいか、機能毎にそれほどばらつき無く実装されているような気がします。

Key Vaultの場合

大体のJava SDKはクライアントをBuilderパターンで構築しますが、そこにhttpLogOptionsを挟み込むだけでログが取得できます。無論、slf4jを介したログ出力になっているので、このあたりの設定は必要ですが、主要なフレームワーク(Spring Bootとかは)には組み込まれていると思うので、そのまま出力されるでしょう。

        var kvCreds = new ClientSecretCredentialBuilder()
                .tenantId("")
                .clientId("")
                .clientSecret("")
                .build();

        var logOptions = new HttpLogOptions().setLogLevel(HttpLogDetailLevel.BODY_AND_HEADERS);

        var secretClient = new SecretClientBuilder()
                .httpLogOptions(logOptions)
                .vaultUrl(kvUri)
                .credential(kvCreds)
                .buildClient();

Azure上にデプロイして動かないなど、Managed Idの設定ではまったりするので、ログが取得できるのは、どこに設定ミスがあるかを確認したりするうえで便利だと思います。

ログの種類

https://github.com/Azure/azure-sdk-for-java/blob/master/sdk/core/azure-core/src/main/java/com/azure/core/http/policy/HttpLogDetailLevel.java

のとおり、NONE/BASIC/HEADERS/BODY/BODY_AND_HEADERSの5種類です。

  • BASICは、URLとHTTPメソッドとリクエスト
  • HEADERSは、BASICに加えてリクエストとレスポンスヘッダ
  • BODYは、BASICにくわえリクエストとレスポンスボディ
  • BODY_AND_HEADERSは、全部という感じです。

BASICの例は以下のような感じになります。

2020-09-29 11:08:59.585  INFO 60628 --- [           main] c.a.s.k.secrets.SecretAsyncClient        : Retrieving secret - ConnectionString
2020-09-29 11:08:59.597  INFO 60628 --- [           main] c.a.s.k.secrets.SecretService.getSecret  : --> GET https://xxxxyyyyzzz.vault.azure.net/secrets/ConnectionString/?api-version=REDACTED
Try count: 1

2020-09-29 11:09:01.201  INFO 60628 --- [ctor-http-nio-1] c.a.s.k.secrets.SecretService.getSecret  : <-- 401 https://xxxxyyyyzzz.vault.azure.net/secrets/ConnectionString/?api-version=REDACTED (1603 ms, 87-byte body)
<-- END HTTP
2020-09-29 11:09:01.764  INFO 60628 --- [nPool-worker-19] c.azure.identity.ClientSecretCredential  : Azure Identity => getToken() result for scopes [https://vault.azure.net/.default]: SUCCESS
2020-09-29 11:09:01.764  INFO 60628 --- [nPool-worker-19] c.a.s.k.secrets.SecretService.getSecret  : --> GET https://xxxxyyyyzzz.vault.azure.net/secrets/ConnectionString/?api-version=REDACTED
Try count: 1

2020-09-29 11:09:02.141  INFO 60628 --- [ctor-http-nio-3] c.a.s.k.secrets.SecretService.getSecret  : <-- 200 https://xxxxyyyyzzz.vault.azure.net/secrets/ConnectionString/?api-version=REDACTED (377 ms, 302-byte body)
<-- END HTTP
2020-09-29 11:09:02.175  INFO 60628 --- [ctor-http-nio-3] c.a.s.k.secrets.SecretAsyncClient        : Retrieved secret - connectionString

Storageの場合

Storage SDKの場合も同じく、httpLogOptionsメソッドを挟み込めばOKです。

        var storageClient = new BlobServiceClientBuilder()
                .httpLogOptions(logOptions)
                .endpoint(endpoint)
                .credential(storageCreds)
                .buildClient();

他のSDK

これ以外にCosmos SDKを試してみましたが、BuilderクラスにhttpLogOptionsメソッドは実装されていませんでした。まだ全てのSDKで使えるわけではなさそうです。(CosmosにはCosmos SDKのログの方法が提供されているとは思いますが)

というわけで、今回も誰得なブログでした。以上。

Spring Boot と Azure AD B2Cの連携

前回はAzure Active Directoryで認証を行いましたが、今回はAzure Active Directory B2C(以下B2C)でやっていきたいと思います。

B2Cそのものの説明はしないので、Azure Active Directory B2C のドキュメント | Microsoft Docs あたりを参考にしてください。

連携方法

連携方法としては、Spring Boot の標準ライブラリのみで行うか、Microsoftが提供しているB2C向けStarterを使うかのいずれかになります。

標準ライブラリを使った方法

構成は前回と同じです。同一プロジェクトに設定を同居できますので、プロジェクトを流用すればいいでしょう。次にプロパティファイルに以下を追加します。 azureadb2cという名前でプロバイダを登録します。この名前は任意なので、変更しても構いません。

spring:
  security:
    oauth2:
      client:
        registration:
          azureadb2c:
            client-id: <<b2c client id>>
            client-secret : <<b2c client secret>>
            client-name: Azure Active Directory B2C
            scope: 
            - openid
            - <<b2c client id>>
            client-authentication-method: post
            authorization-grant-type: authorization_code
            redirect-uri: http://localhost:8080/login/oauth2/code/azureadb2c
        provider:
          azureadb2c:
            token-uri: https://<<b2c tenant name>>.b2clogin.com/morisb2c.onmicrosoft.com/<<user flow name>>/oauth2/v2.0/token
            authorization-uri: https://<<b2c tenant name>>.b2clogin.com/morisb2c.onmicrosoft.com/<<user flow name>>/oauth2/v2.0/authorize
            user-name-attribute: name
            jwk-set-uri: https://<<b2c tenant name>>.b2clogin.com/morisb2c.onmicrosoft.com/<<user flow name>>/discovery/v2.0/keys

注意点としては、以下。

  • Scopeには openid の他にクライアントIDを指定しないと認証に失敗します。ここで少しはまりましたが、B2CのDocsにもどこかにこっそり書いてあるはずです。
  • URIの一部にサインイン用のユーザーフロー名が入るので、B2C側で作成したフロー名を指定してください。

この例では、パスワードリセットとユーザープロファイル編集は無視しています。 設定をするとログイン画面にB2C向けのリンクが表示されるはずなので、あとは普通に認証後にログイン出来ると思います。

B2C Starterを使った方法

以下の依存を追加するとB2C Starterを利用できます。

        <dependency>
            <groupId>com.microsoft.azure</groupId>
            <artifactId>azure-active-directory-b2c-spring-boot-starter</artifactId>
            <version>2.2.5</version>
        </dependency>

この場合は以下の通りプロパティファイルには独自の設定をする必要があります。

azure:
  activedirectory:
    b2c:
      tenant: <<your tenant name>>
      oidc-enabled: true
      client-id: <<client id>>
      client-secret: <<client secret>>
      reply-url: http://localhost:8080/home
      logout-success-url: http://localhost:8080/logout
      user-flows:
        sign-up-or-sign-in: <<sign in flow name>>
        profile-edit: <<profile edit flow name>>
        password-reset: <<password reset flow name>.

こちらのStarterを使うとサインイン、ユーザープロファイル編集、パスワードリセットの3つのフローを設定するだけで扱えるようになります。URIも内部で処理してくれるので面倒くさくはありません。 あと、reply-urlhttp://localhost:8080/にするとうまく動作しなくなるので、 homeで受けて同じページを返すような処理が必要です(いまいちこのあたりの処理に精通していないので失敗原因がわかりません)

実行すると、例のログイン画面に3つでてくるでしょう。実際は、この画面を使うこともないでしょうし、うまく隠蔽してください。

f:id:StateMachine:20200806103142p:plain

まとめ

すべては後半のStarerを使ったやつはDocsにチュートリアルがありますので、そちらを参考にしてくれたほうがはかどると思います。とはいえ、内容が微妙に間違ってたり古くなってたり誤訳だったり、説明不足だったりすることが多いので、実際動かすのは手間がかかる場合もありあmす。

docs.microsoft.com

以上