Skip to content

Protect individual resources from Spring Boot via Keycloak

I’ve got a very simple Spring Boot application with resources at /repositories and /persons.

Here is my build.gradle file.

plugins {
    id 'org.springframework.boot' version '2.4.0'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
    id 'java'
}

// use java 11 until keycloak is fixed
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

dependencyManagement {
    imports {
        mavenBom "org.keycloak.bom:keycloak-adapter-bom:12.0.1"
    }
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.keycloak:keycloak-spring-boot-starter'

    implementation 'org.flywaydb:flyway-core'
    runtime 'org.postgresql:postgresql'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

...
...

Here is my SecurityConfig.java file.

@KeycloakConfiguration
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

  @Autowired
  public void configureGlobal(AuthenticationManagerBuilder auth) {
    var keycloakAuthenticationProvider = keycloakAuthenticationProvider();
    keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
    auth.authenticationProvider(keycloakAuthenticationProvider);
  }

  @Bean
  @Override
  protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
    return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    super.configure(http);
    http.authorizeRequests()
        .antMatchers("/persons*")
        .hasRole("user")
        .anyRequest()
        .permitAll();
  }

  @Bean
  public KeycloakConfigResolver keycloakConfigResolver() {
    return new KeycloakSpringBootConfigResolver();
  }
}

Here is my application.yaml file.

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/postgres
    username: john
    password: john

keycloak:
  auth-server-url: http://localhost:8081/auth/
  realm: myrealm
  resource: myclient
  credentials:
    secret: 45d43bd6-5ab9-476c-83c8-67bd203a78ee

It’s all on my local machine and Keycloak and Postgres are started via docker compose.

version: "3.1"

volumes:
  postgres_data:
      driver: local

services:
  db:
    image: "postgres:13.1"
    ports:
      - "5432:5432"
    environment:
      POSTGRES_DB: postgres
      POSTGRES_USER: john
      POSTGRES_PASSWORD: john
  postgres:
    image: "postgres:13.1"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: keycloak
      POSTGRES_USER: keycloak
      POSTGRES_PASSWORD: password
  keycloak:
    image: quay.io/keycloak/keycloak:12.0.1
    environment:
      DB_VENDOR: POSTGRES
      DB_ADDR: postgres
      DB_DATABASE: keycloak
      DB_USER: keycloak
      DB_SCHEMA: public
      DB_PASSWORD: password
      KEYCLOAK_USER: admin
      KEYCLOAK_PASSWORD: Pa55w0rd
    ports:
      - 8081:8080
    depends_on:
      - postgres

I’ve got the users zemirco and one in Keycloak and they both have the user role. It all works very nicely and the route /persons is protected whereas /repositories is open for all.

Here is my problem! I’d like to secure individual resources, e.g. /person/1. I cannot make it work 🙁 I’ve been trying for days now but no luck. Here are my keycloak settings.

Here is my policy.

policy

Here is the resource.

resource

Here is the permission.

permission

It all works during the evaluating at Keycloak. However it does not work directly at my Spring Boot application. I can still access /person/1 as zemirco although user one should only have access.

I’m a bit lost at the moment. Do you have any ideas?

Thank you very much!


Edit Jan 10 2021

Thank you all for your answers but I still think it’s doable without a lot of extra effort on my side (the application side). I’m especially looking at policy enforcers https://www.keycloak.org/docs/latest/authorization_services/#_enforcer_overview.

A PEP is responsible for enforcing access decisions from the Keycloak server where these decisions are taken by evaluating the policies associated with a protected resource. It acts as a filter or interceptor in your application in order to check whether or not a particular request to a protected resource can be fulfilled based on the permissions granted by these decisions.

This sound exactly like what I’m trying to do. Unfortunately I cannot make it work. I just think my configuration is false or maybe incomplete. I will add a bounty to the question.

Answer

It finally works 🙂 I’m happy!

Here is what I did.

First of all I used the keycloak.json file from my Keycloak client installation. At the beginning I tried to put all configuration into my application.yaml. The keycloak.json file must be located at src/main/webapp/WEB-INF/keycloak.json. When you’re using the JSON file instead of the YAML file you can remove the following from your SecurityConfig.

public KeycloakConfigResolver keycloakConfigResolver() {
  return new KeycloakSpringBootConfigResolver();
}

It’s actually quite nice. You can simply copy&paste the content from Keycloak into the file and you can be sure that everything is right. Another problem I saw is that the config in keycloak.json is called policy-enforcer compared to policy-enforcer-config in application.yaml. That’s quite annoying because I didn’t realize it at the beginning. Here is config.

{
  "realm": "myrealm",
  "auth-server-url": "http://localhost:8081/auth/",
  "ssl-required": "external",
  "resource": "myclient",
  "verify-token-audience": true,
  "credentials": {
    "secret": "45d43bd6-5ab9-476c-83c8-67bd203a78ee"
  },
  "confidential-port": 0,
  "policy-enforcer": {}
}

The "policy-enforcer": {} is the most important part. Here we enable the default settings and simply say that Keycloak should authorize all requests to our resources.

Then everything seemed to work. I was able to gain access to a protected resource only with the right user. However after I logged out and tried to access the resource again I was still able to do it. Here I learned that my ant matchers were wrong. This is the right configuration.

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    super.configure(http);
    http.authorizeRequests()
        .antMatchers("/persons/**") // changed from .antMatchers("/persons*")
        .hasRole("user")
        .anyRequest()
        .permitAll()
        .and()
        // this is just to have a convenient GET /logout method. DO NOT USE IN PRODUCTION
        .logout()
        .logoutRequestMatcher(new AntPathRequestMatcher("/logout"));
  }

Now everything works and my resources/policies/permissions from Keycloak are automatically applied in my Spring Boot application.