Search code examples
javaspringspring-bootspring-securitykeycloak

How to integrate Keycloak into simple Spring Boot back-end with Spring Security?


I'm currently trying to implement security for a simple Spring Boot Back-End application. For that I intended to use Keycloak in combination with Spring Security. After trying a dozens of tutorial set-ups (just a basic filter / SecurityConfig), I'm left clueless as to why the SecurityFilterChain won't work.

As a reference - the following are my dependencies:

  • org.springframework.boot:spring-boot-starter:3.1.0
  • org.springframework.boot:spring-boot-starter-web:3.1.0
  • org.springframework.boot:spring-boot-starter-oauth2-client:3.1.0
  • org.springframework.boot:spring-boot-starter-security:3.1.0
  • org.springframework.boot:spring-boot-starter-oauth2-resource-server:3.1.0
  • org.projectlombok:lombok:1.18.26
  • org.springframework.boot:spring-boot-starter-test:3.1.0

all with Spring Boot 3.1.0! Due to the fact that the online material was from the last two years, I also tried different versions through (e.g. <spring-security.version>6.1.1</spring-security.version>, etc.)

As to my application.properties I added the most basic set-up:

spring.security.oauth2.client.registration.keycloak.client-id=[client-id]
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.keycloak.scope=openid
spring.security.oauth2.client.provider.keycloak.issuer-uri=http://localhost:8080/realms/dev
spring.security.oauth2.client.provider.keycloak.user-name-attribute=preferred_username

That's that! Now onto the different approaches I've went through unsuccessfully:

1. Tutorial from Baeldung

On there it specified the SecurityConfig to be used with a @Bean returning a SecurityFilterChain:

 @Bean
 public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
     http.authorizeRequests()
             .antMatchers("/customers*")
             .hasRole("USER")
             .anyRequest()
             .permitAll();
     http.oauth2Login()
             .and()
             .logout()
             .addLogoutHandler(keycloakLogoutHandler)
             .logoutSuccessUrl("/");
     http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
     return http.build();
 }

... Apparently, 'authorizeRequests()' is deprecated. Same applies for 'oauth2Login()' (even marked for removal)!

2. GitHub Issue #10187 discussion

In the before-mentioned issue, a in-depth discussion broke out with lots of members sharing their code snippet. It's a very recent topic (2022) and thus I thought it would still apply to current standards.

@Configuration
@RequiredArgsConstructor
class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final KeycloakLogoutHandler keycloakLogoutHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests() //
                .anyRequest().fullyAuthenticated() //
                .and().oauth2Client() //
                .and().oauth2Login().tokenEndpoint().and().userInfoEndpoint().userAuthoritiesMapper(userAuthoritiesMapper()) //
                .and().and()
                .logout().addLogoutHandler(keycloakLogoutHandler)
        ;
    }

    private GrantedAuthoritiesMapper userAuthoritiesMapper() {
    [...]

I played with something along those lines. Again... Besides the known culprits marked as deprecated (oauth2Client(), oauth2Login(), etc.) the big issue was WebSecurityConfigurerAdapter is ALSO deprecated. When opening issue #12514 I was then informed that I should proceed with a SecurityFilterChain similar to the first approach.

Either way, I've burnt through approximately 10+ tutorials now and am not able to find one working project which is - considering the size of KeyCloak - very odd. The only good information I found was that the old KeyCloak adapters were deprecated as of last year... though I haven't used them as you can see above.

Is anyone able to help me out? I want to set-up a SIMPLE SecurityConfig to build by base upon. Just fetching the bearer token (btw KeyCloak related set-up works) from the request, then validating the JWT and comparing the user roles / permissions to the needed role on an endpoint (preferably not a global setting, but a local one per endpoint basis).


Solution

  • If your Spring app just acts as an resource server, see my example here: https://github.com/dasniko/keycloak-bookshop-demo/tree/master/msgbroker

    In the dependencies, there's just the org.springframework.boot:spring-boot-starter-oauth2-resource-server.
    The application.properties just need the spring.security.oauth2.resourceserver.jwt.issuer-uri configured and the SecurityConfig class is also small and lean.