CLOVER🍀

That was when it all began.

Keycloak 19.0の管理CLIを使ってみる

これは、なにをしたくて書いたもの?

以前、WildFlyベースのKeycloakを使っていた時に、管理CLIについて調べてみました。

KeycloakのAdmin CLIを使う - CLOVER🍀

KeycloakがQuarkusベースになってから、まだ管理CLIを使っていなかったので今回試してみることにしました。

結論、前と同じ感じでしたけどね。

管理CLI

管理CLIについては、ドキュメントがあります。

Server Administration Guide / Admin CLI

Keycloakのbinディレクトリ内にあるkcadm.sh(またはkcadm.bat)が管理CLIです。

管理CLIは、管理用のREST APIを使って実際の操作を行うようなので、こちらのドキュメントも見ておくと理解が進むでしょう。

Server Administration Guide / Admin CLI / Basic operations and resource URIs

Keycloak Admin REST API

では、管理CLIのドキュメントを見つつ、いくつか操作してみたいと思います。

環境

今回の環境は、こちら。

$ bin/kc.sh --version
Keycloak 19.0.2
JVM: 17.0.4.1 (Eclipse Adoptium OpenJDK 64-Bit Server VM 17.0.4.1+1)
OS: Linux 5.4.0-125-generic amd64

管理ユーザーは、Keycloak起動時に環境変数を指定して作成しておくものとします。

$ KEYCLOAK_ADMIN=admin KEYCLOAK_ADMIN_PASSWORD=password bin/kc.sh start-dev

ヘルプを見る

管理CLIhelpコマンドでヘルプを見ることができます。

$ bin/kcadm.sh help
Keycloak Admin CLI

Use 'kcadm.sh config credentials' command with username and password to start a session against a specific
server and realm.

For example:

  $ kcadm.sh config credentials --server http://localhost:8080/auth --realm master --user admin
  Enter password:
  Logging into http://localhost:8080/auth as user admin of realm master

Any configured username can be used for login, but to perform admin operations the user
needs proper roles, otherwise operations will fail.

Usage: kcadm.sh COMMAND [ARGUMENTS]

Global options:
  -x            Print full stack trace when exiting with error
  --help        Print help for specific command
  --config      Path to the config file (~/.keycloak/kcadm.config by default)

Commands:
  config        Set up credentials, and other configuration settings using the config file
  create        Create new resource
  get           Get a resource
  update        Update a resource
  delete        Delete a resource
  get-roles     List roles for a user or a group
  add-roles     Add role to a user or a group
  remove-roles  Remove role from a user or a group
  set-password  Re-set password for a user
  help          This help

Use 'kcadm.sh help <command>' for more information about a given command.

kcadm.sh [コマンド]の形式で使うようですね。

Usage: kcadm.sh COMMAND [ARGUMENTS]

また、help [コマンド]でコマンドに対するヘルプも見ることができます。

$ bin/kcadm.sh help create
Usage: kcadm.sh create ENDPOINT_URI [ARGUMENTS]

Command to create new resources on the server.

Use 'kcadm.sh config credentials' to establish an authenticated sessions, or use --no-config with
CREDENTIALS OPTIONS to perform one time authentication.

