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 tokenif 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