Lab instructions

Prerequisites

  • JDK 17. Preferably GrallVM JDK 17 or above
  • a decent Java IDE like STS, VS code or IntelliJ IDEA
  • Git
  • A local Keycloak instance (instruction there) with a meetup-public client for which
    • authorization-code flow is enabled
    • BARMAN, WAITER and CASHIER roles are enabled
    • a few users are defined with above roles mapped
    • client roles mapper is enabled if roles where declared at client level rather than realm one

Vanilla spring-boot-starter-oauth2-resource-server

  • add a dependency to org.springframework.boot:spring-boot-starter-oauth2-resource-server
  • add a SecurityConf class decorated with @Configuration
  • define a @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { return http.build(); }
  • in this bean
    • configure an OAuth2 resource-server with JWT decoder: http.oauth2ResourceServer().jwt();
    • configure access-control: http.authorizeHttpRequests().requestMatchers("/tables").permitAll().requestMatchers("/drinks").permitAll().anyRequest().authenticated();
    • disable sessions: http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    • disable CSRF: http.csrf().disable();
  • in properties file, configure JWT decoder: spring.security.oauth2.resourceserver.jwt.issuer-uri=https://localhost:8443/realms/master
  • in OrdersController, add some @PreAuthorize("hasAuthrority('...')") access-control expressions
  • test with Postman: https://www.getpostman.com/collections/813917d5e5d92eeecc70 with following OAuth2 authentication parameters:

spring-addons

Replace spring-boot-starter-oauth2-resource-server dependency with

<dependency>
	<groupId>com.c4-soft.springaddons</groupId>
	<artifactId>spring-addons-webmvc-jwt-resource-server</artifactId>
	<version>6.0.3</version>
</dependency>

Replace SecurityConfig with

@Configuration
@EnableMethodSecurity
public class SecurityConfig {
	/**
	 * <p>
	 * Switch {@link Authentication} implementation for valid JWTs from Spring's
	 * default {@link JwtAuthenticationToken} to
	 * {@link OAuthentication}&lt;{@link OpenidClaimSet}&gt;
	 * </p>
	 * <p>
	 * Note that bean is used only if one of
	 * spring-addons-{webmvc|webflux}-{jwt|introspecting}-resource-server is on the
	 * classpath.
	 * </p>
	 * 
	 * @param authoritiesConverter
	 * @return
	 */
	@Bean
	OAuth2AuthenticationFactory authenticationBuilder(OAuth2AuthoritiesConverter authoritiesConverter) {
		return (bearerString, claims) -> new OAuthentication<>(new OpenidClaimSet(claims),
				authoritiesConverter.convert(claims), bearerString);
	}
}

Replace issuer-uri property with:

com.c4-soft.springaddons.security.issuers[0].location=https://localhost:8443/realms/master
com.c4-soft.springaddons.security.issuers[0].authorities.claims=realm_access.roles,resource_access.meetup-public.roles
com.c4-soft.springaddons.security.cors[0].path=/orders/**
com.c4-soft.springaddons.security.permit-all=/actuator/health/readiness,/actuator/health/liveness,/v3/api-docs/**,/tables,/drinks

Test with Postman it works the same

Unit tests

  • check the dependency to com.c4-soft.springaddons:spring-addons-webmvc-jwt-test:6.0.3
  • fix OrderController to have OrderControllerTest pass

As we replaced the default JwtAuthenticationToken with OAuthentication<OpenidClaimSet> in security config, @AuthenticationPrincipal now injects an OpenidClaimSet (instead of a Jwt). This enables you to write expressions like:

    @PreAuthorize("#order.placedBy eq #claims.subject")
    Object securedControllerMethod(@PathVariable(name = "orderId") Order order, @AuthenticationPrincipal OpenidClaimSet claims) {
        ...
    }

If you face difficulties, you may refer to the finished branch which contains working solution.

To go further

Follow this tutorials and samples

For Keycloak mapper sample (add private claims to tokens), you can refer to this project. Pay attention to resource files, Maven dependencies and implemented interfaces.