1. 概要

Keycloakは、RedHatによって管理され、JBossによってJavaで開発されたオープンソースのIDおよびアクセス管理ソリューションです。

このチュートリアルでは、Spring Bootアプリケーションに組み込まれたKeycloakサーバーをセットアップする方法を学習します。 これにより、事前設定されたKeycloakサーバーを簡単に起動できます。

Keycloakはスタンドアロンサーバーとして実行することもできますが、その場合は、Keycloakをダウンロードして管理コンソールからセットアップする必要があります。

2. Keycloakの事前設定

まず、Keycloakサーバーを事前設定する方法を理解しましょう。

サーバーには一連のレルムが含まれており、各レルムはユーザー管理用の独立したユニットとして機能します。 事前設定するには、JSON形式のレルム定義ファイルを指定する必要があります。

Keycloak Admin Console を使用して構成できるものはすべて、このJSONに保持されます。

承認サーバーは、baeldung-realm.jsonで事前構成されます。 ファイル内のいくつかの関連する構成を見てみましょう。

  • users :デフォルトのユーザーは[email protected]および[email protected]です。 ここにもクレデンシャルがあります
  • clients :ID newClientでクライアントを定義します
  • standardFlowEnabled :trueに設定すると、newClientの認証コードフローがアクティブになります。
  • redirectUris :認証が成功した後にサーバーがリダイレクトするnewClientのURLがここに一覧表示されます
  • webOrigins “ +” に設定すると、redirectUrisとしてリストされているすべてのURLのCORSサポートが可能になります。

KeycloakサーバーはデフォルトでJWTトークンを発行するため、そのために個別の構成は必要ありません。 次に、Mavenの構成を見てみましょう。

3. Maven構成

KeycloakはSpring Bootアプリケーション内に埋め込まれるため、個別にダウンロードする必要はありません。

代わりに、次の一連の依存関係を設定します。

<dependency>
    <groupId>org.springframework.boot</groupId>        
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>
       

ここではSpring Bootの2.6.7バージョンを使用していることに注意してください。 永続性のために、依存関係spring-boot-starter-data-jpaおよびH2が追加されました。 その他のspringframework.bootの依存関係は、Webサポート用です。これは、Keycloak認証サーバーと管理コンソールをWebサービスとして実行できる必要があるためです。

KeycloakとRESTEasyにもいくつかの依存関係が必要です。

<dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>resteasy-jackson2-provider</artifactId>
    <version>3.15.1.Final</version>
</dependency>

<dependency>
    <groupId>org.keycloak</groupId>
    <artifactId>keycloak-dependencies-server-all</artifactId>
    <version>18.0.0</version>
    <type>pom</type>
</dependency> 

KeycloakおよびRESTEasyの最新バージョンについては、Mavenサイトを確認してください。

そして最後に、 プロパティ、Spring Bootで定義されたバージョンの代わりにKeycloakで宣言されたバージョンを使用するには:

<properties>
    <infinispan.version>13.0.8.Final</infinispan.version>
</properties>

4.4。 組み込みKeycloak構成

次に、承認サーバーのSpring構成を定義しましょう。

@Configuration
public class EmbeddedKeycloakConfig {

    @Bean
    ServletRegistrationBean keycloakJaxRsApplication(
      KeycloakServerProperties keycloakServerProperties, DataSource dataSource) throws Exception {
        
        mockJndiEnvironment(dataSource);
        EmbeddedKeycloakApplication.keycloakServerProperties = keycloakServerProperties;
        ServletRegistrationBean servlet = new ServletRegistrationBean<>(
          new HttpServlet30Dispatcher());
        servlet.addInitParameter("javax.ws.rs.Application", 
          EmbeddedKeycloakApplication.class.getName());
        servlet.addInitParameter(ResteasyContextParameters.RESTEASY_SERVLET_MAPPING_PREFIX,
          keycloakServerProperties.getContextPath());
        servlet.addInitParameter(ResteasyContextParameters.RESTEASY_USE_CONTAINER_FORM_PARAMS, 
          "true");
        servlet.addUrlMappings(keycloakServerProperties.getContextPath() + "/*");
        servlet.setLoadOnStartup(1);
        servlet.setAsyncSupported(true);
        return servlet;
    }

