Keycloak with Spring Boot #1 – Configure Spring Security with Keycloak

featured image

Keycloak provides simple integration with Spring applications. As a result, we can easily configure our Spring Boot API security to delegate authentication and authorization to a Keycloak server.

Prerequisites

Keycloak client to be integrated with Spring Security

Securing Spring Boot applications with Keycloak

I’m going to add maven dependencies and required properties for Keycloak integration. Then, before I actually restrict access to my API endpoints, I will define a basic security configuration and test that both tools work together properly.

Add dependencies

First, I’m going to add the required dependencies to my project. In addition to the Keycloak and Spring Security starters for Spring Boot, I’ll add the Keycloak bom for adapters:

Add application properties

I’m running my Keycloak instance in a Docker container. For my sample client configuration, see the screenshot from the Prerequisites section.

I’m going to copy some information from my Keycloak client setup to the application.properties file:

Below is a brief explanation of the properties:

  • realm – my example realm name;
  • resource – my example client name;
  • auth-server-url – you can get the value from the Keycloak OIDC URI endpoint list by visiting the realm settings, clicking the OpenID Endpoint Configuration and copying the auth path:
  • credentials.secret – my example client has the confidential access type. Therefore, I have to copy the secret value from the Credentials tab:
client secret in Keycloak

If you want to access user roles at the client level and not user roles at the realm level, add the following property:

However, in my example I’m using realm roles for users.

Add security config

I’m going to create two configuration classes to handle my Keycloak setup.

The first one provides the ability to resolve my Keycloak config based on the application.properties file. I’m going to enclose it in a separate file to avoid the circular dependency issue (described in Javadoc in the snippet below):

Next, I’m going to add the following class containing a Keycloak-based Spring security configuration:

There are a few things to note:

  1. @KeycloakConfiguration – this metadata annotation provides all annotations that are needed to integrate Keycloak in Spring Security (e.g. for enabling Spring Security or configuring component scanning);
  2. KeycloakWebSecurityConfigurerAdapter – by extending this class we gain a useful base class for creating a WebSecurityConfigurer instance secured by Keycloak;
  3. SecurityConfig::configure – we’re going to overwrite the basic Spring Security behaviour, first by calling the parent implementation and then by adding our own csrf and endpoint protection config;
  4. SecurityConfig::configureGlobal – furthermore, we have to register the KeycloakAuthenticationProvider with the authentication manager;
  5. SecurityConfig::getKeycloakAuthenticationProvider – my auxiliary method for customising the authentication provider. Namely, I’m providing a simple one-to-one GrantedAuthoritiesMapper which adds the ROLE_ prefix and converts the authority value to upper case (e.g. a chief-operating-officer role from Keycloak realm becomes ROLE_CHIEF-OPERATING-OFFICER in my Spring Boot app);
  6. SecurityConfig::sessionAuthenticationStrategy – the session authentication strategy bean has to be of type RegisterSessionAuthenticationStrategy for public or confidential applications (NullAuthenticatedSessionStrategy for bearer-only applications).

Test basic Keycloak configuration for Spring Security

Although I haven’t restricted access to any endpoint in my API, I’m still going to test the current setup. I’m going to enable the DEBUG log lever for security by adding the following property to my application.properties file:

As a result, I can verify the configuration details and see that Keycloak filters are present in the filter chain:

Moreover, Spring Security doesn’t create a user password which would be its default behaviour without the Keycloak configuration.

At this point, my Spring Boot application is delegating authentication and authorization processes to Keycloak. However, I am still free to call the endpoints as they are not explicitly secured.

Secure endpoints

Next, I’m going to restrict access to the API endpoints by replacing anyRequest().permitAll() with the anyRequest().authenticated() line:

After restarting the application, I can see in its logs that all endpoints are protected:

As a result, my Postman setup and Swagger UI config require additional configuration to allow me to make API calls.

Update tests after configuring Spring Security to use Keycloak

Configuring security in a project will break existing Spring MVC tests. Therefore, below you will find some details that require updating.

Disable Keycloak in tests and use plain Spring Security

I don’t want to clutter tests with the configuration of an external authorization service. Therefore, I’m going to configure my Spring MVC tests to use Spring Security instead of Keycloak. For full instructions on how to apply a different security configuration in tests, see the Keycloak with Spring Boot #2 – Spring Security instead of Keycloak in tests post.

Mock an authenticated user

Fortunately, Spring Security provides the @WithMockUser annotation. We can apply it to a specific test or an entire class. In the following example test method, we can see how to use this annotation in a single test while keeping the default user data:

Additionally, we can customise the username, password, roles and authorities (the latter two are exclusive) that our mocked user will receive:

Include csrf protection

As you may have noticed, my security configuration uses csrf protection. Therefore, I have to add the SecurityMockMvcRequestPostProcessors::csrf method that automatically populates a valid CSRF token in my POST request:

Read more on integrating Spring Security with Keycloak

Photo by cottonbro from Pexels

Leave a Reply

Your email address will not be published. Required fields are marked *