Issue
I've currently got some issue when it comes to configure keycloaks policy enforcement/fine graining. I'm trying to secure a path which has a Wildcard in the middle of the URL. for example: /test/{UUID}/bla It works when I secure a path with a wildcard at the end of it like /test/bla/{uuid} but I can't get it to work when there's some variable in the middle of a path.
The Documentation of keycloak says:
Currently a very basic logic for path matching is supported. Examples of valid paths are:
Wildcards: /*
Suffix: /*.html
Sub-paths: /path/*
Path parameters: /resource/{id}
Exact match: /resource
Patterns: /{version}/resource, /api/{version}/resource, /api/{version}/resource/*
I've tried to put {version}, * or {id} but doesnt seem to change anything. My application.properties:
keycloak.securityConstraints[0].securityCollections[0].patterns[1]=/data/{id}/test1
keycloak.securityConstraints[0].securityCollections[0].patterns[2]=/data/*/test2
keycloak.securityConstraints[0].securityCollections[0].patterns[3]=/data/test3/*
I'm able to secure test3 with an id but not the other endpoints. Actually they are secured I just can't get access/policy enforcement to work on them* On my Keycloak config I've also tried alot of URI varaitions like this.
Would be nice if anyone can help me out :) I've also tried this post: Fetch resource given partial url path or based on a regex pattern using keycloak rest admin apis but the matchingURI didn't seem to work.
Solution
This does not answer your question, but:
- do not use Keycloak libs for Spring, it is deprecated.
- why use policy enforcer which has at least two major drawbacks compared to JWT decoding and spring-security expressions?
- far less efficient: it requires a call from resource-server to Keycloak for each and every request when a JWT access-token can be validated and interpreted by resource-server only (a single call to Keycloak is issued at startup to fetch signing key).
- you can't unit-test security rules: a keycloak instance must be up and reachable for policies to be tested in integration tests.
To secure a Spring resource server without Keycloak libs, define spring-security expressions and unit-test it, refer to this article: https://dzone.com/articles/spring-oauth2-resource-servers
Sample (quite advanced) access control rule: @PreAuthorize("is(#username) or isNice() or onBehalfOf(#username).can('greet')")
In action, this gives:
@RestController
public class GreetingController {
@GetMapping("/greet/{username}")
@PreAuthorize("is(#username) or isNice() or onBehalfOf(#username).can('greet')")
public String getGreetingFor(@PathVariable("username") String username, Authentication auth) {
return "Hi %s from %s!".formatted(username, auth.getName());
}
}
And matching unit-tests:
@Test
@ProxiesAuth(
authorities = { "AUTHOR" },
claims = @OpenIdClaims(preferredUsername = "Tonton Pirate"),
proxies = { @Proxy(onBehalfOf = "ch4mpy", can = { "greet" }) })
void whenNotNiceWithProxyThenCanGreetFor() throws Exception {
mockMvc.perform(get("/greet/ch4mpy"))
.andExpect(status().isOk())
.andExpect(content().string("Hi ch4mpy from Tonton Pirate!"));
}
@Test
@ProxiesAuth(
authorities = { "AUTHOR", "NICE" },
claims = @OpenIdClaims(preferredUsername = "Tonton Pirate"))
void whenNiceWithoutProxyThenCanGreetFor() throws Exception {
mockMvc.perform(get("/greet/ch4mpy"))
.andExpect(status().isOk())
.andExpect(content().string("Hi ch4mpy from Tonton Pirate!"));
}
@Test
@ProxiesAuth(
authorities = { "AUTHOR" },
claims = @OpenIdClaims(preferredUsername = "Tonton Pirate"),
proxies = { @Proxy(onBehalfOf = "jwacongne", can = { "greet" }) })
void whenNotNiceWithoutRequiredProxyThenForbiddenToGreetFor() throws Exception {
mockMvc.perform(get("/greet/greeted"))
.andExpect(status().isForbidden());
}
@Test
@ProxiesAuth(
authorities = { "AUTHOR" },
claims = @OpenIdClaims(preferredUsername = "Tonton Pirate"))
void whenHimselfThenCanGreetFor() throws Exception {
mockMvc.perform(get("/greet/Tonton Pirate"))
.andExpect(status().isOk())
.andExpect(content().string("Hi Tonton Pirate from Tonton Pirate!"));
}
Answered By - ch4mp
Answer Checked By - Willingham (JavaFixing Volunteer)