Keycloakにおける多要素認証


:::note info
本記事は、Keycloakのバージョンアップに伴い、
[「Keycloakで多要素認証を試してみる（ワンタイムパスワード認証編）」](https://qiita.com/naokiiiii/items/d30b0d671ab04a56490e)
の記事を最新化・拡充したものです。
:::


# 概要

今回はKeycloakの多要素認証について触れてみます。
「多要素認証」とは、アクセス権を得るのに必要な本人確認のための『複数』の要素（証拠）をユーザーに要求する認証方式です。例えば、複数の要素を組み合わせることで、よりセキュアな認証を可能にしたり、利便性を下げずに、セキュリティを向上させたりできます。

Keycloakが提供する「多要素認証」の設定を確認しながら、「ワンタイムパスワード認証(OTP)」を行います。今回はモバイル端末の認証アプリ(Authenticator)にOTPを送る方式とメールアドレスにOTPを送る方式の2通りを試してみます。

# 認証アプリ(Authenticator)編
管理者として管理コンソールにログインする際にワンタイムパスワード(OTP）を設定し、多要素認証を行なっていきます。

## 事前準備

- Keycloak環境（スタンドアロン構成でOK）
- Keycloak管理ユーザの作成
- 「FreeOTP」がインストールされた「iPhone」端末（「Google Authenticator」や「Microsoft Authenticator」でも可能です。）
※セットアップやユーザ登録等は、「[Keycloak21.1.1のセットアップ](https://qiita.com/l_katayose/items/cbaff1395924cf13218c)」をご参考にして下さい。
※今回はデフォルトでセットアップされている「masterレルム」をそのまま使います。(実際はレルムを分けて作るのが望ましいです)

## Keycloakの設定

Keycloak管理コンソール `http://(KEYCLOAK_FQDN)/auth/admin/master/console/` にアクセス、および、管理ユーザでログインし、[設定(configure)] > [認証(Authentication)]の内容を確認します。

### 認証フロー設定

「認証フロー」は、各種処理パターン毎（ブラウザ認証フロー/登録フロー/ダイレクトグラントフロー/クレデンシャルリセットフロー/クライアント認証フロー）に分けられてビルトイン設定されており、その中のBrowserが、ブラウザで認証する基本パターンの「認証フロー」となっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/42d9d3a0-7ffd-7f91-5f39-74fee9cefc22.png)

上記キャプチャの「認証フロー」の設定は、Keycloakのデフォルトですが、ここに設定されている「認証タイプ」の概要は以下の通りとなり、上から順番に評価されていきます。なお、このフローをコピー編集して、順序を入れ替えたり、他の認証タイプを設定したり、フローをグループ化して何段にもネスト設定したりすることが可能です。

| 認証タイプ | (ネスト1) | (ネスト2) | 認証内容 | 概要 |
|:-|:-|:-|:-|:-|
| Cookie | - | - | 認証クッキー認証 | 認証クッキー内容が有効であることをチェックします。 |
| Kerberos | - | - |  Kerberos認証 | Kerberos Distribution Center（KDC）で認証されたユーザかどうかチェックします。 |
| Identity Provider Redirector | - | - | 外部IDP認証 | 外部のIDPやSNSなど第３者認証情報を利用して、SAMLやOpenID Connectによる認証を行います。 |
| Forms | - | - | フォーム認証 | フォーム入力による認証が使用されます。内容はネストされた認証タイプに依存します。|
| 〃 | Username Password Form | - | IDパスワードで行うフォーム認証 | フォーム入力されたIDとパスワードをチェックします。|
| 〃 | Browser - Conditional OTP | - | （右の概要についての確認） | ユーザが設定されたOTPクレデンシャルを持っているかチェックします。　|
| 〃 | 〃 |  Condition -user configured | （右の概要についての確認） | Keycloakがユーザに対してフロー内の他のエグゼキューションを設定しているかチェックします。|
| 〃 | 〃 | OTP Form | ワンタイムパスワード認証 | フォーム入力されたワンタイムパスワードをチェックします。 |

また、フロー内の各「認証タイプ」には、実行後の動作を決定する「必要条件」が設定可能です。その条件には以下の4種類があります。

| 必要条件 | 意味 | 概要 | 実行後の結果 |
|:-|:-:|:-|:-|
| Alternative | 代替  | 選択された「認証タイプ」が次以降の「認証タイプ」の代替をします。 | 成功すると、次の「認証タイプ」に進むことなく、認証成功となります。失敗すると、次の「認証タイプ」に進みます。 |
| Required | 必須 | 選択された「認証タイプ」の成功が必要になります。 | 成功すると、次の「認証タイプ」に進みます。失敗すると、次の「認証タイプ」に進むことなく、認証失敗となります。 |
| Conditional | 条件付き | 選択された「認証タイプ」はユーザ設定とその条件の可否により実行されます。| このタイプはサブフローがある場合のみ設定され、Conditionalのサブフローの評価の結果によって、RequiredかDisabledとして動作するかが決まります。（※）  |
| Disabled | 無効 | 選択された「認証タイプ」が無効になります。 | 実行されません。 |

※この決定は論理文の評価によって決まりますが、詳細は「[Keycloak Documentation - Server Administration (Built-in flows)](https://www.keycloak.org/docs/21.1.1/server_admin/#built-in-flows)」をご参照下さい。

上述のキャプチャの「認証フロー」設定を図解すると以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/86aa10db-aa0b-4659-d68e-332ac9a12a7e.png)

OTP FormについてRequiredとなっていますが、 conditional サブフローで設定されているため、ユーザーがOTPクレデンシャルをセットアップしている場合にのみ実行されます。そうでない場合、ユーザーにはOTPフォームが表示されません。


### OTPポリシー設定

「OTP」の方式やハッシュアルゴリズムなどが変更できます。ここでは、一旦デフォルトのままとして、次に進みます。
設定値の詳細については公式ドキュメントの「[Keycloak Documentation - Server Administration (One Time Password (OTP) policies)](https://www.keycloak.org/docs/21.1.1/server_admin/#one-time-password-otp-policies)」をご参考ください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/709b8ab5-d8cf-d611-3b2e-631f29367e58.png)

## ユーザ設定

アカウント設定画面 `http://(KEYCLOAK_FQDN)/auth/realms/master/account/` にアクセス、および、管理ユーザでログインし、[Account security] > [Signing in] > [Two-factor authentication]でOTP設定を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/e468f425-33ca-b633-cd89-864d58972923.png)
以下のようにMobile Authenticatorのセットアップ画面が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/28d9bf1f-c135-bb7a-faab-b28a37efd688.png)

「iPhone」端末を取り出し、「FreeOTP」を起動します。
<img width="300" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/4c1aceed-2835-f396-c545-a872bec39371.png>

ユーザ設定画面上に表示されている、２次元バーコードを「FreeOTP」で読み取り、トークン登録します。
<img width="300" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/f4e83ce7-4a99-e302-26fe-51fa92348374.png>

そのまま、登録されたトークンをタップすると「ワンタイムパスワード」が表示されます。
<img width="300" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/a92ec15b-524b-65d3-0a66-611f4ba95145.png>

その「ワンタイムパスワード」をアカウント設定画面のワンタイムコードに入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/6feb7021-717c-525d-8ac2-a76897d1a8c6.png)

