CLOVER🍀

That was when it all began.

Spring SecurityのOAuth 2.0サポートで、Keycloak 19.0のクライアントスコープを使った認可を試す

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

前に、Spring SecurityのOAuth 2.0サポートを使って認証を試してみました。

Spring SecurityのOAuth 2.0サポートでKeycloak 19.0を使った認証を試す - CLOVER🍀

今度は、クライアントスコープを使った認可を簡単に試してみたいと思います。

環境

今回の環境は、こちら。

$ java --version
openjdk 17.0.4 2022-07-19
OpenJDK Runtime Environment (build 17.0.4+8-Ubuntu-120.04)
OpenJDK 64-Bit Server VM (build 17.0.4+8-Ubuntu-120.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.6 (84538c9988a25aec085021c365c560670ad80f63)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 17.0.4, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-124-generic", arch: "amd64", family: "unix"

Keycloakは19.0.1を使い、172.17.0.2で動作しているものとします。

$ bin/kc.sh --version
Keycloak 19.0.1
JVM: 17.0.4 (Eclipse Adoptium OpenJDK 64-Bit Server VM 17.0.4+8)
OS: Linux 5.4.0-124-generic amd64

Keycloakの管理ユーザーは、起動時に作成。

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

お題

今回は、KeycloakのRealm内に作成したクライアントに対してクライアントスコープを付与し、アプリケーション側ではスコープの有無で
アクセス制御を行ってみたいと思います。

Keycloakの準備

まずは、Keycloakの準備をしておきます。

以下の情報、手順で作成していきます。

  • Realm
    • Realmの名前をsample-realmとして作成

特に、デフォルト設定から変更するところがわかるようにしたいと思います。

  • クライアント(Client)
    • 作成時
    • 作成後
      • Settingsタブ
        • Root URLをhttp://localhost:8080に設定
        • Valid redirect URIsにhttp://localhost:8080/*を設定
        • 保存

また、CredentialsタブのClient secretの値を控えておきます。

クライアントスコープ(Client Scope)は、この後に付与します。

  • ユーザー(User)
    • 作成時
      • Usernameをtest-userとして作成
    • 作成後
      • Credentialsタブ
        • パスワードを設定
        • TemporaryをOffに設定
        • 保存

クライアントスコープ(Client Scope)を作成する

次は、クライアントスコープを作成します。作成したクライアントスコープは、クライアントに付与することになります。

今回は、次の2つのスコープを作成することにします。

  • token
  • message

実際に付与するのは、tokenスコープだけにするのですが。

左メニューの「Client scopes」を選択して

「Create client scope」を選択して、クライアントスコープを作成します。

今回はNameとTypeのみを指定して、「Save」。

以下の情報で作成しました。

  • クライアントスコープ
    • token
      • Nameをtokenに設定
      • TypeをDefaultに設定
    • message
      • Nameをmessageに設定
      • TypeをDefaultに設定

ちなみに、ここでの「Type」は以下の3種類から選択できます。

  • Default … 新しくクライアントを作成した際に、自動で「Default」Typeとして付与されるスコープ
  • Optional … 新しくクライアントを作成した際に、自動で「Optional」Typeとして付与されるスコープ
  • None … 新しくクライアントを作成した際に、自動では付与されないスコープ
    • クライアントのスコープの紐づけを行うページで、明示的に付与することは可能

次に、クライアントの「Client scopes」タブを選択して

「Add client scope」を押すと、以下のような表示になるので、付与するスコープを選択。

この時、Typeを「Default」と「Optional」から選択できます。

ちなみに、今回はクライアントスコープを作成する際にTypeを「Default」にしていました。つまり、クライアントとクライアントスコープを
作成する順番を逆にすると、作成されたクライアントにはこれらのスコープは自動的に付与されていたことになります。

今回はmesageというスコープについては紐付けないことにしました。

Typeは付与した後でも変更可能ですが。

ところで、ここまで「Default」と「Optional」の意味そのものは説明してきませんでした。

クライアントスコープについては、以下にまとまっています。

Server Administration Guide / Managing OpenID Connect and SAML Clients / Client scopes

「Default」と「Optional」の違いは、以下に書かれています。

Server Administration Guide / Managing OpenID Connect and SAML Clients / Client scopes / Link client scope with the client

それぞれ、以下のようなものになっています。

  • Default Client Scopes … OpenID ConnectおよびSAMLクライアントに適用され、OpenID Connectの場合は認可リクエストのscopeパラメーターの値に関係なく付与される
    • The client will inherit Protocol Mappers and Role Scope Mappings that are defined on the client scope. For the OpenID Connect Protocol, the Mappers and Role Scope Mappings are always applied, regardless of the value used for the scope parameter in the OpenID Connect authorization request.
  • Optional Client Scopes … OpenID Connectクライアントのみのもので、OpenID Connectの認可リクエストのscopeパラメーターで要求された場合のみ適用される
    • Optional client scopes are applied when issuing tokens for this client but only when requested by the scope parameter in the OpenID Connect authorization request.

つまり、ログイン時にユーザーからスコープの利用を許可された場合に付与されるのがOptional Client Scopeで、ユーザーからの許可に関係なく
付与されるのがDefault Client Scopeということになります。

この「許可する」という話ですが、ログイン時にユーザーが選択することになります。この表示を行うには、クライアントの「Login settings」で
以下の設定を行う必要があります。

  • Consent required … On
  • Display client on screen … On
  • Client consent screen text … スコープの許可確認を求める際に、ユーザーに表示する文字列

デフォルトは無効になっているので、ユーザーのログイン時に確認画面は表示されません。

今回は、このままいきます。

Spring Bootプロジェクトを作成する

では、アプリケーションを作成していきましょう。依存関係にweboauth2-clientを指定して、プロジェクトを作成。

$ curl -s https://start.spring.io/starter.tgz \
  -d bootVersion=2.7.3 \
  -d javaVersion=17 \
  -d name=spring-security-oauth2-client-scoped-authz-example \
  -d groupId=org.littlewings \
  -d artifactId=spring-security-oauth2-client-scoped-authz-example \
  -d version=0.0.1-SNAPSHOT \
  -d packageName=org.littlewings.keycloak.spring \
  -d dependencies=web,oauth2-client \
  -d baseDir=spring-security-oauth2-client-scoped-authz-example | tar zxvf -

ディレクトリ内へ移動。

$ cd spring-security-oauth2-client-scoped-authz-example

Mavenプラグインや依存関係など。

        <properties>
                <java.version>17</java.version>
        </properties>
        <dependencies>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-oauth2-client</artifactId>
                </dependency>
                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-web</artifactId>
                </dependency>

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

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

自動生成されたソースコードは、削除しておきます。

$ rm src/main/java/org/littlewings/keycloak/spring/SpringSecurityOauth2ClientScopedAuthzExampleApplication.java src/test/java/org/littlewings/keycloak/spring/SpringSecurityOauth2ClientScopedAuthzExampleApplicationTests.java

ソースコードの作成。

Spring SecurityのOAuth 2.0に関する設定。

src/main/java/org/littlewings/keycloak/spring/OAuth2ClientSecurityConfig.java

package org.littlewings.keycloak.spring;

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@EnableWebSecurity
public class OAuth2ClientSecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(authorize -> authorize
                        .mvcMatchers("/auth/**").authenticated()
                        .mvcMatchers("/scope/message").hasAuthority("SCOPE_message")
                        .mvcMatchers("/scope/token").hasAuthority("SCOPE_token")
                        .anyRequest().permitAll())
                .oauth2Login(Customizer.withDefaults())
                .logout(logout -> logout
                        .logoutUrl("/logout")
                        .logoutSuccessUrl("/"));

        return http.build();
    }
}

