Spring Boot(MVC) + Spring Security + Azure ADでのログインの実装がとても簡単になった件

[以前も似たような記事を書いていました](https://qiita.com/talesleaves/items/701584fb9e4e0a941059)が、AzureADのSpringスターターが整理され、実装が非常に簡単になっていたので改めて記事にしました。

## 前提条件
- [Spring Bootで"Hello World!"](https://spring.pleiades.io/guides/gs/serving-web-content/)ができる（出来れば数ページのバリエーションが実装済みだと望ましい）
- AzureADの管理者アカウントを持っている
- Spring Boot+Spring WebのアプリにMicrosoft365アカンウントで認証させたい

上記のような方に向けた記事となります。

## AzureAD側での下準備
### ドメインにアプリを登録
[AzureADスターターの開発者ガイド](https://docs.microsoft.com/ja-jp/azure/developer/java/spring-framework/spring-boot-starter-for-azure-active-directory-developer-guide)の「[Azure Active Directory インスタンスの作成](https://docs.microsoft.com/ja-jp/azure/developer/java/spring-framework/configure-spring-boot-starter-java-app-with-azure-active-directory#create-azure-active-directory-instance)」を参考にテナントの作成を行います。
また、認証には当然ユーザー情報が必要となりますので、ユーザーの作成が必要です。グループ別のアクセス認可を行う場合には、グループの作成も必要です。
既にテナントやユーザー、グループの設定が終わっている場合は、「[Spring Boot アプリのアプリケーション登録を追加する](https://docs.microsoft.com/ja-jp/azure/developer/java/spring-framework/configure-spring-boot-starter-java-app-with-azure-active-directory#add-an-application-registration-for-your-spring-boot-app)」だけ済ませておけば大丈夫です。

### アプリケーション登録の注意点
**クライアント シークレットの値は登録時にしか表示されない**ことと、 API のアクセス許可で取得するアクセスの内容に注意しましょう。
以前の記事ではAPIにAzureAD Graphが使用されていましたが、バージョン3.6以降ではデフォルトで[Microsoft Graph](https://docs.microsoft.com/ja-jp/graph/overview)を使用してアクセスするため、APIの許可もMicrosoft Graphから行うことになります。

### アクセス許可の内容（必須）
- Directory.AccessAsUser.All：サインインしているユーザーとしてディレクトリにアクセスする 
- User.Read：サインインの実行とそのユーザーのプロファイルを参照する


上記があれば最低限のログイン処理が可能です。

### リダイレクトURIについて
AzureADスターターを使用してログインを実装する場合、リダイレクトURIは`http|https//{ドメイン}/login/oauth2/code/`で固定されます。Spring SecurityのOAuth2（OpenID Connect）のテンプレートで生成されるURIとは異なりますので注意してください。

#### 追記（250115）
[こちら](https://learn.microsoft.com/ja-jp/azure/developer/java/spring-framework/spring-security-support?tabs=SpringCloudAzure5x#configure-redirect-uri-template)にてURIを変更するサンプルが公開されていました。
いつの間に…

## 利用するStarterと依存関係の記述
### 利用するStarter（必須）
必須のスターターは下記となります。[Spring Initializr](https://start.spring.io/)に表示されるスターター名で記述してあります。

- Spring Web
- OAuth2 Client
- Azure Active Directory

### Gradleで依存性を記述する場合
[Spring Initializr](https://start.spring.io/)を利用するのが確実ですが、執筆時点でAzureADスターターのバージョンが上がっているため、バージョンの指定を下記のように修正することをお勧めします。

```gradle:build.gradle
// 前略
ext {
  set('azureVersion', "3.6+")
}

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
  implementation 'org.springframework.boot:spring-boot-starter-web'
  implementation 'com.azure.spring:azure-spring-boot-starter-active-directory'
}

dependencyManagement {
  imports {
    mavenBom "com.azure.spring:azure-spring-boot-bom:${azureVersion}"
  }
}
// 後略
```

## application.ymlの編集

```yaml:application.yml
azure:
  activedirectory:
    authorization-clients:
      graph:
        scopes:
          - https://graph.microsoft.com/User.Read
          - https://graph.microsoft.com/Directory.AccessAsUser.All
    tenant-id: AzureADのテナントのID
    client-id: AzureADに登録したアプリのID
    client-secret: AzureADに登録したクライアントシークレット
    user-group:
      allowed-group-names:
       - グループ名1
       - グループ名2
```
上記のように整理されて非常にシンプルになりました。

[AzureADスターターの開発者ガイド](https://docs.microsoft.com/ja-jp/azure/developer/java/spring-framework/spring-boot-starter-for-azure-active-directory-developer-guide)の「[構成プロパティ](https://docs.microsoft.com/ja-jp/azure/developer/java/spring-framework/spring-boot-starter-for-azure-active-directory-developer-guide#configurable-properties)」のプロパティには`allowed-groups`という表記がありますが、アクセスを許可するグループをグループ名で指定するときは`allowed-group-names`が推奨されます。
また、グループ名は変更される場合があるため、より安全なのは`allowed-group-ids`を使用して一意に指定することです。

## Configurerクラスを作成する
デフォルトでは、[DefaultAADWebSecurityConfigurerAdapter](https://azuresdkdocs.blob.core.windows.net/$web/java/azure-spring-boot/3.6.1/com/azure/spring/aad/webapp/AADWebAppConfiguration.DefaultAADWebSecurityConfigurerAdapter.html)により、/login以外のパスが自動的に保護されるようになっています。/loginを除外しているのはログイン画面がないとどこにもアクセスできなくなってしまうからですね。

パスの一部に認証をし、また、グループによってアクセスできるパスを変更するなどの構成が必要な場合、[AADWebSecurityConfigurerAdapter](https://azuresdkdocs.blob.core.windows.net/$web/java/azure-spring-boot/3.6.1/index.html)を継承してクラスを作成します。
[AzureADスターターのGitHub](https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/spring/azure-spring-boot-starter-active-directory#azure-ad-spring-boot-starter-client-library-for-java)によると、下記のような構成クラスではすべてのパスにAzureADの認証が行われます。

```java:GitHubより
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class AADOAuth2LoginSecurityConfig extends AADWebSecurityConfigurerAdapter {

    /**
     * Add configuration logic as needed.
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.authorizeRequests()
                .anyRequest().authenticated();
        // Do some custom configuration
    }
}
```
既にあるアプリケーションにAzureADによる認証を組み込んでみるだけのテストであれば、上記のクラスをそのまま使用しても良いでしょう。

実用的には明示的に`super.configure(http);`を呼ぶことでAzureADによる認証に関する諸々の設定がおこなわれ、`http.authorizeRequests()`以下で、認証の必要なパスやグループによるアクセス許可を指定することができます。

## Spring Security導入時のCSRF対策について
Spring Securityが導入されることでCSRF対策が導入されます。これにより、POST、DELETEなどのメソッドのリクエストに対してトークンのチェックが行われます。

テンプレートエンジンにThymeleafを使用している場合は、依存関係にthymeleaf-extras-springsecurity5を追加することでth:method="post"などのCSRF対策が必要なフォームタグに対し、自動的にトークンの隠しフィールドを追加してくれます。
また、Ajaxに対しても同様にトークンのチェックが行われるため、動作しなくなる場合があります。
Spring Securityドキュメントの[Ajax および JSON リクエスト](https://spring.pleiades.io/spring-security/site/docs/current/reference/html5/#servlet-csrf-include-ajax)に公式な対応方法がありますので、これもテンプレートで実装しておくと良いでしょう。
また、このあたりの情報は@opengl-8080さんの「[Spring Security 使い方メモ　CSRF](https://qiita.com/opengl-8080/items/3720ed415c2df26251a7)」が大変参考になります。この記事のシリーズはSpring Securityを使用するうえで大変参考になるので他の記事も目を通すことをお勧めします。


## 参考資料
[azure-sdk-for-java](https://github.com/Azure/azure-sdk-for-java/tree/main/sdk/spring)：MicrosoftのAzure関連のGitHub
[Spring Security 使い方メモ　CSRF](https://qiita.com/opengl-8080/items/3720ed415c2df26251a7)
[Spring Security リファレンス ](https://spring.pleiades.io/spring-security/site/docs/current/reference/html5/)