Arguments:

  Global options:
    -x                    Print full stack trace when exiting with error
    --config              Path to the config file (~/.keycloak/kcadm.config by default)
    --no-config           Don't use config file - no authentication info is loaded or saved
    --token               Token to use to invoke on Keycloak.  Other credential may be ignored if this flag is set.
    --truststore PATH     Path to a truststore containing trusted certificates
    --trustpass PASSWORD  Truststore password (prompted for if not specified and --truststore is used)
    CREDENTIALS OPTIONS   Same set of options as accepted by 'kcadm.sh config credentials' in order to establish
                          an authenticated sessions. In combination with --no-config option this allows transient
                          (on-the-fly) authentication to be performed which leaves no tokens in config file.

  Command specific options:
    ENDPOINT_URI              URI used to compose a target resource url. Commonly used values are:
                              realms, users, roles, groups, clients, keys, serverinfo, components ...
                              If it starts with 'http://' then it will be used as target resource url
    -r, --target-realm REALM  Target realm to issue requests against if not the one authenticated against
    -s, --set NAME=VALUE      Set a specific attribute NAME to a specified value VALUE
    -d, --delete NAME         Remove a specific attribute NAME from JSON request body
    -f, --file FILENAME       Read object from file or standard input if FILENAME is set to '-'
    -b, --body CONTENT        Content to be sent as-is or used as a JSON object template
    -q, --query NAME=VALUE    Add to request URI a NAME query parameter with value VALUE
    -h, --header NAME=VALUE   Set request header NAME to VALUE

    -H, --print-headers       Print response headers
    -o, --output              After creation output the new resource to standard output
    -i, --id                  After creation only print id of the new resource to standard output
    -F, --fields FILTER       A filter pattern to specify which fields of a JSON response to output
                              Use 'kcadm.sh get --help' for more info on FILTER syntax.
    -c, --compressed          Don't pretty print the output
    -a, --admin-root URL      URL of Admin REST endpoint root if not default - e.g. http://localhost:8080/auth/admin


Nested attributes are supported by using '.' to separate components of a KEY. Optionaly, the KEY components
can be quoted with double quotes - e.g. my_client.attributes."external.user.id". If VALUE starts with [ and
ends with ] the attribute will be set as a JSON array. If VALUE starts with { and ends with } the attribute
will be set as a JSON object. If KEY ends with an array index - e.g. clients[3]=VALUE - then the specified item
of the array is updated. If KEY+=VALUE syntax is used, then KEY is assumed to be an array, and another item is
added to it.

Attributes can also be deleted. If KEY ends with an array index, then the targeted item of an array is removed
and the following items are shifted.


Examples:

Create a new realm:
  $ kcadm.sh create realms -s realm=demorealm -s enabled=true

Create a new realm role in realm 'demorealm' returning newly created role:
  $ kcadm.sh create roles -r demorealm -s name=manage-all -o

Create a new user in realm 'demorealm' returning only 'id', and 'username' attributes:
  $ kcadm.sh create users -r demorealm -s username=testuser -s enabled=true -o --fields id,username

Create a new client using configuration read from standard input:
  $ kcadm.sh create clients -r demorealm  -f - << EOF
  {
    "clientId": "my_client"
  }
  EOF

Create a new group using configuration JSON passed as 'body' argument:
  $ kcadm.sh create groups -r demorealm -b '{ "name": "Admins" }'

Create a client using file as a template, and override some attributes - return an 'id' of new client:
  $ kcadm.sh create clients -r demorealm -f my_client.json -s clientId=my_client2 -s 'redirectUris=["http://localhost:8980/myapp/*"]' -i

Create a new client role for client my_client in realm 'demorealm' (replace ID with output of previous example command):
  $ kcadm.sh create clients/ID/roles -r demorealm -s name=client_role


Use 'kcadm.sh help' for general information and a list of commands

コマンドによっては、さらにサブコマンドの指定が必要なものもあります。

configコマンドは、その例ですね。

$ bin/kcadm.sh help config
Usage: kcadm.sh config SUB_COMMAND [ARGUMENTS]

Where SUB_COMMAND is one of: 'credentials', 'truststore'


Use 'kcadm.sh help config SUB_COMMAND' for more info.
Use 'kcadm.sh help' for general information and a list of commands.

configコマンドに加えて、credentialsサブコマンドも指定してヘルプを参照。

$ bin/kcadm.sh help config credentials
Usage: kcadm.sh config credentials --server SERVER_URL --realm REALM --user USER [--password PASSWORD] [ARGUMENTS]
       kcadm.sh config credentials --server SERVER_URL --realm REALM --client CLIENT_ID [--secret SECRET] [ARGUMENTS]
       kcadm.sh config credentials --server SERVER_URL --realm REALM --client CLIENT_ID [--keystore KEYSTORE] [ARGUMENTS]

Command to establish an authenticated client session with the server. There are many authentication
options available, and it depends on server side client authentication configuration how client can or should authenticate.
The information always required includes --server, and --realm. Then, --user and / or --client need to be used to authenticate.
If --client is not provided it defaults to 'admin-cli'. The authentication options / requirements depend on how this client is configured.

