KeycloakへSpring Bootアプリケーションから接続

# はじめに

ここでは、Keycloakをインストール・設定し、Spring Bootアプリケーションから接続することを試してみます。
メモ書きなので説明は必要最小限です（暇なときに説明を書くかも）


# 環境

使用した環境とバージョンは以下のとおりです。

- CentOS 7.5(Keycloak)
- Keycloak 10.0.2
- Spring Boot 2.3.1

作成するSpring BootアプリケーションはローカルのWindows上で開発・実行します。


# Keycloakインストール

Keycloakのインストールの前に、まずOpenJDK（1.8）をインストールします。

```
# yum install java
```

次のKeycloakのインストールですが、下のURLからモジュール(zip)をダウンロードし、サーバへ展開するだけです。

- https://www.keycloak.org//downloads.html

今回ダウンロードしたのは「keycloak-10.0.2.zip」です。

```
# unzip keycloak-10.0.2.zip 
```

# Keycloakの起動と設定

Keycloakを起動する前に管理者アカウント（admin）を作成します。

```
# cd keycloak-10.0.2
# ./bin/add-user-keycloak.sh -u admin
Press ctrl-d (Unix) or ctrl-z (Windows) to exit
Password: 
Added 'admin' to '/opt/keycloak-10.0.2/standalone/configuration/keycloak-add-user.json', restart server to load user
```

次にKeycloakを起動します。ローカル以外からもアクセスできるように「-b 0.0.0.0」をつけて起動しています。

```
# ./bin/standalone.sh -b 0.0.0.0
～省略～
00:35:51,363 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0060: Http management interface listening on http://127.0.0.1:9990/management
00:35:51,363 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0051: Admin console listening on http://127.0.0.1:9990
00:35:51,363 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: Keycloak 10.0.2 (WildFly Core 11.1.1.Final) started in 22714ms - Started 591 of 889 services (606 services are lazy, passive or on-demand)
```

起動後、以下のURLをブラウザから開きます。

- http://[ホスト名]:8080/auth/

ブラウザでは以下の画面が開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179607/b0c9d91f-b328-97c9-3183-9044ff4b1258.png)

[Administration Console]をクリックするとログイン画面へ遷移します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179607/569cdfb7-c2f1-129e-4d18-18332f51309c.png)

作成していた管理者アカウント（admin）でログインします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179607/d7061a83-a061-2423-2c21-e5bfc91b7721.png)

ログイン後、最初にレルム（realm）を作成します。
ここでは"test realm"としています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179607/1bc6cedc-ad40-d3aa-f2d8-e6f4295b342c.png)

外部アプリ用のクライアントを作成します。
[Clients]タブを選択し、[Create]をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179607/6f22c8ec-1cbd-b0b9-f9e5-1e71bd8b84d4.png)

Client IDを入力します。ここでは"login-app"としています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179607/42df8c99-5f04-c0ba-d4fe-7451acc148cf.png)

作成完了後、作成したClientを一覧から選択し、"Valid Redirect URIs"を"http://localhost:8081/*"に変更しています。これが今回作成するSpring BootアプリケーションのURIとなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179607/6dae87ce-7843-3674-a4ae-250c5e0af4ba.png)


# ロール作成

次にロールを作成するために[Roles]タブを選択し、[Add Role]をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179607/9fe4ae03-b56d-f93e-58b4-a5c8af89a64d.png)

ロール名（user）を入力し、[Save]をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179607/720fc7f1-3601-b5c6-8646-2a0e9eeb8a6b.png)



# ユーザ作成

ユーザを作成するために[Users]タブを選択し、[Add user]をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179607/afd1abd0-d679-9902-1a5e-5ccc81f4591a.png)

Usernameに[user1]を入力し、[Save]をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179607/79d5a4da-94a8-644b-92db-34720516fabf.png)

[Credentials]タブを選択し、パスワードを入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179607/ab1ecdbe-96fd-caf2-2a51-f4bdaaf746d6.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179607/c2d13f90-4fb4-52c3-11fd-5548f41b6e93.png)


# ロールとユーザの紐づけ

作成したユーザ（user1）をロール（user）に紐づけるため、[Role Mappings]タブを選択し、user1を[Available Roles]から[Assigned Roles]へ移動します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179607/fa36814a-cda0-d0b9-710e-3a48ad21fcff.png)



# Spring BootでKeycloakを使用

Keycloakクライアントアダプターを使用してSpring Bootに接続します。


