Java で Azure App Service の Azure AD 認証の JWT からユーザー情報を取得してみた

## 背景と目的

Azure App Service は Azure AD 認証が簡単に追加できるので、Web アプリ側で認証機能を実装しなくて済みます。とはいえ Web アプリ側の機能として、認証済みのユーザーを識別して動作させる必要があったりします。そんな時は、認証後のリクエストヘッダーに JWT が挿入されるので、これを利用してユーザー情報を取得します。

## 前提条件

```bash:bash
# Mac 上で検証しました
bash-3.2$ sw_vers
ProductName:            macOS
ProductVersion:         13.1
BuildVersion:           22C65

# Java の情報です
$ java -version
openjdk version "11.0.10" 2021-01-19
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.10+9)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.10+9, mixed mode)

# Maven の情報です
$ mvn -version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: /Users/testuser/.sdkman/candidates/maven/current
Java version: 11.0.10, vendor: AdoptOpenJDK, runtime: /Users/testuser/.sdkman/candidates/java/11.0.10.hs-adpt
Default locale: ja_JP, platform encoding: UTF-8
OS name: "mac os x", version: "10.16", arch: "x86_64", family: "mac"

# Azure CLI の情報です
$ az version
{
  "azure-cli": "2.43.0",
  "azure-cli-core": "2.43.0",
  "azure-cli-telemetry": "1.0.8",
  "extensions": {}
}
```

## Java on App Service を作る

```bash:bash
# 環境変数をセットします
region=japaneast
prefix=mnrjava

# リソースグループを作成します
az group create \
  --name ${prefix}-rg \
  --location $region

# App Service プランを作成します
az appservice plan create \
  --name ${prefix}-plan \
  --resource-group ${prefix}-rg \
  --is-linux \
  --sku B1

# App Service を作成します
az webapp create \
  --name ${prefix} \
  --resource-group ${prefix}-rg \
  --plan ${prefix}-plan \
  --runtime "JAVA:11-java11"

# ブラウザで App Service を開きます
open https://${prefix}.azurewebsites.net
```

## Java のサンプルアプリを動かす

```bash:bash
# GitHub から Java のサンプルアプリをダウンロードします
git clone https://github.com/Azure-Samples/java-docs-spring-hello-world

# サンプルアプリのディレクトリに移動します
cd java-docs-spring-hello-world

# Java をコンパイルして Jar にします
mvn package

# Jar をローカルで起動します
java -jar ./target/java-docs-spring-hello-world-0.0.1-SNAPSHOT.jar

# （別ターミナルで実行）ローカルで Web アプリの動作を確認します
open http://localhost:8080

# Java アプリを停止します
[Ctrl+C]

# Jar を App Service にデプロイします
az webapp deploy \
  --name ${prefix} \
  --resource-group ${prefix}-rg \
  --type jar \
  --src-path ./target/java-docs-spring-hello-world-0.0.1-SNAPSHOT.jar

# ブラウザで App Service を開きます
open https://${prefix}.azurewebsites.net
```

![appservice-aadauth-java-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675885/5aac17ac-5255-09e7-b4e0-08b7a726dfe0.png)

## App Service に Azure AD 認証を追加

```bash:bash
# Azure AD にアプリ登録を行います（アクセス許可として Microsoft Graph の User.Read を付与します）
appid=$(az ad app create \
  --display-name ${prefix} \
  --web-home-page-url https://${prefix}.azurewebsites.net \
  --web-redirect-uris https://${prefix}.azurewebsites.net/.auth/login/aad/callback \
  --enable-id-token-issuance true \
  --sign-in-audience AzureADMyOrg \
  --required-resource-accesse '[
    {
      "resourceAccess": [
        {
          "id": "e1fe6dd8-ba31-4d61-89e7-88639da4683d",
          "type": "Scope"
        }
      ],
      "resourceAppId": "00000003-0000-0000-c000-000000000000"
    }
  ]' \
  --query appId \
  --output tsv)

# App Service に Azure AD 認証機能を追加します
az webapp auth microsoft update \
  --name ${prefix} \
  --resource-group ${prefix}-rg \
  --client-id $appid \
  --issuer https://sts.windows.net/$(az account show --query tenantId --output tsv)

# App Service の認証を有効にします
az webapp auth update \
  --name ${prefix} \
  --resource-group ${prefix}-rg \
  --enabled true \
  --unauthenticated-client-action RedirectToLoginPage

# 未認証時は Azure AD へリダイレクトさせます
az webapp auth update \
  --name ${prefix} \
  --resource-group ${prefix}-rg \
  --set globalValidation.redirectToProvider=azureactivedirectory

# トークンストアを有効にします
az webapp auth update \
  --name ${prefix} \
  --resource-group ${prefix}-rg \
  --set login.tokenStore.enabled=true

# ブラウザで App Service にアクセスします（承諾画面が表示されます）
open https://${prefix}-app.azurewebsites.net
```

![appservice-aadauth-java-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675885/c08672b4-3d0b-f828-1865-8baeea49962a.png)

## Java アプリで JWT からユーザー情報を取得

```bash:bash
# DemoApplication.java を VSCode で開く
code src/main/java/com/example/demo/DemoApplication.java
```

コードを以下のように書き換えます。

```java:DemoApplication.java
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestHeader;
import java.util.Base64;

@SpringBootApplication
@RestController
public class DemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}

	@RequestMapping("/")
	String sayHello(
		@RequestHeader(name="X-MS-CLIENT-PRINCIPAL-NAME",required=false,defaultValue="test@example.com") String principalName,
		@RequestHeader(name="X-MS-TOKEN-AAD-ID-TOKEN",required=false,defaultValue="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c") String idToken
		) {
		String res = "<p>Hello Azure!</p>";
		res = res + "<p>principalName: " + principalName + "</p>";
		res = res + "<p>idToken: " + idToken + "</p>";

		String[] chunks = idToken.split("\\.");
		Base64.Decoder decoder = Base64.getUrlDecoder();
		String payload = new String(decoder.decode(chunks[1]));
		res = res + "<p>payload: " + payload.replace(",",",<br>") + "</p>";

		return res;
	}
}
```

```bash:bash
# Java をコンパイルして Jar にします
mvn package

# Jar をローカルで起動します
java -jar ./target/java-docs-spring-hello-world-0.0.1-SNAPSHOT.jar

# （別ターミナルで実行）ローカルで Web アプリの動作を確認します
open http://localhost:8080

# Java アプリを停止します
[Ctrl+C]

# Jar を App Service にデプロイします
az webapp deploy \
  --name ${prefix} \
  --resource-group ${prefix}-rg \
  --type jar \
  --src-path ./target/java-docs-spring-hello-world-0.0.1-SNAPSHOT.jar

# ブラウザで App Service を開きます
open https://${prefix}.azurewebsites.net
```

payload 部分の JSON はパースせず、見やすいようにカンマのところで改行して見えるようにしています。  
upn 以外にも name や ipaddr が情報として取得できるようです。

![appservice-aadauth-java-03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675885/9e2f4673-0d4a-bac2-dc1c-80be03b5088a.png)

## 参考

https://learn.microsoft.com/ja-jp/azure/app-service/configure-authentication-oauth-tokens

https://learn.microsoft.com/ja-jp/azure/app-service/quickstart-java?tabs=javase&pivots=platform-linux-development-environment-azure-portal