CLOVER🍀

That was when it all began.

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

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

先日、WildFlyベースからQuarkusベースになったKeycloakをインストールしてKeycloak自体にログインするところまでやってみました。

Ubuntu Linux 20.04 LTSにKeycloak 19.0をインストールする - CLOVER🍀

今回は、Spring SecurityのOAuth 2.0サポートを使ってKeycloakと連携してログインまで行ってみます。

環境

今回の環境は、こちら。

$ 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のバージョンはこちらで、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_ADMIN=admin KEYCLOAK_ADMIN_PASSWORD=password bin/kc.sh start-dev

お題

タイトルにも書いているとおりですが、Spring Securityを使って作成したアプリケーションのログインの確認のみを行います。
この時、ログインにはKeycloakを使います。

今回は認証のみで、認可には触れないことにします。また、ログアウトにも触れません。

Keycloakの準備

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

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

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

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

  • Client
    • 作成時
    • 作成後
      • Settingsタブ
        • Root URLをhttp://localhost:8080に設定
        • Valid redirect URIsにhttp://localhost:8080/*を設定
        • 保存

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

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

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

次は、アプリケーションを作成していきます。

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

プロジェクト内に移動。

$ cd spring-security-oauth2-client-login-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>

spring-boot-starter-oauth2-clientが含まれているのがポイントです。

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

$ rm src/main/java/org/littlewings/keycloak/spring/SpringSecurityOauth2ClientLoginExampleApplication.java src/test/java/org/littlewings/keycloak/spring/SpringSecurityOauth2ClientLoginExampleApplicationTests.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("/secure/**").authenticated()
                        .anyRequest().permitAll())
                .oauth2Login(Customizer.withDefaults())
                .logout(logout -> logout
                        .logoutUrl("/logout")
                        .logoutSuccessUrl("/"));

        return http.build();
    }
}

このあたりを参考に設定しました。

Authorize HttpServletRequests with AuthorizationFilter :: Spring Security

OAuth2 :: Spring Security

OAuth 2.0 Login :: Spring Security

Core Configuration :: Spring Security

/secure配下のURLは、ログイン必須とします。

RestController。

src/main/java/org/littlewings/keycloak/spring/SampleController.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 SampleController {
    @GetMapping
    public String index() {
        return "Hello, Spring Security Application!!";
    }

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

    @GetMapping("secure/token")
    public OAuth2AuthenticationToken secureToken(OAuth2AuthenticationToken authenticationToken) {
        return authenticationToken;
    }
}

secureをパスに含まないエンドポイントは、ログインしていなくてもアクセスできることを前提にしています。

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

OAuth2AuthenticationToken (spring-security-docs 5.7.2 API)

今回は、こちらをそのままクライアントに返すようにしています。

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=A3PoFLyrslGxxErrqJ4NALqpdOPsMmf8
spring.security.oauth2.client.registration.keycloak.client-name=spring security oauth2 login 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

Spring Bootのドキュメントには、このあたりの情報はなさそうです。

spring.security.oauth2.client.provider. OAuth provider details. spring.security.oauth2.client.registration. OAuth client registrations.

Common Application Properties / Security Properties

OAuth 2.0のフローについては、認可コードフローを使うこととしてこのあたりを見つつ設定しました。

Authorization Grant Support :: Spring Security

Authorization Grant Support / Authorization Code

Web Application Security / / client-registration

Web Application Security / provider

Keycloakのエンドポイントは、こちらに記載があります。

Server Administration Guide / SSO protocols / OpenID Connect / Keycloak server OIDC URI endpoints

動作確認してみる

準備ができたので、動作確認してみましょう。

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

$ mvn spring-boot:run

http://localhost:8080/にアクセスしてみます。

http://localhost:8080/tokenにアクセスしてみます。

いずれも、ログインしていなくてもアクセスできます。後者は、情報が出力されませんが。

http://localhost:8080/secure/tokenにアクセスすると、ログインが必要なのでKeycloakにリダイレクトされます。

ユーザー名とパスワードを入力して、ログイン。

ログインが成功すると、http://localhost:8080/secure/tokenにリダイレクトされ、今度はユーザーの情報などが表示されます。

ログインしているので、http://localhost:8080/tokenにアクセスすると先ほどと挙動が変わり、http://localhost:8080/secure/tokenと同じ内容が
表示されます。

OKですね。

ちなみに、今の設定でhttp://localhost:8080/logoutにアクセスするとログアウト画面に遷移しますが、

ここでログアウトしてもアプリケーション側からログインするだけなので、http://localhost:8080/secure/tokenにアクセスするとKeycloakに
リダイレクトしつつKeycloak側ではログインしたままなので即座にログイン済みの状態になります。

このあたりは、また今度追うとしましょう。

まとめ

Spring SecurityのOAuth 2.0サポートとKeycloakを使って、認証だけ試してみました。

認可やログアウトなどもっといろいろ見たいのですが、ちょっと大変になりそうだったので少しずつ見ていくことにしました。

ゆっくりやっていきましょう。