If confidential client authentication is also configured, you may have to specify a client id, and client credentials in addition to
user credentials. Client credentials are either a client secret, or a keystore information to use Signed JWT mechanism.
If only client credentials are provided, and no user credentials, then the service account is used for login.

Arguments:

  Global options:
    -x                      Print full stack trace when exiting with error
    --config                Path to a config file (~/.keycloak/kcadm.config by default)
    --truststore PATH       Path to a truststore containing trusted certificates
    --trustpass PASSWORD    Truststore password (prompted for if not specified and --truststore is used)

  Command specific options:
    --server SERVER_URL     Server endpoint url (e.g. 'http://localhost:8080/auth')
    --realm REALM           Realm name to use
    --user USER             Username to login with
    --password PASSWORD     Password to login with (prompted for if not specified and --user is used)
    --client CLIENT_ID      ClientId used by this client tool ('admin-cli' by default)
    --secret SECRET         Secret to authenticate the client (prompted for if --client is specified, and no --keystore is specified)
    --keystore PATH         Path to a keystore containing private key
    --storepass PASSWORD    Keystore password (prompted for if not specified and --keystore is used)
    --keypass PASSWORD      Key password (prompted for if not specified and --keystore is used without --storepass,
                            otherwise defaults to keystore password)
    --alias ALIAS           Alias of the key inside a keystore (defaults to the value of ClientId)


Examples:

Login as 'admin' user of 'master' realm to a local Keycloak server running on default port.
You will be prompted for a password:
  $ kcadm.sh config credentials --server http://localhost:8080/auth --realm master --user admin

Login to Keycloak server at non-default endpoint passing the password via standard input:
  $ kcadm.sh config credentials --server http://localhost:9080/auth --realm master --user admin << EOF
  mypassword
  EOF

Login specifying a password through command line:
  $ kcadm.sh config credentials --server http://localhost:9080/auth --realm master --user admin --password $PASSWORD

Login using a client service account of a custom client. You will be prompted for a client secret:
  $ kcadm.sh config credentials --server http://localhost:9080/auth --realm master --client reg-cli

Login using a client service account of a custom client, authenticating with signed JWT.
You will be prompted for a keystore password, and a key password:
  $ kcadm.sh config credentials --server http://localhost:9080/auth --realm master --client reg-cli --keystore ~/.keycloak/keystore.jks

Login as 'user' while also authenticating a custom client with signed JWT.
You will be prompted for a user password, a keystore password, and a key password:
  $ kcadm.sh config credentials --server http://localhost:9080/auth --realm master --user user --client reg-cli --keystore ~/.keycloak/keystore.jks


Use 'kcadm.sh help' for general information and a list of commands

管理ユーザーでログインする

管理CLIを使い、管理ユーザーでKeycloakにログインしてみます。

Server Administration Guide / Admin CLI / Authenticating

configコマンドを使います。

$ bin/kcadm.sh config credentials --server http://localhost:8080 --realm master --user admin --password password
Logging into http://localhost:8080 as user admin of realm master

--serverでKeycloakサーバーのエンドポイントを、--realmでRealm、--user--passwordでユーザー名とパスワードを指定します。

--passwordを指定しない場合は、対話形式で入力することになります。

Logging into http://localhost:8080 as user admin of realm master
Enter password: ********

ユーザー名とパスワードの代わりに、--clientでクライアントIDと--secretでクライアントシークレットを指定してログインすることも
できるようです(--secretを指定しなかった場合は対話形式)。

ログインに成功すると、$HOME/.keycloak/kcadm.configに認証情報が出力されます。

$HOME/.keycloak/kcadm.config