    @Bean
    FilterRegistrationBean keycloakSessionManagement(
      KeycloakServerProperties keycloakServerProperties) {
        FilterRegistrationBean filter = new FilterRegistrationBean<>();
	filter.setName("Keycloak Session Management");
	filter.setFilter(new EmbeddedKeycloakRequestFilter());
	filter.addUrlPatterns(keycloakServerProperties.getContextPath() + "/*");

	return filter;
    }

    private void mockJndiEnvironment(DataSource dataSource) throws NamingException {		 
        NamingManager.setInitialContextFactoryBuilder(
          (env) -> (environment) -> new InitialContext() {
            @Override
            public Object lookup(Name name) {
                return lookup(name.toString());
            }
	
            @Override
            public Object lookup(String name) {
                if ("spring/datasource".equals(name)) {
                    return dataSource;
                } else if (name.startsWith("java:jboss/ee/concurrency/executor/")) {
                    return fixedThreadPool();
                }
                return null;
            }

            @Override
            public NameParser getNameParser(String name) {
                return CompositeName::new;
            }

            @Override
            public void close() {
            }
        });
    }
     
    @Bean("fixedThreadPool")
    public ExecutorService fixedThreadPool() {
        return Executors.newFixedThreadPool(5);
    }
     
    @Bean
    @ConditionalOnMissingBean(name = "springBootPlatform")
    protected SimplePlatformProvider springBootPlatform() {
        return (SimplePlatformProvider) Platform.getPlatform();
    }
}

注:コンパイルエラーについて心配する必要はありません。後でEmbeddedKeycloakRequestFilterクラスを定義します。

ここでわかるように、最初に、レルム定義ファイルで指定されているように、Keycloakプロパティを永続的に保存するためにKeycloakServerPropertiesを使用してKeycloakをJAX-RSアプリケーションとして構成しました。 次に、セッション管理フィルターを追加し、JNDI環境をモックして、メモリ内のH2データベースである spring /datasourceを使用しました。

5. KeycloakServerProperties

次に、先ほど説明したKeycloakServerPropertiesを見てみましょう。

@ConfigurationProperties(prefix = "keycloak.server")
public class KeycloakServerProperties {
    String contextPath = "/auth";
    String realmImportFile = "baeldung-realm.json";
    AdminUser adminUser = new AdminUser();

    // getters and setters

    public static class AdminUser {
        String username = "admin";
        String password = "admin";

        // getters and setters        
    }
}

ご覧のとおり、これはcontextPath、adminUser、およびレルム定義ファイルを設定するための単純なPOJOです。

6. EmbeddedKeycloakApplication

次に、前に設定した構成を使用してレルムを作成するクラスを見てみましょう。

public class EmbeddedKeycloakApplication extends KeycloakApplication {
    private static final Logger LOG = LoggerFactory.getLogger(EmbeddedKeycloakApplication.class);
    static KeycloakServerProperties keycloakServerProperties;

    protected void loadConfig() {
        JsonConfigProviderFactory factory = new RegularJsonConfigProviderFactory();
        Config.init(factory.create()
          .orElseThrow(() -> new NoSuchElementException("No value present")));
    }
     
    @Override
    protected ExportImportManager bootstrap() {
        final ExportImportManager exportImportManager = super.bootstrap();
        createMasterRealmAdminUser();
        createBaeldungRealm();
        return exportImportManager;
    }

    private void createMasterRealmAdminUser() {
        KeycloakSession session = getSessionFactory().create();
        ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session);
        AdminUser admin = keycloakServerProperties.getAdminUser();
        try {
            session.getTransactionManager().begin();
            applianceBootstrap.createMasterRealmUser(admin.getUsername(), admin.getPassword());
            session.getTransactionManager().commit();
        } catch (Exception ex) {
            LOG.warn("Couldn't create keycloak master admin user: {}", ex.getMessage());
            session.getTransactionManager().rollback();
        }
        session.close();
    }

    private void createBaeldungRealm() {
        KeycloakSession session = getSessionFactory().create();
        try {
            session.getTransactionManager().begin();
            RealmManager manager = new RealmManager(session);
            Resource lessonRealmImportFile = new ClassPathResource(
              keycloakServerProperties.getRealmImportFile());
            manager.importRealm(JsonSerialization.readValue(lessonRealmImportFile.getInputStream(),
              RealmRepresentation.class));
            session.getTransactionManager().commit();
        } catch (Exception ex) {
            LOG.warn("Failed to import Realm json file: {}", ex.getMessage());
            session.getTransactionManager().rollback();
        }
        session.close();
    }
}

