gRPC Spring Boot Server with Oauth2 (Keycloak)

This post will describe, in shortest steps, the configuration needed to get spring boot grpc server secured with oauth2 tokens, in my case with Keycloak.

Resources:

My demo project can be found at github. Server code has been updated to have all the configuration necessary, but Its commented out so the example can be used as a simple grcp client – server demo.

My local setup is:

  • Keycloak running in docker container, bootstrapped with exported (and customized by adding test users) realm json file. Use postman to obtain user access token.
  • Spring boot gRPC server
  • Testing with Postman gRPC calls (remember to add Header Authorization Bearer token if using oauth2 authorization)

My tokens are using custom realm access roles, in following format:

"realm_access": {
    "roles": [
      "User",
      "Admin"
    ]
  },

And that is the reason I had to write custom KeycloakJwtGrantedAuthoritiesConverter to pull the roles out of the token.

Other configuration is as follows:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, proxyTargetClass = true)
public class SecurityConfig {

    @Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
    private String jwkSetUri;

    @Bean
    public JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withJwkSetUri(this.jwkSetUri).build();
    }


    /*
    * Use this bean for regular oauth2 tokens that use scope
    * @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
    JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
    grantedAuthoritiesConverter.setAuthoritiesClaimName("authorities");

    JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
    jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
    return jwtAuthenticationConverter;
    }*/

    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        KeycloakJwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new KeycloakJwtGrantedAuthoritiesConverter();

        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
        return jwtAuthenticationConverter;
    }


    @Bean
    AuthenticationManager authenticationManager(JwtDecoder jwtDecoder, JwtAuthenticationConverter jwtAuthenticationConverter) {
        final JwtAuthenticationProvider jwtAuthenticationProvider = new JwtAuthenticationProvider(jwtDecoder);
        jwtAuthenticationProvider.setJwtAuthenticationConverter(jwtAuthenticationConverter);

        final List<AuthenticationProvider> providers = new ArrayList<>();
        providers.add(jwtAuthenticationProvider);
        return new ProviderManager(providers);
    }

    @Bean
    GrpcAuthenticationReader authenticationReader() {
        final List<GrpcAuthenticationReader> readers = new ArrayList<>();
        readers.add(new BearerAuthenticationReader(accessToken -> new BearerTokenAuthenticationToken(accessToken)));
        return new CompositeGrpcAuthenticationReader(readers);
    }
}

Where application.properties holds the value for:

spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:8081/auth/realms/YOUR_REALM/protocol/openid-connect/certs

PS I am using Postman from the snap packages, to update it you can use:

$ sudo snap refresh postman --channel=v9/stable