{
  "serverUrl" : "http://localhost:8080",
  "realm" : "master",
  "endpoints" : {
    "http://localhost:8080" : {
      "master" : {
        "clientId" : "admin-cli",
        "token" : "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJHVGJMRFRTbEJneUpoVDBXV2xXQ0xIQzJtNEotbkFFZW1FbG83SVV5Mm13In0.eyJleHAiOjE2NjM0ODM3NjgsImlhdCI6MTY2MzQ4MzcwOCwianRpIjoiNDlhMzAxMDItNmM3YS00YWIzLWFmMTctMDllZTgyMWE0NTFjIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9tYXN0ZXIiLCJzdWIiOiI1YWE3M2JiNS0xYmViLTQyOWYtYWEyYi03NzhlZmRjZWIwNDUiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJhZG1pbi1jbGkiLCJzZXNzaW9uX3N0YXRlIjoiY2UzODAzMTMtMTI2Yy00MWYwLWEyOWQtZjgxZWQwZDM1NTgyIiwiYWNyIjoiMSIsInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsInNpZCI6ImNlMzgwMzEzLTEyNmMtNDFmMC1hMjlkLWY4MWVkMGQzNTU4MiIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWRtaW4ifQ.kKtYwxtrTJ9f5Rn4ASY4vqP21854cl6f8BvEUN9tyU9YJoCUS2Z1Aq10Sq-w7oQX2qZhbuUlnR6HuW30BOiVRM5ZHKH2Q_uxuG_xWB-H2tcD15cyRr9ajgm6nxD1FIflXOaaeTeWB_ow2n6SOqo7DcS40-fE5O3iANc5Cz1sGTNUYG5YWoogvD3cDai-kDGC0sAghZVlT0SScJutDUIWrupFGXLvD7XSfnWHarQdgdOg6COtvyf_lMOaxSxEbrciie5w1rs7GCRdtMidAt3KYcfwLjO8yHvT6TQM4CNrwHn9OKogIYcmyl1Ob5p6sL72DfoB8eK2SwQ4boKhIPytsQ",
        "refreshToken" : "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJjOGZkNWI5ZS03MzRkLTQ0NDctOGI4Ni0zYTlmOTZmODc5MzcifQ.eyJleHAiOjE2NjM0ODU1MDgsImlhdCI6MTY2MzQ4MzcwOCwianRpIjoiNWVkNDE4NDAtZDZmZi00NWI3LWIxOTMtNGNhYjNhMjYwMzc3IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9tYXN0ZXIiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL21hc3RlciIsInN1YiI6IjVhYTczYmI1LTFiZWItNDI5Zi1hYTJiLTc3OGVmZGNlYjA0NSIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJhZG1pbi1jbGkiLCJzZXNzaW9uX3N0YXRlIjoiY2UzODAzMTMtMTI2Yy00MWYwLWEyOWQtZjgxZWQwZDM1NTgyIiwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwic2lkIjoiY2UzODAzMTMtMTI2Yy00MWYwLWEyOWQtZjgxZWQwZDM1NTgyIn0.kbe8hOs_OmIIl3vFxbeKjbIZnD3FQAEprlL87mAIFaE",
        "grantTypeForAuthentication" : "password",
        "expiresAt" : 1663483768063,
        "refreshExpiresAt" : 1663485508063
      }
    }
  }
}

トークンの有効期限が切れると、再度ログインが必要になります。

Realmを操作する

Realmを操作してみましょう。

Server Administration Guide / Admin CLI / Realm operations

Realmの作成。

Server Administration Guide / Admin CLI / Realm operations / Creating a new realm

$ bin/kcadm.sh create realms -s realm=[Realm名] -s enabled=true


## または
$ bin/kcadm.sh create realms --set realm=[Realm名] --set enabled=true

-s--set)は属性を「名前=値」の形式で指定するオプションです。

例。

$ bin/kcadm.sh create realms -s realm=sample-realm -s enabled=true
Created new realm with id 'sample-realm'

Realmの一覧を取得。

Server Administration Guide / Admin CLI / Realm operations / Listing existing realms

$ bin/kcadm.sh get realms

特定のRealmを取得。

Server Administration Guide / Admin CLI / Realm operations / Getting a specific realm

$ bin/kcadm.sh get realms/[Realm名]

例。

$ bin/kcadm.sh get realms/sample-realm

Realmの更新や削除は、こちら。

Server Administration Guide / Admin CLI / Realm operations / Updating a realm

Server Administration Guide / Admin CLI / Realm operations / Deleting a realm

クライアントを操作する

クライアントの操作については、こちら。

