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