Configuring our Spring Boot API to use Keycloak as an authentication and authorization server can greatly simplify our codebase. However, it adds another external dependency that will complicate the integration testing. As a remedy, we can switch to native Spring Security when executing tests to verify only the business rules for access control instead of cluttering the code with Keycloak dependencies.
<!-- pom.xml -->
…
<dependencies>
…
<!-- Security -->
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- END of Security -->
<!-- Tests -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!-- END Tests -->
…
</dependencies>
…
keycloak-spring-boot
repository. If you want to run the app locally, visit the project repository on GitHub and follow the directions in the README.md file.For example, after we add Keycloak config to a project, Spring MVC tests will fail to load the ApplicationContext
due to the following error:
java.io.FileNotFoundException: Unable to locate Keycloak configuration file: keycloak.json
Currently, there is no default way to disable Keycloak protection (note that the property keycloak.enabled=false is not supported).
While we could provide just enough Keycloak configuration to run the tests, it would introduce irrelevant details into the code. Therefore, we need to ensure a security config that keeps the test code clean and still allows API access rules to be verified.
First, we’ll make the Keycloak setup dependent on a custom application property. Then we’ll configure our tests to use just Spring Security.
We’re going to create a custom application property to control the loading of the security config. An important point to remember is that I want Keycloak to be used by default. In other words, I only want to turn it off in testing.
Create the application.properties
file in the test/resources
directory and copy the following property:
# src/test/resources/application.properties
security.config.use-keycloak=false
When executing tests, Spring Boot will only see the properties defined in this file. If you have any properties that should also be used in your tests, be sure to copy them to this file as well. The advantage of this approach is that it removes all irrelevant properties from the testing context and also provides a clear view of our testing setup.
Next, we need to configure our app to use Keycloak either when our custom property explicitly says so or when it hasn’t been specified at all (by default). Therefore, we’re going to aply the ConditionalOnProperty annotation to all Keycloak-related configuration classes (there are two in my project):
package in.keepgrowing.keycloakspringboot.security.config;
…
import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
@KeycloakConfiguration
@ConditionalOnProperty(name = "security.config.use-keycloak", havingValue = "true", matchIfMissing = true)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
…
package in.keepgrowing.keycloakspringboot.security.config;
…
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnProperty(name = "security.config.use-keycloak", havingValue = "true", matchIfMissing = true)
public class KeycloakConfig {
…
Thanks to specifying the matchIfMissing
attribute, the Keycloak configuration is our default security configuration. However, it won’t be used when running tests because we explicitly disabled it in the test/resources/application.properties
file.
As you may have noticed, we don’t have any security setup for our testing at the moment. Ideally, we want to test the business rules for API security without duplicating it in the tests. Therefore, I’m going to extract the common configuration into a static method so that I can reuse it in my tests:
package in.keepgrowing.keycloakspringboot.security.config;
import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
@KeycloakConfiguration
@ConditionalOnProperty(name = "security.config.use-keycloak", havingValue = "true", matchIfMissing = true)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
public static void configureApiSecurity(HttpSecurity http) throws Exception {
http
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.authorizeRequests()
…
.anyRequest().authenticated();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
configureApiSecurity(http);
}
…
}
As a result, I can reuse the SecurityConfig::configureApiSecurity
method outside of the Keycloak configuration class.
Consequently, we can now provide a separate security configuration for the tests. Create a class in the test
package that:
security.conifg.use-keycloak
property is set to false
,SecurityConfig::configureApiSecurity
method we defined earlier.You can see my conifg class below:
package in.keepgrowing.keycloakspringboot.security.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@ConditionalOnProperty(name = "security.config.use-keycloak", havingValue = "false")
public class TestSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
SecurityConfig.configureApiSecurity(http);
}
}
This is all I need to apply my security config in tests without coupling it with an external authorization server.
Finally, by running tests from my ProductControllerTest
class, I can verify the actual configuration in logs:
As we can see, a user password is generated automatically according to the default Spring Security specification and there is no mention of any Keycloak configuration.
Spring Security allows us to use role-based control to restrict access to API resources. However,…
A custom annotation in Spring Boot tests is an easy and flexible way to provide…
Delegating user management to Keycloak allows us to better focus on meeting the business needs…
Swagger offers various methods to authorize requests to our Keycloak secured API. I'll show you…
Keycloak provides simple integration with Spring applications. As a result, we can easily configure our…
Postman comes with a wide variety of OAuth 2.0 compliant configuration options that allow us…