これは、なにをしたくて書いたもの?
先日、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
として作成
- Realmの名前を
特に、デフォルト設定から変更するところがわかるようにしたいと思います。
- Client
また、CredentialsタブのClient secretの値を控えておきます。
- User
- 作成時
- Usernameを
test-user
として作成
- Usernameを
- 作成後
- Credentialsタブ
- パスワードを設定
- TemporaryをOffに設定
- 保存
- Credentialsタブ
- 作成時
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
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を使って、認証だけ試してみました。
認可やログアウトなどもっといろいろ見たいのですが、ちょっと大変になりそうだったので少しずつ見ていくことにしました。
ゆっくりやっていきましょう。