SpringBoot as WAR doesn't load/initialize Spring Config for Keycloak

Viewed 664

I have a SpringBoot 2.3.1 application build with maven that loads some configuration from a application.yml in src/main/resources.

As a JAR the configuration is loaded from <jarfile>/BOOT-INF/classes/application.yml.

As a WAR the configuration is not loaded (null), but the WAR contains it in <warfile>/WEB-INF/classes/application.yml

My relevant dependencies are:

...
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</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-security</artifactId>
</dependency>
<dependency>
  <groupId>org.keycloak</groupId>
  <artifactId>keycloak-spring-boot-starter</artifactId>
</dependency>
...

and the profile is release.

  <profiles>
    <profile>
      <id>dev</id>
      <activation>
        <activeByDefault>true</activeByDefault>
      </activation>
      <properties>
        <project.packaging>jar</project.packaging>
      </properties>
    </profile>
    <profile>
      <id>release</id>
      <properties>
        <project.packaging>war</project.packaging>
      </properties>
      <dependencies>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
          <exclusions>
            <exclusion>
              <artifactId>spring-boot-starter-tomcat</artifactId>
              <groupId>org.springframework.boot</groupId>
            </exclusion>
          </exclusions>
        </dependency>
        <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
          <version>3.0.1</version>
          <scope>provided</scope>
        </dependency>
      </dependencies>
    </profile>
  </profiles>

  <build>
    <defaultGoal>spring-boot:run</defaultGoal>
    <finalName>${project.artifactId}</finalName>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

I've tested that with

/** Main Entry Point for this Application */
@SpringBootApplication
public class Application extends SpringBootServletInitializer implements WebApplicationInitializer {
  private static final Logger log = LoggerFactory.getLogger(Application.class);

  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }

  @Override
  protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
    log.info("Configure Servlet");
    return builder.sources(Application.class);
  }
}

And a config class like:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
@KeycloakConfiguration
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
...
  @Value("${spring.application.name}")
  private String name;
  @Value("${keycloak.realm}")
  private String realm;

  /** Set Keycloak Config Resolver to use Spring Config */
  @Bean
  public KeycloakSpringBootConfigResolver keycloakConfigResolver() {
    log.error("1-spring.application.name is: " + name);
    log.error("1-keycloak.realm is: " + realm);
    KeycloakSpringBootConfigResolver resolver = new KeycloakSpringBootConfigResolver();
    return resolver;
  }

  @Bean(name = "MyTEST1")
  String getString() {
    log.error("2-spring.application.name is: " + name);
    log.error("2-keycloak.realm is: " + realm);
    return "Hello world";
  }

In the log message, I can see during startup that the realm and name is null in keycloakConfigResolver() method but milliseconds later not null in getString(). The 2- logs are directly after the 1-.. logs in my log file.

2020-06-18 15:14:58.438 DEBUG 14536 --- [alina-utility-2] d.m.selfservices.platform.Application    : Running with Spring Boot v2.3.1.RELEASE, Spring v5.2.7.RELEASE
2020-06-18 15:14:58.440  INFO 14536 --- [alina-utility-2] d.m.selfservices.platform.Application    : No active profile set, falling back to default profiles: default
2020-06-18 15:14:59.711  INFO 14536 --- [alina-utility-2] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1233 ms
2020-06-18 15:15:00.192 ERROR 14536 --- [alina-utility-2] d.m.s.platform.SecurityConfig            : 1-spring.application.name is: null
2020-06-18 15:15:00.192 ERROR 14536 --- [alina-utility-2] d.m.s.platform.SecurityConfig            : 1-keycloak.realm is: null
2020-06-18 15:15:00.664  INFO 14536 --- [alina-utility-2] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@167c9b7e, org.springframework.security.web.context.SecurityContextPersistenceFilter@4c7dee5, org.springframework.security.web.header.HeaderWriterFilter@43d8836e, org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter@2d74b982, org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter@2d74b982, org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter@1d951d12, org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter@1d951d12, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@13256bad, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@4f27b99b, org.keycloak.adapters.springsecurity.filter.KeycloakSecurityContextRequestFilter@2839e57e, org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticatedActionsFilter@41fb5754, org.springframework.security.web.session.SessionManagementFilter@149db72c, org.springframework.security.web.access.ExceptionTranslationFilter@49d41456, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@2aa4e85a]
2020-06-18 15:15:00.683 ERROR 14536 --- [alina-utility-2] d.m.s.platform.SecurityConfig            : 2-spring.application.name is: Plattform
2020-06-18 15:15:00.683 ERROR 14536 --- [alina-utility-2] d.m.s.platform.SecurityConfig            : 2-keycloak.realm is: myrealm
2020-06-18 15:15:00.931  INFO 14536 --- [alina-utility-2] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-06-18 15:15:01.170  INFO 14536 --- [alina-utility-2] d.m.selfservices.platform.Application    : Started Application in 3.277 seconds (JVM running for 1431.91)

I can confirm that behaviour on Tomcat 9.0.36, Wildfly 20 and with Spring Boot 2.2.6 and Keycloak 9.0.3 and 10.0.2. With embedded Tomcat everything is fine. When I intentionally misspell keycloak.realm, I'm getting correctly an exception, that the value could not be resolved.

So, the properties are there, I can reach them with a simple bean definition, but during keycloakConfigResolver() it isn't available. I'm aware of the "new" - but that is the example from Keycloak.

What do I miss?

1 Answers

I presume the problem here is creation of the KeycloakSpringBootConfigResolver via "new".

The resolver has an internal AdapterConfig object. As part of some refactoring/bugfixing this object became non-static some time ago. Before the change the static AdapterConfig has been set centrally and all instances (constructed via new) could access that object. So this seems to be a bug within spring-keycloak.

The root cause here is, that KeycloakSpringBootConfigResolver should have been created by Spring itself and the AdapterConfig should have been declared an @Autowired field (or been set via constructor).

Two possible solutions come to mind.

  1. Create your own KeycloakSpringBootConfigResolver (really not much code in there) using @Autowired on the AdapterConfig field (The injection will be done by Spring once your keycloakConfigResolver() annotated method returns the instance
  2. Set the AdapterConfig via reflection within your keycloakConfigResolver() method
Related