これで「OTP」の設定は完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/f86cb258-c069-87e8-a6b8-069392f264ad.png)

## 動作確認

一度ログアウトをしてから、ユーザ設定画面 `http://(KEYCLOAK_FQDN)/auth/realms/master/account/`にアクセスします。
「ログイン画面」が表示されました。「ID」と「パスワード」を入力し、ログインします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/20e0c528-4a35-ac84-e762-13b4e34c062d.png)

続けて、「ワンタイムコード入力画面」が表示されるようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/f5d1aeef-3e3c-fb08-20d7-c81e08b71b22.png)

「iPhone」端末を取り出し、「FreeOTP」を起動し、ワンタイムパスワードを発行します。
<img width="300" src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/5bc2b14a-0d9e-7b48-a615-ec98a35fa5f0.png>

発行された「ワンタイムパスワード」を、「ワンタイムコード入力画面」のワンタイムコードに入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/a93fef14-d0b8-d64e-5731-d1b27736ce46.png)

無事ログインすることができました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/c6950983-0329-d9f9-e367-5bce9f024057.png)

# メールアドレス認証編
ここまでは管理者のログインに関して多要素認証を行ってきましたが、こちらの章ではユースケースとして多く使われる、ユーザのログイン時におけるメールアドレスにOTPを送付し、多要素認証を行う方法について紹介します。


## 事前準備
- Keycloak環境（スタンドアロン構成でOK）
- masterレルムとは別のレルムを作成
- 管理ユーザと一般ユーザの作成
- メールサーバ構築（今回はローカル開発用メールサーバとしてmaildevを使用しています。）


## サンプルアプリの起動
以下の記事を参考にJavascriptのクライアントアダプタを導入したサンプルアプリを構築します。

https://qiita.com/s-takino/items/193a776eb69aedc5ca06

内容としては認証不要ページと要認証ページが用意されたシンプルなアプリケーションです。
- 認証不要な画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/78eab6cc-6fb5-860e-935b-759ab3b92899.png)
- 要認証な画面（以下は認証後表示される画面）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/774b974f-9e4e-114c-046f-16801bfc4834.png)
今回はこちらのサンプルアプリを検証環境として使用します。

## 開発用メールサーバの起動
今回の検証では開発メール用サーバとして、maildevを使用しています。起動方法やmaildevが行ってくれることについては以下を参考にさせていただきました。

https://qiita.com/sotarok/items/ff5ca29f88d1f8069c98