/auth配下は認証必須、/scope/messagemessageスコープを権限として要求し、/scope/tokenは同じくtokenスコープを要求します。
それ以外のパスは、認証なしでアクセスできるようにしています。

               .authorizeHttpRequests(authorize -> authorize
                        .mvcMatchers("/auth/**").authenticated()
                        .mvcMatchers("/scope/message").hasAuthority("SCOPE_message")
                        .mvcMatchers("/scope/token").hasAuthority("SCOPE_token")
                        .anyRequest().permitAll())

Authorize HttpServletRequests with AuthorizationFilter :: Spring Security

ここで指定しているスコープは、先ほど作成したクライアントスコープですね。

また、今回はhasAuthorityを使って確認することにします。hasRoleではなく、です。

Authorization Architecture :: Spring Security

Authorize HttpServletRequest with FilterSecurityInterceptor :: Spring Security

hasRoleは、ROLE_を接頭辞とするものですね。

RestController。

src/main/java/org/littlewings/keycloak/spring/ScopedController.java

package org.littlewings.keycloak.spring;

import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ScopedController {
    @GetMapping
    public String index() {
        return "Hello, Spring Security Application!!";
    }

    @GetMapping("token")
    public Object token(OAuth2AuthenticationToken authenticationToken) {
        return authenticationToken != null ? authenticationToken : "not login";
    }

    @GetMapping("auth/message")
    public String authenticatedMessage() {
        return "Authenticated!!";
    }

    @GetMapping("scope/message")
    public String authorizedMessage() {
        return "Authorized!!";
    }


    @GetMapping("scope/token")
    public OAuth2AuthenticationToken authorizedToken(OAuth2AuthenticationToken authenticationToken) {
        return authenticationToken;
    }
}