## pom.xml

以下のpom.xmlを持つMavenプロジェクトを作成します。

```xml:pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>test</groupId>
    <artifactId>spring-boot-keycloak</artifactId>
    <version>0.0.1</version>
    <packaging>jar</packaging>

    <properties>
        <keycloak-adapter-bom.version>10.0.2</keycloak-adapter-bom.version>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
    </parent>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.keycloak.bom</groupId>
                <artifactId>keycloak-adapter-bom</artifactId>
                <version>${keycloak-adapter-bom.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <version>2.5.1</version>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
```


## メインアプリ

今回のメインとなるアプリケーションクラスです。
普通のSpring Bootのアプリケーションです。

```java.App.java
@SpringBootApplication
public class App {

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

}
```


# コントローラとHTMLファイルの作成

Controller(コントローラ)クラスを以下のように作成。
"/" -> index.html, "hello" -> hello.htmlを読み込むようにしています。

```java:WebController.java
@Controller
public class WebController {

    @GetMapping(path = "/")
    public String index() {
        return "index";
    }

    @GetMapping(path = "/hello")
    public String customers(Principal principal, Model model) {

        model.addAttribute("username", principal.getName());
        return "hello";
    }

}
```

index.htmlとhello.htmlはJavaテンプレートエンジンであるThymeleafを使用しています。
index.htmlにアクセスするとログインリンクが出て、リンクをクリックするとKeyCloakによるログインの後、hello.htmlに遷移するようにしています。

```html:index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
</head>
<body>
	<div class="container">
		<div>
			<h1>Portal</h1>
		</div>
		<div>
			<a th:href="@{/hello}">Login</a>
		</div>
	</div>
</body>
</html>

```

```html:hello.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
</head>
<body>
	<div id="container">
		<h1>
			Hello, <span th:text="${username}"></span>
		</h1>
	</div>
</body>
</html>
```


# Keycloakとアプリケーションの設定

application.propertiesは以下のように設定しています。

```properties:application.properties
# server port
server.port=8081

# Keycloak
keycloak.auth-server-url=http://[Keycloakサーバのホスト名]:8080/auth
# レルム名を設定する。
keycloak.realm=test realm
# クライアントIDを設定する。
keycloak.resource=login-app
keycloak.public-client=true
## OpenID ConnectのIDトークン属性を設定。
keycloak.principal-attribute=preferred_username
```


## Keycloakクライアントアダプター

Keycloakクライアントアダプター（keycloak-spring-security-adapter）を使用して、アクセス制御する設定を行います。


```java:SecurityConfig.java
@KeycloakConfiguration
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        KeycloakAuthenticationProvider provider = keycloakAuthenticationProvider();
        provider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(provider);
    }

    // Spring Security AdapterとSpring Boot Adapterを一緒に使用する場合、application.propertiesから
    // 設定情報を取得するために必要。
    @Bean
    public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }

    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.authorizeRequests()
            .antMatchers("/hello*")
            .hasRole("user")
            .anyRequest().permitAll();
    }
}
```

ポイントは以下の箇所で、"/hello*"にマッチした場合、"user"ロールを持っている場合のみ認可するようにしています。

```java

        http.authorizeRequests()
            .antMatchers("/hello*")
            .hasRole("user")
            .anyRequest().permitAll();
```

プログラム作成は以上で完了です。


# 作成したSpring Bootアプリケーションの起動

作成したSpring Bootアプリケーションの起動するために以下のコマンドを実行します。

```
mvn clean spring-boot:run
```

起動後、以下のURLでSpring Bootアプリケーションへアクセスします。

- http://localhost:8081/

アクセスすると、以下のログインリンクがある画面が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179607/78a78009-4c43-7be1-9e11-b80cf4666402.png)

ここでリンクをクリックすると、「/hello」とマッチしてKeycloakでの認証にうつります。ログイン画面ではKeycloakの管理コンソールで作成した、"user"ロールを持つ"user1"でログインします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179607/e805fd66-bf93-5555-30c6-242f8f457799.png)

初めてのログインの場合は以下のようにパスワードの変更が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179607/821894ab-8907-7122-e05d-cd5a572576d9.png)

ログインに成功すると、以下のようにユーザ名を表示するだけの画面へ遷移するようになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179607/0f0a455b-3131-0d40-1e43-911b7f22d74b.png)



# 参考

- [A Quick Guide to Using Keycloak with Spring Boot](https://www.baeldung.com/spring-boot-keycloak)