Spring boot + Keycloak + Permission based on authorization scope + Spring security

Bad, good or best practice?

Requirement: Secure an API, MyService, developed with Spring boot and RestController. The client provides an authentication JWT

Details:

  • The JWT is provided by a front end via a standard flow
  • Permissions on keycloak are defined via granularly configured permissions, for example GET:STATUS is an authorization scope to read the states of a resource.

Keycloak:

  • Added two clients to keycloak, one for the front end application (standard flow) called client_fe and one called client_service for the states service.
  • Permissions configurations for client_service:
    – n. 3 AUTHORIZATION_SCOPE: DELETE:STATUS, GET:STATUS e POST:STATUS
    – n. 1 RESOURCE called status-check-resource with URI=/users/status/check and scopes: DELETE:STATUS, GET:STATUS e POST:STATUS
    – n. 2 POLICY based on role:
    — only_configurator based on role configurator
    — only_doctor based on role doctor
    – n. 3 PERMISSIONS based on scope
    — delete-status-permission, DELETE:STATUS, only_configurator and only_doctor
    — get-status-permission, GET:STATUS, only_doctor
    — post-status-permission, POST:STATUS, only_configurator

Spring Boot (MyService):

pom.xml

<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-security</artifactId>
	</dependency>

application.yml
spring.security.oauth2.resourceserver.jwt.jwk-set-uri = http://localhost:8080/realms/myrealm/protocol/openid-connect/certs

Parts of java controller class StatusController (RestController, RequestMapping(“/users”))

    @PreAuthorize("hasAuthority('SCOPE_GET:STATUS')")
	@GetMapping("/status/check"){...}
	
	@PreAuthorize("hasAuthority('SCOPE_DELETE:STATUS')")
	@DeleteMapping("/status/check"){...}
	
	@PreAuthorize("hasAuthority('SCOPE_POST:STATUS')")
	@PostMapping("/status/check"){...}

Spring security layer configuration

...
	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

		JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
		jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new KeycloakScopeConverter());

		http
				.authorizeHttpRequests((authorize) -> authorize
						.anyRequest().authenticated()
				)
				.oauth2ResourceServer(
						oauth2 -> oauth2.jwt(
								jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter)
						)
				);

		return http.build();
	}

KeycloakScopeConverter, a java class that, given the incoming JWT (the one provided by the front end), obtains an RPT token and extracts the authorization scopes from this.

makes an http POST with
url: http://localhost:8080/realms/myrealm/protocol/openid-connect/token
grant_type: urn:ietf:params:oauth:grant-type:uma-ticket
audience: client_service
header: "Authorization", " Bearer " + jwt.getTokenValue()) (jwt fornito dal FE)

The scopes are extracted from the array permissions:
ArrayList<String> _scopes = new ArrayList<>();
  for (Map<String, Object> permission : permissions) {
      ArrayList<String> scopes = (ArrayList<String>) permission.get("scopes");
      if (scopes != null) {
        for (String scope : scopes) {
            _scopes.add(scope);
        }
     }
   }

    Collection<GrantedAuthority> returnValue = _scopes
            .stream().map(scope -> "SCOPE_" + scope)
            .map(SimpleGrantedAuthority::new)
            .collect(Collectors.toList());

In this way a control using the PreAuthorize(“hasAuthority(”…"))

The question is: Is this a bad, good or best practice?

Over to you.

:point_right: See the #keycloak Slack channel response

For those who don’t have Slack access, below are my replies:

Reply-1:
I would say that the answer depends on the use case you want to implement and which components you want to act as PDP and PEP.
For example, in an API acting as an OAuth 2.0 Resource Server based on scopes (as in your example with Spring Security), you define the authorization rules in your app using annotations.
However, when you move to UMA (with the adapter), Keycloak is acting as the PDP. Therefore, Keycloak becomes the Authorization Service in your architecture.
Each approach has its pros and cons based on different factors. Authorization is a complex topic.
Finally just to clarity your comment “There is no OAuth2 in the strict sense, the client, an Angular application, obtains a JWT from keycloak”,
It’s not just a JWT; you are implementing an OIDC federation, which is an identity layer built on top of the OAuth 2.0 framework.

Reply-2:
If you using the adapter, this means that your are implementing UMA. IMO, UMA it’s a good idea un paper but it can be a complex to implement. While UMA 2.0 was introduce to simplify thing, it hasn’t seen widespread adoption. And nobody is talking about this protocol in the authorization space.
Nevertheless, it’s just my felling on this, I usually try to live by the KISS design principle

1 Like