7. カスタムプラットフォームの実装

すでに述べたように、KeycloakはRedHat/JBossによって開発されています。 したがって、Wildflyサーバーに、またはQuarkusソリューションとしてアプリケーションをデプロイするための機能および拡張ライブラリーを提供します。

この場合、これらの選択肢から離れ、その結果、一部のプラットフォーム固有のインターフェイスとクラスにカスタム実装を提供する必要があります。

たとえば、 EmbeddedKeycloakApplication では、抽象 JsonConfigProviderFactory の空のサブクラスを使用して、Keycloakのサーバー構成keycloak-server.jsonを最初にロードしました。

public class RegularJsonConfigProviderFactory extends JsonConfigProviderFactory { }

次に、 KeycloakApplication を拡張して、masterbaeldungの2つのレルムを作成しました。 これらは、レルム定義ファイルbaeldung-realm.jsonで指定されたプロパティに従って作成されます。

ご覧のとおり、 KeycloakSession を使用してすべてのトランザクションを実行します。これを正しく機能させるには、カスタムの AbstractRequestFilter EmbeddedKeycloakRequestFilter )を作成する必要がありました。 EmbeddedKeycloakConfigファイルのKeycloakSessionServletFilterを使用して、このためのbeanを設定します。

さらに、org.keycloak.common.util.ResteasyProviderとorg.keycloak.platform.PlatformProvider の独自の実装を持ち、外部の依存関係に依存しないように、いくつかのカスタムプロバイダーが必要です。

重要なのは、これらのカスタムプロバイダーに関する情報をプロジェクトの META-INF / services フォルダーに含めて、実行時に取得できるようにすることです。

8. すべてをまとめる

これまで見てきたように、 Keycloakは、アプリケーション側から必要な構成を大幅に簡素化しました。 データソースやセキュリティ構成をプログラムで定義する必要はありません。

すべてをまとめるには、SpringとSpringBootApplicationの構成を定義する必要があります。

8.1. application.yml

Spring構成には単純なYAMLを使用します。

server:
  port: 8083

spring:
  datasource:
    username: sa
    url: jdbc:h2:mem:testdb;DB_CLOSE_ON_EXIT=FALSE

keycloak:
  server:
    contextPath: /auth
    adminUser:
      username: bael-admin
      password: ********
    realmImportFile: baeldung-realm.json

8.2. SpringBootアプリケーション

最後に、Spring Bootアプリケーションは次のとおりです。

@SpringBootApplication(exclude = LiquibaseAutoConfiguration.class)
@EnableConfigurationProperties(KeycloakServerProperties.class)
public class AuthorizationServerApp {
    private static final Logger LOG = LoggerFactory.getLogger(AuthorizationServerApp.class);
    
    public static void main(String[] args) throws Exception {
        SpringApplication.run(AuthorizationServerApp.class, args);
    }

    @Bean
    ApplicationListener<ApplicationReadyEvent> onApplicationReadyEventListener(
      ServerProperties serverProperties, KeycloakServerProperties keycloakServerProperties) {
        return (evt) -> {
            Integer port = serverProperties.getPort();
            String keycloakContextPath = keycloakServerProperties.getContextPath();
            LOG.info("Embedded Keycloak started: http://localhost:{}{} to use keycloak", 
              port, keycloakContextPath);
        };
    }
}

特に、ここでは KeycloakServerProperties 構成を有効にして、ApplicationListenerBeanに挿入しました。

このクラスを実行した後、認証サーバーのウェルカムページ(http:// localhost:8083 / auth / )にアクセスできます。

9. 結論

このクイックチュートリアルでは、SpringBootアプリケーションに組み込まれたKeycloakサーバーをセットアップする方法を説明しました。 このアプリケーションのソースコードは、GitHubから入手できます。

この実装の元のアイデアはThomasDarimontによって開発され、プロジェクトembedded-spring-boot-keycloak-serverにあります。