Azure App Serviceでazure-spring-boot-starter-active-directoryを使ったらログイン失敗する

# 困ったこと

[「チュートリアル:Azure Active Directory 用の Spring Boot Starter を使用して Java Web アプリをセキュリティで保護する」](https://docs.microsoft.com/ja-jp/azure/developer/java/spring-framework/configure-spring-boot-starter-java-app-with-azure-active-directory)を見てその通りにしてみるのだけど、いざAzure App Service (Web Apps) にデプロイして動かしてみると、こんな感じで「The reply URL specified in the request does not match the reply URLs configured for the application」というエラーになる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2079/a54216bf-6060-d7c8-d0f4-5602cc780328.png)

メッセージにある通り、これは「reply URL」の問題である。そしてそれは、Azure ADの「アプリ登録」→「認証」→「プラットフォーム構成]→「Web」にある、「リダイレクト URI」のことだ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2079/9426f380-32ce-ca97-f670-e563ecc70185.png)

チュートリアルには `http://localhost:8080/login/oauth2/code/` を設定せよとあるが、もちろんこれはローカルマシンでデバッグ実行するときの設定であり、App Serviceに配置する場合は適切なURIを設定しないといけない。幸い、ここには複数のURIを登録できる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2079/a920e85a-6543-ef9c-6e3e-8987ba0d73c4.png)

こんな感じで、`http://localhost:8080` の部分を `https://your-azure-web-app-name.azurewebsites.net` に置き換えたURIを追加しておけばよさそうだ。

それなのに、まだ「The reply URL specified in the request does not match the reply URLs configured for the application」というエラーが解消されない。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2079/a54216bf-6060-d7c8-d0f4-5602cc780328.png)

何が原因で、どうすればいいのか？

とりあえず、ブラウザー開発者ツールで通信をキャプチャしてみると……

最初のリダイレクト。httpsにアクセスしたはずがhttpにリダイレクトされる。これはひとまず無視する[^1]。

[^1]: 無視しないほうが解決の早道だったことが後でわかる。

```
GET / HTTP/1.1
Host: your-azure-web-app-name.azurewebsites.net

HTTP/1.1 302
Location: http://your-azure-web-app-name.azurewebsites.net/oauth2/authorization/azure
```

次のリダイレクトでAzure ADのログイン画面に飛ぶのだが、その時に渡しているURLパラメーターが……（見やすさのために空白と改行を追加）

```
GET /oauth2/authorization/azure HTTP/1.1
Host: your-azure-web-app-name.azurewebsites.net

HTTP/1.1 302
Location: https://login.microsoftonline.com/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/oauth2/v2.0/authorize
  ?response_type=code
  &client_id=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
  &scope=openid%20profile%20https://graph.microsoft.com/User.Read%20https://graph.microsoft.com/Directory.Read.All
  &state=XXXXXXXXX-XXXXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXX
  &redirect_uri=http://your-azure-web-app-name.azurewebsites.net/login/oauth2/code/
  &nonce=XX-XXXXXXXXXXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXX
```

`redirect_uri` が `http://` になってる。

「それなら、Azure ADのリダイレクトURIを`http://`にしたらいい」と思ったら残念。`http://`で登録できるのは`localhost`のみという仕様である。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2079/a5ec29fb-432a-5437-f744-56e9b1344e31.png)

# 解決方法 (おすすめ)

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">X-Forwarded-Proto をどっかが見ていないのが原因なのだろうな <a href="https://t.co/VVDTtMU4Jm">https://t.co/VVDTtMU4Jm</a></p>&mdash; Tatsuro Shibamura (@shibayan) <a href="https://twitter.com/shibayan/status/1426172070593925132?ref_src=twsrc%5Etfw">August 13, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

あらためて調べたら……こんなのがあるじゃないですか。

[Spring Boot 「使い方」ガイド - リファレンスドキュメント](https://spring.pleiades.io/spring-boot/docs/current/reference/html/howto.html#howto.webserver.use-behind-a-proxy-server)

> ## 3.12. フロントエンドプロキシサーバーの背後で実行する
> プロキシが一般的に使用される `X-Forwarded-For` および `X-Forwarded-Proto` ヘッダーを追加する場合、`server.forward-headers-strategy` を `NATIVE` に設定するだけでサポートできます。このオプションを使用すると、Web サーバー自体がこの機能をネイティブでサポートします。特定のドキュメントを確認して、特定の動作について学ぶことができます。

早速やってみたらら、これで一発解決。

`application.yml` なら

```yml
server:
  forward-headers-strategy: NATIVE
```

`application.properties` なら

```properties
server.forward-headers-strategy=NATIVE
```

環境変数で指定するなら（App Serviceなら「構成」→「アプリケーション設定」）

```sh
SERVER_FORWARD_HEADERS_STRATEGY=NATIVE
```

設定結果はこちら。最初のリダイレクトの方もきちんとHTTPSを保つようになった。

```
GET / HTTP/1.1
Host: your-azure-web-app-name.azurewebsites.net

HTTP/1.1 302
Location: https://your-azure-web-app-name.azurewebsites.net/oauth2/authorization/azure
```

```
GET /oauth2/authorization/azure HTTP/1.1
Host: your-azure-web-app-name.azurewebsites.net

HTTP/1.1 302
Location: https://login.microsoftonline.com/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/oauth2/v2.0/authorize
  ?response_type=code
  &client_id=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
  &scope=openid%20profile%20https://graph.microsoft.com/User.Read%20https://graph.microsoft.com/Directory.Read.All
  &state=XXXXXXXXX-XXXXXXXXXX_XXXXXXXXXXXXXXXXXXXXXXX
  &redirect_uri=https://your-azure-web-app-name.azurewebsites.net/login/oauth2/code/
  &nonce=XX-XXXXXXXXXXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXX
```

ちなみに、上記のドキュメントには

> __ヒント__
> Tomcat を使用していて、プロキシで SSL を終了している場合は、`server.tomcat.redirect-context-root` を `false` に設定する必要があります。これにより、リダイレクトが実行される前に `X-Forwarded-Proto` ヘッダーを受け入れることができます。

ともありますが、Spring BootのデフォルトであるEmbedded Tomcatではこの設定は不要だった。

# おまけ：別の方法（最初に調べたのはこちらですが、おすすめしません）

`redirect-uri-template` の設定を追加して、そこに `https://your-azure-web-app-name.azurewebsites.net/login/oauth2/code/` と書いてしまえばいい。

ドキュメントはこちら。
[Azure AD Spring Boot Starter client library for Java | Microsoft Docs](https://docs.microsoft.com/ja-jp/java/api/overview/azure/spring-boot-starter-active-directory-readme?view=azure-java-stable#support-setting-redirect-uri-template)

`application.yml` なら

```yml
azure:
  activedirectory:
    redirect-uri-template: https://your-azure-web-app-name.azurewebsites.net/login/oauth2/code/
```


`application.properties` なら

```properties
azure.activedirectory.redirect-uri-template=https://your-azure-web-app-name.azurewebsites.net/login/oauth2/code/
```

だけど、たぶん環境変数で指定するほうがいいだろう。（App Serviceなら「構成」→「アプリケーション設定」）

```sh
AZURE_ACTIVEDIRECTORY_REDIRECT_URI_TEMPLATE=https://your-azure-web-app-name.azurewebsites.net/login/oauth2/code/
```