ちょっと前に、KeycloakのClient Adapterを使って、Keycloakに対してOpenID Connectを使って連携するエントリをいくつか
書いてみました。
今度は、KeycloakのClient Adapterは使わず、もう少し汎用に近いライブラリでKeycloakに対してOpenID Connectを使って
連携してみたいと思います。
対象は、Spring Securityです。
参考にするのは、すでに存在するこちらのブログエントリ。
Spring Boot 1/2のアプリにKeycloakのOpenID Connectを使ってシングルサインオン
あと、こちらのサンプルコードも。
https://github.com/spring-projects/spring-boot/tree/v2.0.4.RELEASE/spring-boot-samples/spring-boot-sample-oauth2-client
と、これを案内しただけでは終われないので、自分でも試してみたいと思います。
環境
このエントリで使った環境は、こちら。
$ java -version openjdk version "1.8.0_181" OpenJDK Runtime Environment (build 1.8.0_181-8u181-b13-0ubuntu0.18.04.1-b13) OpenJDK 64-Bit Server VM (build 25.181-b13, mixed mode) $ mvn -version Apache Maven 3.5.4 (1edded0938998edf8bf061f1ceb3cfdeccf443fe; 2018-06-18T03:33:14+09:00) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 1.8.0_181, vendor: Oracle Corporation, runtime: /usr/lib/jvm/java-8-openjdk-amd64/jre Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "4.15.0-33-generic", arch: "amd64", family: "unix"
準備
Maven依存関係など、pom.xmlの主要な定義はこちら。
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <spring-boot.version>2.0.4.RELEASE</spring-boot.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-client</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-jose</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>${spring-boot.version}</version> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
Spring BootのWeb、SecurityのStarter、それからOAuth2のClientライブラリを加えているところがポイントです。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-client</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-jose</artifactId> </dependency>
Spring Boot 2.0/Spring Security 5.0でOAuth2を使用するには、これらのライブラリを追加する必要があります。
OAuth 2.0 Client - spring-security-oauth2-client.jar
OAuth 2.0 JOSE - spring-security-oauth2-jose.jar
Keycloakのバージョンは、4.3.0.Finalです。
Keycloakの管理アカウントは、以下のコマンドで作成。
$ bin/add-user-keycloak.sh -u keycloak-admin -p password ## Keycloak Server自体を再起動 $ bin/add-user.sh -u admin -p password $ bin/jboss-cli.sh -c -u=admin -p=password --command=reload
また、Keycloakには以下の情報でRealmおよびユーザーを作成しておきます。
ClientのCredentialsなどは、適宜取得します。…というか、Spring Securityの設定を行う時に、いろいろ出てきます。
サンプルコードを書く
それでは、サンプルコードを書いていきます。
Spring Securityの設定は、こちらで。
src/main/java/org/littlewings/keycloak/spring/SecurityConfig.java
package org.littlewings.keycloak.spring; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Value("${keycloak.logout-uri}") String logoutUri; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/secure/**").authenticated() .anyRequest().permitAll() .and() .oauth2Login() .and() .logout() .logoutSuccessUrl("/public/hello") .logoutRequestMatcher(new AntPathRequestMatcher("/logout")); // enable GET access } }
認証が必要なページにはOAuth2 Loginを行うようにし、その範囲は「/secure」配下としました。また、それ以外はアクセスフリーです。
設定は、このあたりを参考に。
OAuth 2.0 Login / Provide a WebSecurityConfigurerAdapter
ログアウト後はログイン不要なURLに戻ってくるようにして、ログアウト用のURLは今回はGETアクセスを許容(CSRF対策オフ)。
続いて、Controller。
未ログインでも、アクセス可能なControllerを作成。
src/main/java/org/littlewings/keycloak/spring/controller/PublicController.java
package org.littlewings.keycloak.spring.controller; import java.util.Collections; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("public") public class PublicController { @GetMapping("hello") public String hello() { return "Hello Application!!"; } @GetMapping("user") public Object user(OAuth2AuthenticationToken authenticationToken) { return authenticationToken != null ? authenticationToken : Collections.emptyMap(); } }
ログイン必須ではないControllerでも、ログインしていればユーザーの情報が取得できるようです。ここでは、ログインしているユーザーの情報は、
OAuth2AuthenticationTokenを使って受け取ります。
未ログインの時はOAuth2AuthenticationTokenは渡されない(nullになる)ので、そこは考慮の上ということで。
設定上、ログイン必須にしているControllerは、こちら。
src/main/java/org/littlewings/keycloak/spring/controller/SecureController.java
package org.littlewings.keycloak.spring.controller; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("secure") public class SecureController { @GetMapping("hello") public String hello() { return "Hello Secure Application!!"; } @GetMapping("user") public Object user(OAuth2AuthenticationToken authenticationToken) { return authenticationToken; } }
こちらでのOAuth2AuthenticationTokenの受け取りは、ログインが前提になっているので特にnullの確認は行わずそのままレスポンスとします。
起動クラスは、こちら。
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=sample-rest-api spring.security.oauth2.client.registration.keycloak.client-secret=19d00092-8897-41c0-ac41-1847151797b4 spring.security.oauth2.client.registration.keycloak.client-name=keycloak client example spring.security.oauth2.client.registration.keycloak.provider=keycloak spring.security.oauth2.client.registration.keycloak.scope=openid spring.security.oauth2.client.registration.keycloak.redirect-uri-template={baseUrl}/login/oauth2/code/{registrationId} spring.security.oauth2.client.registration.keycloak.client-authentication-method=basic spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code spring.security.oauth2.client.provider.keycloak.authorization-uri=http://172.17.0.2:8080/auth/realms/demo-api/protocol/openid-connect/auth spring.security.oauth2.client.provider.keycloak.token-uri=http://172.17.0.2:8080/auth/realms/demo-api/protocol/openid-connect/token spring.security.oauth2.client.provider.keycloak.user-info-uri=http://172.17.0.2:8080/auth/realms/demo-api/protocol/openid-connect/userinfo spring.security.oauth2.client.provider.keycloak.jwk-set-uri=http://172.17.0.2:8080/auth/realms/demo-api/protocol/openid-connect/certs spring.security.oauth2.client.provider.keycloak.user-name-attribute=preferred_username spring.jackson.serialization.indent_output=true
最後の以下の部分は、ControllerのレスポンスとなるJSONをフォーマットするためのものですね。
spring.jackson.serialization.indent_output=true
「spring.security.oauth2.client.registration.〜」および「spring.security.oauth2.client.provider.〜」に関する説明は、以下を参照します。
OAuth 2.0 Login / Setting the redirect URI
OAuth 2.0 Login / ClientRegistration
OAuth 2.0 Login / Spring Boot 2.0 Property Mappings
「spring.security.oauth2.client.registration.[registrationId].〜」については、好きなregistrationIdを指定して設定します。
- client-id … Keycloakで登録したClient ID
- client-secret … Keycloakから取得したClientのSecret
- client-name … 任意の値(ログイン先の選択時に表示)
- provider … 次の「spring.security.oauth2.client.provider.[provider].〜」で使うproviderの名前
- scope … openid、email、profileから選択
- redirect-uri-template … ログイン後に、認証サーバー(Keycloak)からリダイレクトするURL。通常、「{baseUrl}/login/oauth2/code/{registrationId}」でOK
- client-authentication-method … basicかpostを指定
- authorization-grant-type … OAuth 2.0でのAutorization Grantを指定。authorization_codeまたはimplicit
そして、「spring.security.oauth2.client.provider.[provider].〜」ではKeycloakへのエンドポイントの設定を行っていきます。
各種URLは、以下を見て設定していきます。
Other OpenID Connect Libraries
KeycloakのClient Adapterを使わない場合に設定する、各種エンドポイントの設定です。
- authorization-uri … Authorization Endpointを指定
- token-uri … Token Endpointを指定
- user-info-uri … Userinfo Endpointを指定
- jwk-set-uri … Certificate Endpointを指定
- user-name-attribute … Userinfo Responseの内容のうち、ユーザーの名前に該当する属性を指定。今回は「preferred_username」を設定
これで準備完了です。
確認
パッケージングして
$ mvn package
起動。
$ java -jar target/spring-security-example-0.0.1-SNAPSHOT.jar
書いてから気づいた、名前が…。
ログイン不要なURLにアクセス。
http://localhost:8080/public/hello
http://localhost:8080/public/user
ユーザー情報を表示する方は、まだなにも出ません。
次に、ログインが必要なページにアクセス。
http://localhost:8080/secure/hello
すると、認証先の選択画面が現れます。といっても、今回はKeycloak用に作成したもののみですが。
リンク先に行くと、Keycloakのログイン画面が表示されるので、ここでログイン。
ユーザー情報も表示できます。
http://localhost:8080/secure/user
また、ログイン不要なページの方でもユーザー情報が取得できるようになっています。
http://localhost:8080/public/user
最後、ログアウト。
http://localhost:8080/logout
再度ログインが必要なページにアクセスすると、またこちらのページが表示されます。
この状態だとKeycloak自体からはログアウトしていないので、リンク先に行くとすぐにログインが必要なページが表示されます。
Keycloak自体も合わせてログアウトしたい場合は、ログアウト用のエンドポイントを呼び出すように組み込むんでしょうねぇ…。