以下の3つのメソッドは、それぞれ認証必須、messageスコープ、tokenスコープを要求するメソッドです。

    @GetMapping("auth/message")
    public String authenticatedMessage() {
        return "Authenticated!!";
    }

    @GetMapping("scope/message")
    public String authorizedMessage() {
        return "Authorized!!";
    }


    @GetMapping("scope/token")
    public OAuth2AuthenticationToken authorizedToken(OAuth2AuthenticationToken authenticationToken) {
        return authenticationToken;
    }

それ以外は、ログインしていなくてもアクセス可能です。

OAuth2AuthenticationTokenクラスは、Spring SecurityのOAuth 2.0サポートにおける認証情報です。

mainクラス。

src/main/java/org/littlewings/keycloak/spring/App.java

package org.littlewings.keycloak.spring;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

設定。

src/main/resources/application.properties

spring.security.oauth2.client.registration.keycloak.client-id=spring-security-client
spring.security.oauth2.client.registration.keycloak.client-secret=UPGaDF2h2CIAbWE4dCv0CK36bfkiXrLq
spring.security.oauth2.client.registration.keycloak.client-name=spring security oauth2 authorization example
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.keycloak.scope=openid
spring.security.oauth2.client.registration.keycloak.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}

spring.security.oauth2.client.provider.keycloak.authorization-uri=http://172.17.0.2:8080/realms/sample-realm/protocol/openid-connect/auth
spring.security.oauth2.client.provider.keycloak.token-uri=http://172.17.0.2:8080/realms/sample-realm/protocol/openid-connect/token
spring.security.oauth2.client.provider.keycloak.user-info-uri=http://172.17.0.2:8080/realms/sample-realm/protocol/openid-connect/userinfo
spring.security.oauth2.client.provider.keycloak.jwk-set-uri=http://172.17.0.2:8080/realms/sample-realm/protocol/openid-connect/certs
spring.security.oauth2.client.provider.keycloak.user-name-attribute=preferred_username

spring.jackson.serialization.indent_output=true
#logging.level.org.springframework.security=DEBUG

OAuth 2.0のフローとしては、認可コードフローを使います。

動作確認してみる

それでは、動作確認してみます。

アプリケーションを起動。

$ mvn spring-boot:run

まずはログインしないまま、http://localhost:8080/にアクセスしてみます。

http://localhost:8080/tokenにアクセス。

ログインを必要とするhttp://localhost:8080/auth/messageにアクセスしてみます。

すると、Keycloakのログインページにリダイレクトされるので、先ほど作成したユーザー名とパスワード入力してログイン。

ログインすると、http://localhost:8080/auth/messageにリダイレクトしてきて今度はページが表示されます。

http://localhost:8080/tokenにアクセスすると、今度は認証情報が表示されます。

authoritySCOPE_tokenも含まれています。

次は認可を確認します。tokenスコープが必要なhttp://localhost:8080/scope/tokenにアクセスしてみます。