Server Administration Guide / Admin CLI / Client operations

クライアントの作成。

Server Administration Guide / Admin CLI / Client operations / Creating a client

$ bin/kcadm.sh create clients -r [Realm名] -s clientId=[クライアントID] -s enabled=true

-r(または--target-realm)は対象となるRealmを指定します。

例。

$ bin/kcadm.sh create clients -r sample-realm -s clientId=app-client -s enabled=true

クライアントの一覧を取得。

Server Administration Guide / Admin CLI / Client operations / Listing clients

$ bin/kcadm.sh get clients -r [Realm名]

例。

$ bin/kcadm.sh get clients -r sample-realm

-q--query)でクエリーを使ったり、-F--fields)で出力するフィールドを絞り込んだりもできます。

$ bin/kcadm.sh get clients -r sample-realm -q 'clientId=app-client' -F id,clientId
[ {
  "id" : "af3726af-4568-415b-94a9-ccde214a9ee6",
  "clientId" : "app-client"
} ]

特定のクライアントを取得する場合。

Server Administration Guide / Admin CLI / Client operations / Getting a specific client

$ bin/kcadm.sh get clients/[クライアントのID] -r [Realm名]

クライアントのIDはclientIdではなく、id`です。

例。

$ bin/kcadm.sh get clients/af3726af-4568-415b-94a9-ccde214a9ee6 -r sample-realm

取得項目の絞り込みができるのも同じですね。

$ bin/kcadm.sh get clients/af3726af-4568-415b-94a9-ccde214a9ee6 -r sample-realm -F id,clientId
{
  "id" : "af3726af-4568-415b-94a9-ccde214a9ee6",
  "clientId" : "app-client"
}

クライアントシークレットの取得。

Server Administration Guide / Admin CLI / Client operations / Getting the current secret for a specific client

$ bin/kcadm.sh get clients/[クライアントのID]/client-secret -r [Realm名]

例。

$ bin/kcadm.sh get clients/af3726af-4568-415b-94a9-ccde214a9ee6/client-secret -r sample-realm
{
  "type" : "secret",
  "value" : "YN8T7VjjOY8wN0HPuzav2algDi0V1yTf"
}

新しいクライアントシークレットの作成や、クライアントシークレットの更新。

Server Administration Guide / Admin CLI / Client operations / Generate a new secret for a specific client

Server Administration Guide / Admin CLI / Client operations / Updating the current secret for a specific client

クライアントの更新。

Server Administration Guide / Admin CLI / Client operations / Updating a client

$ bin/kcadm.sh update clients/[クライアントのID] -r [Realm名] -s [名前=値] -s ...

例。

$ bin/kcadm.sh update clients/af3726af-4568-415b-94a9-ccde214a9ee6 -r sample-realm -s rootUrl=http://localhost:8080 -s 'redirectUris=["http://localhost:8080/login/oauth2/code/keycloak"]' -s 'attributes={"post.logout.redirect.uris" : "http://localhost:8080/welcome"}'

attributesのようなものは、「追加」扱いになるみたいですね。

$ bin/kcadm.sh get clients/af3726af-4568-415b-94a9-ccde214a9ee6 -r sample-realm

〜attributesのみ抜粋〜

  "attributes" : {
    "post.logout.redirect.uris" : "http://localhost:8080/welcome",
    "client.secret.creation.time" : "1663485100"
  },

rootUrl」やbaseUrl`など、作成時に指定していなかったものは管理CLIでクライアントの情報を取得しても、属性自体が表示されないものが
あります。Web UIでの更新時に見た目以上に更新が入るような感じもするので、更新対象の属性がわからなかったら1度Web UIで
更新して情報を見るとよいでしょうね。

クライアントの削除。

Server Administration Guide / Admin CLI / Client operations / Deleting a client

ロールを操作する

ロールの操作については、こちら。

Server Administration Guide / Admin CLI / Role operations

Realmロールとクライアントロールの2種類がありますが、今回はRealmロールのみを扱うことにします。

Realmロールの作成。

Server Administration Guide / Admin CLI / Role operations / Creating a realm role

$ bin/kcadm.sh create roles -r [Realm名] -s name=[Realmロール名]

