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.
Here is the resource.
Here is the 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.
Advertisement
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.