Issue
I've a simple question regarding the object. I made a @Controller to manage some REST APIs which read and write to a db which looks like this:
@RestController
@RequestMapping("/api")
public class PersonController {
@Autowired
PersonService personService;
@RequestMapping(value="/write/persons", method=RequestMethod.POST)
public Person createPerson(@RequestBody Person person) {
return personService.createPerson(person);
}
@RequestMapping(value="/read/persons", method=RequestMethod.GET)
public List<Person> getPersons() {
return personService.getPersons();
}
@RequestMapping(value="/write/persons/{id}", method=RequestMethod.PUT)
public Person updatePerson(@PathVariable(value = "id") Long id, @RequestBody Person personDetails) {
return personService.updatePerson(id, personDetails);
}
@RequestMapping(value="/write/persons/{id}", method=RequestMethod.DELETE)
public void deletePerson(@PathVariable(value = "id") Long id) {
personService.deletePerson(id);
}
@RequestMapping(value="/read/personsbyname", method=RequestMethod.GET)
public List<Person> findByFirstName(@RequestParam(value = "fstname", defaultValue = "Test") String firstName) {
return personService.findAllByFirstName(firstName);
}
}
Then I also made a web security configurer which looks like this:
@Configuration
public class JWTSecurityConfig extends WebSecurityConfigurerAdapter {
//@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/write/**").hasAuthority("SCOPE_write")
.antMatchers("/api/read/**").hasAuthority("SCOPE_read")
.anyRequest().permitAll()
.and().oauth2ResourceServer().jwt();
}
}
Also in keycloack which is running on localhost:8080 I defined a user and 2 clients with, obviously, a read scope and a write scope. I'm using Postman to test this out and everything seem to work fine: if I try to call a "write API" without retrieving the access token with the write scope before calling it, Postman returns 403 Forbidden and I'm ok with it. The question is then: how keycloack (or another auth server) and spring work together? Using Postman to retrieve the access token I've to insert Client-ID, Auth URL and Token URL which I can retrieve only if I access to OIDC specs (after logging into keycloak as admin). After that it asks me to log as a keycloak user but why it happens? Keycloak just wants to know that the client Postman is emulating has a "connected" Resource Owner? I don't know if I specifed the question properly, let me know if something is not clear.
Solution
You need to get more knowledge of OpenID and spring-security specs.
Given your conf, each request to /api/read or /api/write on your resource-server will have to be authorized: contain an authorization header with an access-token.
In your resource-server security configuration, the client is not important (can be Postman, a web app, or whatever), what is required is that resource-owner (end-user) is authenticated and granted with SCOPE_read
or SCOPE_write
authorities.
Because you use default spring-security JwtAuthoritiesConverter (which turns scope into authorities), this happens if Keycloak has put "read" or "write" (or both) in scope
claim of the JWT it issued as access-token after the user / client authenticated (depends if you used "authorization code" or "client credentials" flow).
You should not use this default converter. Scopes are not authorities.
As OpenID only specifies identification (contains nothing about a User rights), each OIDC authorization-server uses its own private claims for rights. For that purpose, Keycloak uses
realm_access.roles
by defaultresource_access.{clientId}.roles
in addition, if client roles mapper is activated
but other vendors willuse something else.
To parse the right claims into authorities, you might use:
- https://github.com/ch4mpy/spring-addons: there is a property to list claims to get authorities from (and others to set a prefix or force authories case)
- write your own authorities converter
I recommand you do not use Keycloak spring-boot adapter which is deprecated
To make it short, what you need:
- have roles assigned to users (and clients if you allow client credentials flow) in Keycloak
- have relevent Keycloak mappers enabled: if roles are assigned at client level, then client roles mapper must be enabled
- define your spring authorities-converter and security rules based on what actually is in access-tokens (use https://jwt.io to explore it if needed)
Answered By - ch4mp
Answer Checked By - Marie Seifert (JavaFixing Admin)