例。

$ bin/kcadm.sh create roles -r sample-realm -s name=test-role

Realmロールの一覧。

Server Administration Guide / Admin CLI / Role operations / Listing realm roles

$ bin/kcadm.sh get roles -r [Realm名]

例。

$ bin/kcadm.sh get roles -r sample-realm

更新、削除。

Server Administration Guide / Admin CLI / Role operations / Updating a realm role

Server Administration Guide / Admin CLI / Role operations / Deleting a realm role

ユーザーを操作する

ユーザーの操作については、こちら。

Server Administration Guide / Admin CLI / User operations

ユーザーの作成。

Server Administration Guide / Admin CLI / User operations / Creating a user

$ bin/kcadm.sh create users -r [Realm名] -s username=[ユーザー名] -s enabled=true -s ...

例。

$ bin/kcadm.sh create users -r sample-realm -s username=test-user -s enabled=true
Created new user with id 'ff3ed0b7-15dc-4bd6-a07a-087f2ff2a64c'

ユーザーの一覧の取得。

Server Administration Guide / Admin CLI / User operations / Listing users

$ bin/kcadm.sh get users -r [Realm名]

例。

$ bin/kcadm.sh get users -r sample-realm

-qでクエリー、-Fで取得するフィールドの絞り込みができるのは、クライアントと変わりません。

$ bin/kcadm.sh get users -r sample-realm -q 'username=test-user' -F id,username
[ {
  "id" : "ff3ed0b7-15dc-4bd6-a07a-087f2ff2a64c",
  "username" : "test-user"
} ]

特定のユーザーを取得する。

Server Administration Guide / Admin CLI / User operations / Getting a specific user

$ bin/kcadm.sh get users/[ユーザーのID] -r [Realm名]

ユーザーのIDは、クライアントの時と同じくidなのでUUID指定になります。

例。

$ bin/kcadm.sh get users/ff3ed0b7-15dc-4bd6-a07a-087f2ff2a64c -r sample-realm

ユーザーの更新。

Server Administration Guide / Admin CLI / User operations / Updating a user

$ bin/kcadm.sh update users/[ユーザーのID] -r sample-realm -s [属性名=値] -s ...

例。

$ bin/kcadm.sh update users/ff3ed0b7-15dc-4bd6-a07a-087f2ff2a64c -r sample-realm -s firstName=カツオ -s lastName=磯野

ユーザーの削除。

Server Administration Guide / Admin CLI / User operations / Deleting a user

パスワードのリセット。

Server Administration Guide / Admin CLI / User operations / Resetting a user’s password

$ bin/kcadm.sh set-password -r [Realm名] --username [ユーザー名] --new-password [新しいパスワード] [--temporary]

例。

$ bin/kcadm.sh set-password -r sample-realm --username test-user --new-password password

--temporaryを付与すると、ログイン時に変更を求められる状態になります。

$ bin/kcadm.sh set-password -r sample-realm --username test-user --new-password password --temporary

ユーザーへのRealmロールの付与。

Server Administration Guide / Admin CLI / User operations / Adding realm roles to a user

$ bin/kcadm.sh add-roles -r [Realm名] --uusername [ユーザー名] --rolename [Realmロール名]

例。

$ bin/kcadm.sh add-roles -r sample-realm --uusername test-user --rolename test-role

ユーザーに付与されているRealmロールの確認。

Server Administration Guide / Admin CLI / User operations / Listing assigned, available, and effective realm roles for a user

$ bin/kcadm.sh get-roles -r [Real名] --uusername [ユーザー名]

例。

$ bin/kcadm.sh get-roles -r sample-realm --uusername test-user

ユーザーからのRealmロールの削除。

Server Administration Guide / Admin CLI / User operations / Removing realm roles from a user

$ bin/kcadm.sh remove-roles -r [Realm名] --uusername [ユーザー名] --rolename [Realmロール名]

例。

$ bin/kcadm.sh remove-roles -r sample-realm --uusername test-user --rolename test-role

いったん、こんなところで。

こうやって管理CLIを扱うと、いろいろと理解が深まって良いですね。復習にもなりました。