行うことは難しくなく、以下コマンドでインストールと起動を行うだけです。
```
$ npm install -g maildev
$ maildev
```
SMTPサーバは 1025番ポートで、Web Viewerは1080番ポートで起動されます。
以下のように`localhost:1080`にアクセスすることで1025番ポートに来たメールを表示してくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/8cd5c0b8-3345-cb7e-1385-c6abd28292f0.png)

## Keycloakの設定
サンプルアプリの記事と同様に以下の設定を行います。
- レルムの作成
- サンプルアプリのクライアント設定
- ユーザの作成

その上で、ユーザログイン時にメールが飛ぶようにRealm settingsからEmailの設定と、認証フローの設定を行っていきます。

### 認証SPIを使ったカスタマイズ
KeycloakはSPI(Service Provider Interface)によりKeycloak自体のソースを変更せずに、拡張できる仕様となっています。
メールアドレスを使用したメールアドレス認証は本来、「[Keycloak Documentation - Server Development (Authentication SPI)](https://www.keycloak.org/docs/21.1.1/server_development/#_auth_spi)」を参考にカスタマイズ開発を行う必要がありますが、今回は検証のため、メールアドレスでのOTP認証を行えるように実装された、以下のライブラリを使用しました。

https://github.com/mesutpiskin/keycloak-2fa-email-authenticator

上記から取得したソースを`mvn package`コマンドによってjarファイルにしたうえで、`README.md`にある必要な設定を行います。
上記コマンドにより作成された`keycloak-2fa-email-authenticator.jar`をkeycloak/providers/に配置することで、認証フローに対して、メールアドレスにOTPを送信する`Email OTP`を組み込むことが可能になります。

### 認証フローの設定
組み込みフローを変更することはできないため、作成した認証システムを追加するには、既存のフローをコピーするか、独自のフローを作成する必要があります。
今回は、`Email OTP`を組み込むためAuthenticationから新しくフローを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/72585fa4-3ed3-d83c-0876-375a64c138c1.png)
認証アプリ(Authenticator)編で使用した`browser`と同様のフローを作成し、`OTP Form`の部分を`Email OTP`に変えて、動作を確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/f13c6c8f-ea81-ef21-980f-8ca84389c0b8.png)
また、上記画面右上の`Action`タブから`Bind flow`を選択し、`Browser flow`に対して、今回作成したフローを適用させます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/aaf3bfea-bd62-b663-b94c-8e0424f01c62.png)


### Emailの設定
「Realm settings」のEmailタブから送信元となるメールアドレスを入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/80684f88-7213-3a83-c0f8-eb2d15f2ccb8.png)
下にスクロールし、「Host」に送信時に使用するSMTPサーバのホスト名、「Port」にポート番号を入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/c54f0fda-d7f5-c2d8-fc84-86df03653124.png)

## 動作確認
準備が整ったので、ユーザがログイン時にOTPが該当ユーザのメールアドレスに送られることを確認しましょう。
まずはサンプルアプリを起動し、「要認証」ページを開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/78c42f56-7310-56db-9e9a-99614bcedf96.png)
Keycloakの認証画面が表示されるので、ユーザIDとパスワードを入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/dbd09e54-2673-4a07-f887-925d6988d418.png)
次に以下の画面が表示され、KeycloakからOTPを含んだメールがEmailの設定画面で設定したSMTPサーバに送られます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/8d2aa64d-8ad4-d40e-2589-372bde09a145.png)
開発用メールサーバのViewerでメールが届いていることを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/fc73110e-63d3-d2cd-2502-e7816a4a4ae6.png)
届いているメールの「Access code」を入力し、Submitします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/4f5eba95-14a1-464a-6ada-a80de5a2675a.png)
サンプルアプリの要認証画面を開くことができました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3341882/4b65652f-f56a-a5ec-7439-95f94fc21899.png)


# まとめ
今回、「多要素認証」の例として、Keycloak標準搭載である「ワンタイムパスワード認証(OTP)」を試してみましたが、簡単に、よりセキュアな認証設定が出来ました。カスタマイズ開発をしなくてもある程度柔軟に、「認証フロー」の定義変更だけで、要件に合わせて複数の認証を組み合わせた「多要素認証」を実現できるようになっています。

今回のメールを使ったOTP認証のように標準搭載にない認証要件があれば、「[Keycloak Documentation - Server Development (Authentication SPI)](https://www.keycloak.org/docs/21.1.1/server_development/#_auth_spi)」を参照しながら、「認証タイプ」のカスタマイズ開発も可能です。

# 参考資料
- [Keycloak Documentation - Server Administration (Authentication Flows)](https://www.keycloak.org/docs/21.1.1/server_admin/#_authentication-flows)
- [KeycloakのJavaScriptクライアントアダプターを使ってみる。](https://qiita.com/s-takino/items/193a776eb69aedc5ca06)
- [maildev (SMTP mock server) を使ってハイパーメール開発](https://qiita.com/sotarok/items/ff5ca29f88d1f8069c98)
- [Keyclaok 日本語ドキュメント](https://keycloak-documentation.openstandia.jp/21.1/ja_JP/server_development/index.html)