﻿# 如何用 Docker 快速上手 Keycloak 開發模式

要體驗強大的 [Keycloak](https://www.keycloak.org/) 這套開源的身分認證與存取管理系統，最簡單的方式莫過於透過 Docker/Podman 執行 Keycloak 的開發模式了。你可以在短時間內就可以架設出一套擁有 OpenID Connect (OIDC) 與 OAuth 2.0 提供者的完整實作，是一套功能強大同時又免費的解決方案。這篇文章我就來描述一下 Keycloak 啟動與初始化設定的過程。

### 啟動 Keycloak 伺服器

1.  拉取最新版容器映象
    
    ```bash
    docker pull quay.io/keycloak/keycloak:latest
    ```
    
2.  啟動 Keycloak 容器 (開發模式)
    
    ```bash
    docker run --name keycloak_test -d -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=changeME quay.io/keycloak/keycloak:latest start-dev
    ```
    
    以下是初次啟動時的 Docker Logs
    
    ```bash
    docker logs keycloak_test
    ```
    
    ```
    Updating the configuration and installing your custom providers, if any. Please wait.
    2023-04-21 06:35:34,329 INFO  [io.quarkus.deployment.QuarkusAugmentor] (main) Quarkus augmentation completed in 5974ms
    2023-04-21 06:35:35,897 INFO  [org.keycloak.quarkus.runtime.hostname.DefaultHostnameProvider] (main) Hostname settings: Base URL: <unset>, Hostname: <request>, Strict HTTPS: false, Path: <request>, Strict BackChannel: false, Admin URL: <unset>, Admin: <request>, Port: -1, Proxied: false
    2023-04-21 06:35:37,128 WARN  [io.quarkus.agroal.runtime.DataSources] (main) Datasource <default> enables XA but transaction recovery is not enabled. Please enable transaction recovery by setting quarkus.transaction-manager.enable-recovery=true, otherwise data may be lost if the application is terminated abruptly
    2023-04-21 06:35:37,846 INFO  [org.infinispan.SERVER] (keycloak-cache-init) ISPN005054: Native IOUring transport not available, using NIO instead: io.netty.incubator.channel.uring.IOUring
    2023-04-21 06:35:38,029 WARN  [org.infinispan.CONFIG] (keycloak-cache-init) ISPN000569: Unable to persist Infinispan internal caches as no global state enabled
    2023-04-21 06:35:38,062 WARN  [org.infinispan.PERSISTENCE] (keycloak-cache-init) ISPN000554: jboss-marshalling is deprecated and planned for removal
    2023-04-21 06:35:38,080 INFO  [org.infinispan.CONTAINER] (keycloak-cache-init) ISPN000556: Starting user marshaller 'org.infinispan.jboss.marshalling.core.JBossUserMarshaller'
    2023-04-21 06:35:38,226 INFO  [org.keycloak.broker.provider.AbstractIdentityProviderMapper] (main) Registering class org.keycloak.broker.provider.mappersync.ConfigSyncEventListener
    2023-04-21 06:35:39,530 INFO  [org.keycloak.quarkus.runtime.storage.legacy.liquibase.QuarkusJpaUpdaterProvider] (main) Initializing database schema. Using changelog META-INF/jpa-changelog-master.xml
    2023-04-21 06:35:41,141 INFO  [org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory] (main) Node name: node_126670, Site name: null
    2023-04-21 06:35:41,277 INFO  [org.keycloak.services] (main) KC-SERVICES0050: Initializing master realm
    2023-04-21 06:35:42,875 INFO  [io.quarkus] (main) Keycloak 21.1.0 on JVM (powered by Quarkus 2.13.7.Final) started in 8.393s. Listening on: http://0.0.0.0:8080
    2023-04-21 06:35:42,875 INFO  [io.quarkus] (main) Profile dev activated.
    2023-04-21 06:35:42,876 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, jdbc-h2, jdbc-mariadb, jdbc-mssql, jdbc-mysql, jdbc-oracle, jdbc-postgresql, keycloak, logging-gelf, micrometer, narayana-jta, reactive-routes, resteasy, resteasy-jackson, smallrye-context-propagation, smallrye-health, vertx]
    2023-04-21 06:35:43,025 INFO  [org.keycloak.services] (main) KC-SERVICES0009: Added user 'admin' to realm 'master'
    2023-04-21 06:35:43,028 WARN  [org.keycloak.quarkus.runtime.KeycloakMain] (main) Running the server in development mode. DO NOT use this configuration in production.
    ```
    
    從 Log 可以看出，他預設會初始化一個名為 `master` 的 realm (領域)，而且新增了一位名為 `admin` 的使用者到 `master` realm 中。除此之外，開發模式的預設設定比較不安全，若要正式環境使用請參考 [Configuring Keycloak for production](https://www.keycloak.org/server/configuration-production) 文件說明。
    
    預設網址: [http://localhost:8080](http://localhost:8080) (點選 `Administration Console` 進行登入)
    
    預設帳號: `admin`
    
    預設密碼: `changeME`
    
    ![Keycloak Administration UI](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/3fc775ee4bfe9b4ee7b5-233561371-d764effc-a8f6-4e4e-aaa6-5e447333fc2d.jpg)
    

### 初始化設定

1.  建立 Realms (領域)
    
    由於預設已經有一個名為 `master` 的領域(Realm)，所以你不一定要建立新的。不過，這邊有幾個重要的觀念必須事先建立：
    
    -   每個不同的 Realms 之間是互相獨立隔離的
    -   一個 Realms 只能管理它下面所屬的使用者
    -   一個使用者只能屬於並且能登入到一個 Realms
2.  建立 Clients (用戶端)
    
    這裡的選項很多，每個都要消化一下！
    
    **General Settings**
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/0c95c41119b839956475-233567034-a49f5ddd-d170-4f82-87f8-90156acf5b07.png)
    
    這裡只要設定 **Client ID** 與 **Name** 即可。
    
    **Capability config**
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/07f83df526697f13817c-233563818-14f4f878-579d-4984-9f67-47be6e27f2a9.png)
    
    在 Keycloak 之中，用戶端區分成三種不同的 Access Type (存取類型)：
    
    -   Access Type=`confidential` 用在可以妥善保存 `client_secret` 的情境
    -   Access Type=`public` 用在 `client_secret` 無法妥善保存的情境 (SPA)
    -   Access Type=`bearer-only` 用在 Web API 只能用 `Bearer Token` 需要直接呼叫 RS (資源伺服器) 的情境
    
    這裡的 `Client authentication` 如果設定為 `On` 的話，所代表的意思就是要對 Client 進行 `client_id` 與 `client_secret` 的驗證，也就是 OAuth 2.0 的 `Authorization Code Flow` 這個授權流程在取得 Token 時一定會用到的資訊。
    
    另外，這個 `Authentication flow` 寫的也有點隱晦，前 4 個跟 OAuth 2.0 標準中定義的流程也很難對的上，以下我概要說明一下：
    
    -   `Standard flow` = `Authorization Code Flow`
    -   `Implicit flow` = `Implicit Flow`
    -   `Direct access grants` = `Resource Owner Password Credentials Grant` (ROPC)
    -   `Service accounts roles` = `Client Credentials Grant`
    -   OAuth 2.0 Device Authorization Grant = [OAuth 2.0 Device Authorization Grant](https://oauth.net/2/device-flow/)
    -   OIDC CIBA Grant = [Client-Initiated Backchannel Authentication Flow](https://cloudentity.com/developers/basics/oauth-grant-types/ciba-flow/)
    
    **Login settings**
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/92f7bdb91790014903e6-233568298-b2b9c891-e83c-47ff-b6cf-0a84024e9cef.png)
    
    這裡只有 `Valid redirect URIs` 是必要設定，而 Web origins 主要是設定 CORS 設定，輸入 `+` 就代表直接拿 `Valid redirect URIs` 當作 CORS 的 Origin！
    
    按下 `SAVE` 儲存！
    
    **Credentials**
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/6d941ed894bf8ddefefc-233569164-6a8d1dbf-9ada-4465-9f11-16b32596fab8.png)
    
    這個 `Credentials` 頁籤要先建立完用戶端之後才會出現，你在這個時候才能建立 OAuth 2.0 所需的 Client secret 來用！
    
3.  建立 Realm roles (領域角色)
    
    建立特定領域下的角色，有些內建的角色擁有不同的權限，角色可以設定 Attributes，也可以把使用者指派到特定角色中。
    
4.  建立 Users (使用者)
    
    你可以在領域下建立多位不同的使用者，用來進行 SSO (Single-Sign On) 或通過 OpenID Connect 認證流程。
    
5.  建立 Groups (群組)
    
    你可以在領域下建立多個不同的群組，群組可以設定 Attributes，也可以設定 Role Mapping，群組是可以分層的(hierarchical)，一個群組可以包含多個子群組，但一個子群組只能隸屬於一個父群組。父群組的 Attributes 與 Role Mapping 會自動繼承給所有他的子群組。
    
    > 這在 ABAC (Attribute-based Access Control) 架構下很好用。
    

### 取得端點資訊

要跟 Keycloak 整合 OAuth 2.0 與 OpenID Connect，最重要的就是取得所有相關的端點資訊，這部分資訊放在 `Configure` > `Realm settings` 頁面下方：

![Configure > Realm settings](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/2c7ab1c2e40223612797-233576062-b25ce657-e6d2-4da8-b237-b22be388b530.jpg)

注意：每個 Realm 因為都是獨立的，所以不同的 Realm 都會有不同的 Endpoints，所以才必須要到上述頁面查找資訊。

本地開發模式 `master` 領域的端點如下：

```json
{
  "issuer": "http://localhost:8080/realms/master",
  "authorization_endpoint": "http://localhost:8080/realms/master/protocol/openid-connect/auth",
  "token_endpoint": "http://localhost:8080/realms/master/protocol/openid-connect/token",
  "introspection_endpoint": "http://localhost:8080/realms/master/protocol/openid-connect/token/introspect",
  "userinfo_endpoint": "http://localhost:8080/realms/master/protocol/openid-connect/userinfo",
  "end_session_endpoint": "http://localhost:8080/realms/master/protocol/openid-connect/logout",
  "jwks_uri": "http://localhost:8080/realms/master/protocol/openid-connect/certs",
  "check_session_iframe": "http://localhost:8080/realms/master/protocol/openid-connect/login-status-iframe.html",
  "registration_endpoint": "http://localhost:8080/realms/master/clients-registrations/openid-connect",
  "revocation_endpoint": "http://localhost:8080/realms/master/protocol/openid-connect/revoke",
  "device_authorization_endpoint": "http://localhost:8080/realms/master/protocol/openid-connect/auth/device",
  "backchannel_authentication_endpoint": "http://localhost:8080/realms/master/protocol/openid-connect/ext/ciba/auth",
  "pushed_authorization_request_endpoint": "http://localhost:8080/realms/master/protocol/openid-connect/ext/par/request",
}
```

### 相關連結

-   [Server - Guides - Keycloak](https://www.keycloak.org/guides#server)
-   [Running Keycloak in a container](https://www.keycloak.org/server/containers)
-   [Configuring Keycloak for production](https://www.keycloak.org/server/configuration-production)