こちらはスコープが付与されているので表示されました。

messageスコープが必要なhttp://localhost:8080/scope/messageにアクセスしてみます。

こちらはスコープが保持されていないので、403になりましたね。

OKそうです。

なお、ここでの動作確認には載せていませんが、未ログイン状態で認可が必要なページにアクセスしてもKeycloakのログインページに
リダイレクトされ、ログインに成功すると戻ってきます。実際にアクセス可能かどうかは、持っているスコープ次第ですが。

クライアントスコープが実際どう返ってきているのか確認する

ところで、クライアントスコープにSCOPE_というprefixが付いているのがちょっと気になり。実際どうなっているのか、tcpdum
キャプチャしてみました。

$ sudo tcpdump -i any host 172.17.0.2 and tcp port 8080 -A

こちらのトークンエンドポイントのレスポンスを見てみます。

http://172.17.0.2:8080/realms/sample-realm/protocol/openid-connect/token

こんなレスポンスになっていました。

{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI1cUEzNGNEeFM5VWcxV2ZpY0FiUU01RFU5NWk5T1BBeU5ocUNqT2d2QzYwIn0.eyJleHAiOjE2NjE1MjQ0NTQsImlhdCI6MTY2MTUyNDE1NCwiYXV0aF90aW1lIjoxNjYxNTI0MTU0LCJqdGkiOiJkMGFhNWY2NC05OTUwLTQyZGEtOGRmNC04ZTE1MWM4YjQ0YWIiLCJpc3MiOiJodHRwOi8vMTcyLjE3LjAuMjo4MDgwL3JlYWxtcy9zYW1wbGUtcmVhbG0iLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiNjMxZDliODgtM2U1Mi00M2M4LThjZGQtZmUzOWU2MGUzMzdkIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoic3ByaW5nLXNlY3VyaXR5LWNsaWVudCIsIm5vbmNlIjoieWNNLWhfX2JzNEE2SS1oa3Bsd3VFNTFQRmlFU1R2RXBUTTBSTUR0WkVGYyIsInNlc3Npb25fc3RhdGUiOiI3ZjI4ZTQwMy05YTcwLTRjNjUtYjIwOS04ZjFmN2I4NWE4OGUiLCJhY3IiOiIxIiwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtc2FtcGxlLXJlYWxtIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHRva2VuIHByb2ZpbGUiLCJzaWQiOiI3ZjI4ZTQwMy05YTcwLTRjNjUtYjIwOS04ZjFmN2I4NWE4OGUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InRlc3QtdXNlciIsImdpdmVuX25hbWUiOiIiLCJmYW1pbHlfbmFtZSI6IiJ9.fXdutTgL4l9mLLB0jLXs-r2LJGRCeL63B67fefN32hBHthFR7DunhK6qYmbBQovnmqboNJgVdDBedhO81z1wiJnIFHloTfTbbA2KFds9b1X5pttC8IXh0JVMixovMajOvUVVaG-E2AJ-Ip1W3eNLH1OohpfJnix8TE-to-fNi3IvadkPn-gneeKq16Vw6cjK3bll6whcrnSWevlAAA7Z9PM1hZU6-j0EZxRWT8dGgpRbAQnYBAYQb8CN6q-J0DtHKcZ4kppEF_awb12yx5ZhBPKYqV_-MmxqtGwu-yXSNGu4x2gR1g3a2P03AljyQzIazL3MfTYAzypNqhhPurhJNQ","expires_in":300,"refresh_expires_in":1800,"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI2MTFhODMxNi1jMjFjLTQzMDMtYWMzZC1jOTQ0NzlhYzU3NTAifQ.eyJleHAiOjE2NjE1MjU5NTQsImlhdCI6MTY2MTUyNDE1NCwianRpIjoiZGQ2ODUxOTYtOGU3ZS00YmI4LWEyOTQtNzc0NGNlYzJkY2ZlIiwiaXNzIjoiaHR0cDovLzE3Mi4xNy4wLjI6ODA4MC9yZWFsbXMvc2FtcGxlLXJlYWxtIiwiYXVkIjoiaHR0cDovLzE3Mi4xNy4wLjI6ODA4MC9yZWFsbXMvc2FtcGxlLXJlYWxtIiwic3ViIjoiNjMxZDliODgtM2U1Mi00M2M4LThjZGQtZmUzOWU2MGUzMzdkIiwidHlwIjoiUmVmcmVzaCIsImF6cCI6InNwcmluZy1zZWN1cml0eS1jbGllbnQiLCJub25jZSI6InljTS1oX19iczRBNkktaGtwbHd1RTUxUEZpRVNUdkVwVE0wUk1EdFpFRmMiLCJzZXNzaW9uX3N0YXRlIjoiN2YyOGU0MDMtOWE3MC00YzY1LWIyMDktOGYxZjdiODVhODhlIiwic2NvcGUiOiJvcGVuaWQgZW1haWwgdG9rZW4gcHJvZmlsZSIsInNpZCI6IjdmMjhlNDAzLTlhNzAtNGM2NS1iMjA5LThmMWY3Yjg1YTg4ZSJ9.ba5RGn0RSCi6kuJ9N1VqaaesptUn8Np4JM2qNXgKvWA","token_type":"Bearer","id_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI1cUEzNGNEeFM5VWcxV2ZpY0FiUU01RFU5NWk5T1BBeU5ocUNqT2d2QzYwIn0.eyJleHAiOjE2NjE1MjQ0NTQsImlhdCI6MTY2MTUyNDE1NCwiYXV0aF90aW1lIjoxNjYxNTI0MTU0LCJqdGkiOiIwZDcxZTllYy1kZDgzLTQzM2YtOGVjYi1kM2U5ZTQzNDU5OTMiLCJpc3MiOiJodHRwOi8vMTcyLjE3LjAuMjo4MDgwL3JlYWxtcy9zYW1wbGUtcmVhbG0iLCJhdWQiOiJzcHJpbmctc2VjdXJpdHktY2xpZW50Iiwic3ViIjoiNjMxZDliODgtM2U1Mi00M2M4LThjZGQtZmUzOWU2MGUzMzdkIiwidHlwIjoiSUQiLCJhenAiOiJzcHJpbmctc2VjdXJpdHktY2xpZW50Iiwibm9uY2UiOiJ5Y00taF9fYnM0QTZJLWhrcGx3dUU1MVBGaUVTVHZFcFRNMFJNRHRaRUZjIiwic2Vzc2lvbl9zdGF0ZSI6IjdmMjhlNDAzLTlhNzAtNGM2NS1iMjA5LThmMWY3Yjg1YTg4ZSIsImF0X2hhc2giOiI0YVBFRnY4WGlSdlVtUmI2Zi1vaVNRIiwiYWNyIjoiMSIsInNpZCI6IjdmMjhlNDAzLTlhNzAtNGM2NS1iMjA5LThmMWY3Yjg1YTg4ZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoidGVzdC11c2VyIiwiZ2l2ZW5fbmFtZSI6IiIsImZhbWlseV9uYW1lIjoiIn0.CvIoWBK_u0_ANyRt7h_A_seuIqXq-LTRnXofnka5QrCNjOuPdEJkHhfJP55gPHVU3Sa3dWy7isEyJRz9EPUXFal26ZleW6KNZ8q-tPPIFNI67ZWtgJyTWvFyfWMn5TQf4Z86QhPiRrKDBGwR0looO_QBtyIncdt_u4Bj60CdJF9i995j21KmpPz2CYPgG9VK4ERLhIqqGM_1AyHSAZis4Laosh66rQ8A6nSIM2NgF3-DZOUpBdTpGzL9C7eM582vZQtrxjWAgWLNI6EvjLbGAq_7Zbzcwc_kkRzvyPgLZKAiVnm8W4nbfj4jnPzrlkxUKq2DUdqy1VS0UXCLV2WQxQ","not-before-policy":0,"session_state":"7f28e403-9a70-4c65-b209-8f1f7b85a88e","scope":"openid email token profile"}

よく見ると、スコープはこんな感じになっていますね。

"scope":"openid email token profile"

ということは、SCOPE_というprefixはKeycloakが付与しているものではなさそうです。

Spring Securityが付与しているようですね。

https://github.com/spring-projects/spring-security/blob/5.7.3/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserService.java#L133-L135

確認できて、ちょっとすっきりしました。

デバッグログを確認する

認可が上手くいかない時など、どのようなAuthorityを持っているか確認するには、Spring SecurityのログレベルをDEBUGレベルにすると
よさそうです。

application.propertiesを載せた時にはコメントアウトしていましたが、以下を加えると

logging.level.org.springframework.security=DEBUG

こんな感じでSpring Securityの認証情報がログに出力されるようになります。

2022-08-27 00:17:31.179 DEBUG 28134 --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy        : Securing GET /scope/token
2022-08-27 00:17:31.180 DEBUG 28134 --- [nio-8080-exec-5] w.c.HttpSessionSecurityContextRepository : Retrieved SecurityContextImpl [Authentication=OAuth2AuthenticationToken [Principal=Name: [test-user], Granted Authorities: [[ROLE_USER, SCOPE_email, SCOPE_openid, SCOPE_profile, SCOPE_token]], User Attributes: [{at_hash=--Y8b8Mx9YvzPWAs5u5Atw, sub=631d9b88-3e52-43c8-8cdd-fe39e60e337d, email_verified=false, iss=http://172.17.0.2:8080/realms/sample-realm, typ=ID, preferred_username=test-user, given_name=, nonce=4aLSKGdCQ8LSj0KSgEQ5rE0gRBEttlnmYbzTeR7HA1c, sid=aec8ef3e-7477-4976-a805-f7ef9a4edad5, aud=[spring-security-client], acr=0, azp=spring-security-client, auth_time=2022-08-26T15:09:23Z, exp=2022-08-26T15:22:21Z, session_state=aec8ef3e-7477-4976-a805-f7ef9a4edad5, family_name=, iat=2022-08-26T15:17:21Z, jti=5587dc3c-2b18-4fdb-92cb-ea0e592abc8f}], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=E2D9AB9DBD6197EFE41A8B60B2D827F5], Granted Authorities=[ROLE_USER, SCOPE_email, SCOPE_openid, SCOPE_profile, SCOPE_token]]]
2022-08-27 00:17:31.180 DEBUG 28134 --- [nio-8080-exec-5] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to SecurityContextImpl [Authentication=OAuth2AuthenticationToken [Principal=Name: [test-user], Granted Authorities: [[ROLE_USER, SCOPE_email, SCOPE_openid, SCOPE_profile, SCOPE_token]], User Attributes: [{at_hash=--Y8b8Mx9YvzPWAs5u5Atw, sub=631d9b88-3e52-43c8-8cdd-fe39e60e337d, email_verified=false, iss=http://172.17.0.2:8080/realms/sample-realm, typ=ID, preferred_username=test-user, given_name=, nonce=4aLSKGdCQ8LSj0KSgEQ5rE0gRBEttlnmYbzTeR7HA1c, sid=aec8ef3e-7477-4976-a805-f7ef9a4edad5, aud=[spring-security-client], acr=0, azp=spring-security-client, auth_time=2022-08-26T15:09:23Z, exp=2022-08-26T15:22:21Z, session_state=aec8ef3e-7477-4976-a805-f7ef9a4edad5, family_name=, iat=2022-08-26T15:17:21Z, jti=5587dc3c-2b18-4fdb-92cb-ea0e592abc8f}], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=E2D9AB9DBD6197EFE41A8B60B2D827F5], Granted Authorities=[ROLE_USER, SCOPE_email, SCOPE_openid, SCOPE_profile, SCOPE_token]]]
2022-08-27 00:17:31.182 DEBUG 28134 --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy        : Secured GET /scope/token
2022-08-27 00:17:31.257 DEBUG 28134 --- [nio-8080-exec-5] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request

これらの内容を確認すると、解決が早くなるかもしれません。

まとめ

Spring SecurityのOAuth 2.0サポートで、Keycloakのクライアントスコープを使った認可を試してみました。

こちらは特に問題なく、あっさりと確認できたのでまあ良いかな、と。

動きも予想通りでした。