KeycloakでOpenID Connectを使ってみる（Spring Bootアプリケーション編）

<!--
Spring BootベースのWebアプリケーションの認証をOpenID ConnectでKeycloakに委譲する
-->
# 概要

Keycloakアドベンドカレンダー3日目は、**Spring BootベースのWebアプリケーションの認証をKeycloakに委譲する**手順について説明します。KeycloakにはSpring Boot用のアダプターがあり、簡単にSpring BootベースのWebアプリケーションをセキュリティ保護できます。これにより、最新の認証プロトコルである**「OpenID Connect」**(以下OIDC)に対応したセキュアなSpring Bootアプリケーションとなります。

連携ができると、ユーザーはOIDCの「End User」に、Spring BootベースのWebアプリケーションはOIDCの「Relying Party」に、KeycloakはOIDCの「OpenID Provider」になります。

<!--
![env.png](https://qiita-image-store.s3.amazonaws.com/0/43869/b7accfd4-a4fa-d495-0dca-bac3c962e45e.png)
-->

![env.png](https://qiita-image-store.s3.amazonaws.com/0/43869/24c63d44-c00c-7311-9b87-9792a8f3a3d7.png)

# はじめる前に

このチュートリアルの操作をはじめる前に、Keycloakのセットアップを完了し、管理者ユーザーを作成する必要があります。[Keycloakアドベンドカレンダー2日目](https://qiita.com/tamura__246/items/13fc301e9409fef77bf3)の記事を参考にそれらの作業を実施しておいて下さい。また、Webアプリケーションのビルドと起動にGradleかMavenを使用しますので、いずれかをインストールしておいて下さい。

なお、今回はKeycloakもSpring BootベースのWebアプリケーションもlocalhostに構築します。

# 連携の手順

連携の手順は以下のようになります。これが完了したら、動作確認をします。

 1. Keycloakの設定
   - 1.1. レルムの作成
   - 1.2. クライアントの作成
   - 1.3. ロールの作成
   - 1.4. ユーザーの作成とロールの割り当て
 2. 簡単なSpring Bootアプリケーションの作成
   - 2.1. Spring Initializrでアプリの雛形作成
   - 2.2. クラスとhtmlの作成
   - 2.3. アプリのKeycloak連携の設定

## 1.1. レルムの作成

レルムを作成する前にレルムについて簡単に説明します。**Keycloakにおける「レルム(Realm)」とは、ユーザー、ロール、接続するデータストア(LDAPなど)などをまとめるための範囲を意味します。**ユーザーは、レルム内に作成することができ、認証の方法などをレルム単位で定義することができます。デフォルトでは、「Master」というレルムが一つあり、その後追加する全てのレルムを包含して管理することができます。「Master」レルムは、レルムの階層内で最も高いレベルであり、スーパー管理者(初回セットアップ時に作成した管理者アカウント)で管理できます。

<!--
![realm.png](https://qiita-image-store.s3.amazonaws.com/0/43869/19222b0c-236d-624d-8903-bb53eacb95b4.png)
-->

![realm.png](https://qiita-image-store.s3.amazonaws.com/0/43869/01b693f0-4f38-9ed6-595b-1a4f0007760e.png)

「Master」レルムを使ってユーザーなどを管理することもできますが、基本的にはレルムを作成することが推奨されているので、まずはレルムを作成します。なお、「Master」レルムは削除することもできます。

[http://localhost:8080/auth/admin/](http://localhost:8080/auth/admin/)にアクセスして、管理者アカウントでKeycloak管理コンソールにログインします。

|![2017-10-29 11.34.04 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/da505248-6588-10da-5fc8-7ce1c5abad11.png)|
|:-:|

左側の一番上コーナーにある「Master」と表示されたドロップダウン・メニューで、「レルムの追加」をクリックします。

|![2017-11-04 01.03.23 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/f5e310c7-07f4-b696-4f2c-a1e3746ced94.png)|
|:-:|

<!--
![2017-11-04 01.03.23 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/f5e310c7-07f4-b696-4f2c-a1e3746ced94.png)
-->

レルム追加ページが開きます。新規レルムを作成するので、レルム名に「demo」と入力して「作成」ボタンをクリックします。

|![2017-11-04 01.07.14 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/bf6e13a7-9f39-d387-6ee3-30befd87b477.png)|
|:-:|

レルムを作成すると、管理コンソールのメインページに遷移します。現在のレルムは「demo」となっているはずです。

|![2017-11-04 01.10.35 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/ab4867c3-c592-2809-197b-75f0c329a72b.png)|
|:-:|

左上隅のドロップダウン・メニューをクリックして、管理下にあるmasterレルムと今作成したレルムを切替えることができます。

## 1.2. クライアントの作成

次に、クライアントを作成します(※)。今回のケースにおいて、クライアントとは、OIDCで規定された「Relying Party」(OIDC 1.0のベースとなっているOAuth 2.0では「クライアント」とも言います)であり、Spring Bootアプリケーションのことになります。

<table><tr><td>※正確に言うと、クライアントのプロファイルを作成します。クライアント自体は、後述する「Spring Initializr」などを使って作成します。</td></tr></table>

左メニューバーで「クライアント」をクリックします。クライアントの一覧画面が表示されるので、「作成」ボタンをクリックします。

|![2017-11-05 20.25.52 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/d40c874e-801f-bc2d-4419-3110ae175df0.png)|
|:-:|

「sample-app」と入力して、「保存」ボタンをクリックします。

|![2017-11-05 20.26.06 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/ef204867-c028-95f1-527f-a5415aa60a5a.png)|
|:-:|

「有効なリダイレクトURI」に「`http://localhost:8081/hello`」と入力します。

|![Screenshot from 2017-11-30 21-26-28.png](https://qiita-image-store.s3.amazonaws.com/0/43869/44211bde-b7dd-71ca-2880-beedb98a5894.png)
|:-:|

<!--![2017-11-05 20.27.19 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/9f17916d-8ca3-3e59-b85f-ce215ea23c75.png)|

|![2017-11-05 20.27.19 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/d5251b2d-4159-41b9-bc45-84c9c3b16792.png)|
|:-:|
-->

## 1.3. ロールの作成

次に、ユーザーに割り当てるロールを作成します。このロールが割り当てられたユーザーだけが、Spring Bootアプリケーションにアクセスできるように、後で設定を行います。

<table><tr><td>ロールは、ユーザーの役割を意味し、ユーザーのタイプやカテゴリを識別するために利用します。管理者、ユーザー、マネージャー、従業員などが、組織内に存在する典型的なロールです。アプリケーションでは、ユーザーの管理が難しくならないように、個々のユーザーではなく特定のロールにアクセス権を割り当てることがよくあります。</td></tr></table>

左メニューバーで「ロール」をクリックします。ロールの一覧画面が表示されるので、「作成」ボタンをクリックします。

|![2017-11-05 20.34.06 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/3a6b651c-5a9e-7b08-a0f8-f2c92e0d07c3.png)|
|:-:|

「ロール名」に「user」と入力して「保存」ボタンをクリックします。

|![2017-11-05 20.34.29 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/4c2f2409-ef7a-a876-5d7a-60b4867bf547.png)|
|:-:|

## 1.4. ユーザーの作成とロールの割り当て

最後に、ユーザーを作成し、ロールを割り当てます。左メニューバーで「ユーザー」をクリックします。ユーザーの一覧画面が表示されるので、「作成」ボタンをクリックします。

ユーザー一覧のページが開きます。空のユーザーリストの右側にある「ユーザーの追加」をクリックします。

|![2017-11-04 01.36.39 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/f1dc4d9a-d146-20be-17d1-ada662f8fb37.png)|
|:-:|

必須入力のユーザー名のみを入力して、「保存」ボタンをクリックすると、新規ユーザーの管理ページが開きます。

|![2017-11-04 01.38.12 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/35cd7ced-dab4-bbdd-441a-700a25d4e671.png)|
|:-:|

次に、新規ユーザーのパスワードを設定します。「クレデンシャル」タブをクリックします。

新しいパスワードとパスワード(確認)を入力すると、赤い「パスワードリセット」ボタンが表示されます。この時、「一時的」は「オフ」に変更して下さい。これにより、初回ログイン時にパスワードを変更する必要がなくなります。

|![2017-11-04 11.37.09 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/7d65e588-e535-80a3-2f2b-40cfd92d23ac.png)|
|:-:|

「パスワードリセット」ボタンをクリックすると、パスワードがリセットされます。

最後に「ロールマッピング」のタグをクリックして、「使用可能なロール」の中にある`user`を「アサイン済みロール」に移動します。

|![2017-11-05 21.48.03 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/db51c6d6-3fa2-4c17-9079-82b53d25873e.png)|
|:-:|

以上でKeycloakの設定は完了です。

## 2.1. Spring Initializrでアプリの雛形作成

次に、Spring Bootアプリケーションを作成します。Spring Bootアプリケーションの雛形作成には、Spring Initializrを使用します。[Spring Initializr](https://start.spring.io/)のページにアクセスして下さい。

|![2017-11-05 20.42.16 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/46611ad4-7cf1-6bd3-6940-deda75a2aea0.png)|
|:-:|

上の画面のように以下を入力、選択してから、「Generate Project」ボタンをクリックして下さい。

|項目|設定値|
|---|---|
|Project|Gradle ProjectまたはMaven Project|
|言語|Java|
|Dependencies|Web、Thymeleaf、DevTools、Keycloak|
|Group|com.example|
|Artifact|sample-app|

圧縮ファイルがダウンロードされるので、適当なディレクトリに解凍して下さい。

## 2.2. クラスとhtmlの作成

まずはトップページの静的なHTMLを作成します。`src/main/resources/static/`に`index.html`というHTMLファイルを次のような内容で作成します。

```html
<html>
<head>
    <title>Sample Application</title>
</head>
 <body>
   <h1>Sample Application</h1>
   <a href="/hello">Go to hello page!</a>
 </body>
</html>
```

次にこの画面から呼び出されるコントローラーと画面を作成します。コントローラーは、`src/main/java/com/example/sampleapp/`に`HelloController.java`というファイル名で次のような内容とします。

```java
package com.example.sampleapp;

import java.util.Locale;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
class HelloController {

    @RequestMapping(value = "/hello")
    public ModelAndView hello(HttpSession ses, ModelAndView mav, Locale locale) {
    	mav.setViewName("hello");
        mav.addObject("message", "Hello! Spring Boot and Keycloak!");
        return mav;
    }
    
    @RequestMapping(value = "/logout")
    public String logout(HttpServletRequest request) throws ServletException {
       request.logout();
       return "redirect:/";
    }
}
```

このコントローラから呼び出される画面は、`src/main/resources/templates/`に`hello.html`というHTMLファイルで作成します。

```html
<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" th:with="lang=${#locale.language}" th:lang="${lang}">
<h1>Hello</h1>
<p th:text="${message}" /><br />
<p>
    <a href="/logout">Logout</a>
</p>
</html>
```

ディレクトリ構成は以下のようになっています。

![2017-11-05 21.02.14 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/5653a713-1c28-d3a0-29d9-971e4f5e62e8.png)

これでサンプルのWebアプリケーションは完成しました。

## 2.3. アプリのKeycloak連携の設定

最後に、作成したWebアプリケーションにKeycloakと連携するための設定を行います。`/src/main/resources/application.properties`に以下の定義を追加して下さい。

```properties
keycloak.auth-server-url=http://localhost:8080/auth
keycloak.realm=demo
keycloak.public-client=true
keycloak.resource=sample-app
keycloak.security-constraints[0].authRoles[0]=user
keycloak.security-constraints[0].securityCollections[0].patterns[0]=/hello/
server.port=8081
```

[http://localhost/hello/](http://localhost/hello/)以下へのアクセスには認証が必要で、そのユーザーにはロール「user」が必要であることを定義しています。最後のプロパティは、このアプリケーションがポート8081でリッスンされることを指定しています。 

以上で全ての設定は完了です。

# 動作確認

では、動作確認してみましょう。次のコマンドで作成したWebアプリケーションを起動します。

Gradleの場合

```bash
$ gradle bootRun
```

Mavenの場合

```bash
$ mvn spring-boot:run
```

[http://localhost:8081](http://localhost:8081)にアクセスすると、次のような画面が表示されます。

|![2017-11-05 22.10.02 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/9a9fda5f-cb42-7d09-5fff-da30c9ebf356.png)|
|:-:|

「Go to hello page!」のリンクをクリックすると、Keycloakのログイン画面にリダイレクトされます。

|![2017-11-05 22.04.53 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/dd290f64-31b8-1bbe-629a-c3955d429a19.png)|
|:-:|

作成したユーザーでログインして下さい。これまでの設定が適切にできていれば、次のような画面が表示されます。

|![2017-11-05 22.11.56 からのスクリーンショット.png](https://qiita-image-store.s3.amazonaws.com/0/43869/1c8e4440-761d-9471-f1a0-85bd08e33597.png)|
|:-:|

# 最後に

このようにSpring BootベースのWebアプリケーションの認証をKeycloakに委譲するのはとても簡単です。簡単なだけでなく、標準的でセキュアなログイン処理を実装したことにもなります。

今回はSpring Boot Adapterを使用しましたが、Spring Security Adapterというものもあり、Spring Boot Keycloak Starterに含まれています。これについては、また別の機会に説明します(その機会があれば...)。

----
# 参考資料

 - [Keycloak Documentation](http://www.keycloak.org/documentation.html)
 - [NRI OpenStandia Keycloak日本語ドキュメント - Keycloak 入門編](http://keycloak-documentation.openstandia.jp/master/ja_JP/getting_started/index.html)
 - [Red Hat Developer Program Blog - Easily secure your Spring Boot applications with Keycloak](https://developers.redhat.com/blog/2017/05/25/easily-secure-your-spring-boot-applications-with-keycloak/)

　[![mark.png](https://github.com/k-tamura/cocha-icons/raw/master/mark.png)](https://github.com/k-tamura/cocha-icons)