# grpc-spring-boot-starter **Repository Path**: tanbinh/grpc-spring-boot-starter ## Basic Information - **Project Name**: grpc-spring-boot-starter - **Description**: Spring Boot starter module for gRPC framework. - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2024-01-15 - **Last Updated**: 2024-01-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README = Spring boot starter for http://www.grpc.io/[gRPC framework.] :toc: image:https://img.shields.io/maven-central/v/io.github.lognet/grpc-spring-boot-starter.svg?label=Maven%20Central[link=https://search.maven.org/search?q=g:%22io.github.lognet%22%20AND%20a:%22grpc-spring-boot-starter%22] image:https://travis-ci.org/LogNet/grpc-spring-boot-starter.svg?branch=master[Build Status,link=https://travis-ci.org/LogNet/grpc-spring-boot-starter] image:https://codecov.io/gh/LogNet/grpc-spring-boot-starter/branch/master/graph/badge.svg["Codecov",link="https://codecov.io/gh/LogNet/grpc-spring-boot-starter/branch/master"] Best viewed with image:https://www.octotree.io/_nuxt/img/03e72a3.svg["Octoree",width="24", link="https://www.octotree.io"] :toc: :source-highlighter: prettify :numbered: :icons: font == Features Auto-configures and runs the embedded gRPC server with @GRpcService-enabled beans as part of spring-boot application. + [IMPORTANT] Starting from release `4.0.0` the starter is compiled and tested against Spring Boot *2.3.X* only, + *1.5.X* Spring boot version support is dropped to allow tight Spring Boot Security framework integration. == Setup [source,groovy] ---- repositories { mavenCentral() //maven { url "https://oss.sonatype.org/content/repositories/snapshots" } //for snashot builds } dependencies { compile 'io.github.lognet:grpc-spring-boot-starter:4.2.0' } ---- If you are using Spring Boot Dependency Management plugin, it might pull not the same version as the version this started was compiled against, causing binary incompatibility issue. + In this case you'll need to forcibly and implicitly set the `grpc` version to use (see link:ReleaseNotes.adoc[version matrix here^] ): [source,groovy] ---- configurations.all { resolutionStrategy.eachDependency { details -> if ("io.grpc".equalsIgnoreCase(details.requested.group)) { details.useVersion "1.33.0" } } } ---- [IMPORTANT] Starting from release `3.0.0` the artifacts are published to *maven central*. Pay attention that `group` has changed from `org.lognet` to `io.github.lognet`. [NOTE] The release notes with compatibility matrix can be found link:ReleaseNotes.adoc[here^] == Usage * Start by https://github.com/google/protobuf-gradle-plugin[generating] stub and server interface(s) from your `.proto` file(s). * Annotate your server interface implementation(s) with `@org.lognet.springboot.grpc.GRpcService` * Optionally configure the server port in your `application.yml/properties`. Default port is `6565`. [source,yaml] ---- grpc: port: 6565 ---- [NOTE] A random port can be defined by setting the port to `0`. + The actual port being used can then be retrieved by using `@LocalRunningGrpcPort` annotation on `int` field which will inject the running port (explicitly configured or randomly selected) * Optionally enable server reflection (see https://github.com/grpc/grpc-java/blob/master/documentation/server-reflection-tutorial.md) [source,yaml] ---- grpc: enableReflection: true ---- * Optionally set the number of seconds to wait for preexisting calls to finish during graceful server shutdown. New calls will be rejected during this time. A negative value is equivalent to an infinite grace period. Default value is `0` (means don't wait). [source,yaml] ---- grpc: shutdownGrace: 30 ---- The starter supports also the `in-process server`, which should be used for testing purposes : [source,yaml] ---- grpc: enabled: false <1> inProcessServerName: myTestServer <2> ---- <1> Disables the default server (`NettyServer`). <2> Enables the `in-process` server. [NOTE] If you enable both the `NettyServer` and `in-process` server, they will both share the same instance of `HealthStatusManager` and `GRpcServerBuilderConfigurer` (see <>). == Show case In the `grpc-spring-boot-starter-demo` project you can find fully functional examples with integration tests. + === Service implementation The service definition from `.proto` file looks like this : [source,proto] ---- service Greeter { rpc SayHello ( HelloRequest) returns ( HelloReply) {} } ---- Note the generated `io.grpc.examples.GreeterGrpc.GreeterImplBase` class that extends `io.grpc.BindableService`.(The generated classes were intentionally committed for demo purposes). All you need to do is to annotate your service implementation with `@org.lognet.springboot.grpc.GRpcService` [source,java] ---- @GRpcService public static class GreeterService extends GreeterGrpc.GreeterImplBase{ @Override public void sayHello(GreeterOuterClass.HelloRequest request, StreamObserver responseObserver) { final GreeterOuterClass.HelloReply.Builder replyBuilder = GreeterOuterClass.HelloReply.newBuilder().setMessage("Hello " + request.getName()); responseObserver.onNext(replyBuilder.build()); responseObserver.onCompleted(); } } ---- === Interceptors support The starter supports the registration of two kinds of interceptors: _Global_ and _Per Service_. + In both cases the interceptor has to implement `io.grpc.ServerInterceptor` interface. - Per service [source,java] ---- @GRpcService(interceptors = { LogInterceptor.class }) public class GreeterService extends GreeterGrpc.GreeterImplBase{ // ommited } ---- `LogInterceptor` will be instantiated via spring factory if there is bean of type `LogInterceptor`, or via no-args constructor otherwise. - Global [source,java] ---- @GRpcGlobalInterceptor public class MyInterceptor implements ServerInterceptor{ // ommited } ---- The annotation on java config factory method is also supported : [source,java] ---- @Configuration public class MyConfig{ @Bean @GRpcGlobalInterceptor public ServerInterceptor globalInterceptor(){ return new ServerInterceptor(){ @Override public ServerCall.Listener interceptCall(ServerCall call, Metadata headers, ServerCallHandler next) { // your logic here return next.startCall(call, headers); } }; } } ---- Global interceptors can be ordered using Spring's `@Ordered` or `@Priority` annotations. Following Spring's ordering semantics, lower order values have higher priority and will be executed first in the interceptor chain. [source,java] ---- @GRpcGlobalInterceptor @Order(10) public class A implements ServerInterceptor{ // will be called before B } @GRpcGlobalInterceptor @Order(20) public class B implements ServerInterceptor{ // will be called after A } ---- The particular service also has the opportunity to disable the global interceptors : [source,java] ---- @GRpcService(applyGlobalInterceptors = false) public class GreeterService extends GreeterGrpc.GreeterImplBase{ // ommited } ---- === Spring security support The starter provides built-in support for authenticating and authorizing users leveraging integration with https://spring.io/projects/spring-security[Spring Security framework]. + Please refer to the sections on <> for details on supported authentication providers and configuration options. === Transport Security (TLS) The transport security can be configured using root certificate together with its private key path: [source,yaml] ---- grpc: security: cert-chain: classpath:cert/server-cert.pem private-key: file:../grpc-spring-boot-starter-demo/src/test/resources/cert/server-key.pem ---- The value of both properties is in form supported by https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/io/ResourceEditor.html[ResourceEditor]. + The client side should be configured accordingly : [source,java] ---- ((NettyChannelBuilder)channelBuilder) .useTransportSecurity() .sslContext(GrpcSslContexts.forClient().trustManager(certChain).build()); ---- This starter will pull the `io.netty:netty-tcnative-boringssl-static` dependency by default to support SSL. + If you need another SSL/TLS support, please exclude this dependency and follow https://github.com/grpc/grpc-java/blob/master/SECURITY.md[Security Guide]. [NOTE] If the more detailed tuning is needed for security setup, please use custom configurer described in <> === Custom gRPC Server Configuration To intercept the `io.grpc.ServerBuilder` instance used to build the `io.grpc.Server`, you can add bean that inherits from `org.lognet.springboot.grpc.GRpcServerBuilderConfigurer` to your context and override the `configure` method. + By the time of invocation of `configure` method, all discovered services, including theirs interceptors, had been added to the passed builder. + In your implementation of `configure` method, you can add your custom configuration: [source,java] ---- @Component public class MyGRpcServerBuilderConfigurer extends GRpcServerBuilderConfigurer{ @Override public void configure(ServerBuilder serverBuilder){ serverBuilder .executor(YOUR EXECUTOR INSTANCE) .compressorRegistry(YOUR COMPRESSION REGISTRY) .decompressorRegistry(YOUR DECOMPRESSION REGISTRY) .useTransportSecurity(YOUR TRANSPORT SECURITY SETTINGS); ((NettyServerBuilder)serverBuilder)// cast to NettyServerBuilder (which is the default server) for further customization .sslContext(GrpcSslContexts // security fine tuning .forServer(...) .trustManager(...) .build()) .maxConnectionAge(...) .maxConnectionAgeGrace(...); } }; } ---- [NOTE] If you enable both `NettyServer` and `in-process` servers, the `configure` method will be invoked on the same instance of configurer. + If you need to differentiate between the passed `serverBuilder` s, you can check the type. + This is the current limitation. == Spring Security Integration === Setup .Dependencies to implement authentiction scheme (to be added to server-side project) [cols="a,a"] |=== |Scheme |Dependencies |Basic | * `org.springframework.security:spring-security-config` |Bearer | * `org.springframework.security:spring-security-config` * `org.springframework.security:spring-security-oauth2-jose` * `org.springframework.security:spring-security-oauth2-resource-server` |_Custom_ | * `org.springframework.security:spring-security-config` * `your.custom.lib` |=== === Server side configuration GRPC security configuration follows the same principals and APIs as Spring WEB security configuration. ==== Default Defining bean with type `GrpcSecurityConfigurerAdapter` annotated with `@EnableGrpcSecurity` is sufficient to secure you GRPC services and/or methods : [source,java] ---- @EnableGrpcSecurity public class GrpcSecurityConfiguration extends GrpcSecurityConfigurerAdapter { } ---- This default configuration secures GRPC methods/services annotated with `org.springframework.security.access.annotation.@Secured` annotation. + If `JwtDecoder` bean exists in your context, it will also register `JwtAuthenticationProvider` to handle the validation of authentication claim. ==== Custom Various configuration examples and test scenarios are link:grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth[here]. [source,java] ---- @EnableGrpcSecurity public class GrpcSecurityConfiguration extends GrpcSecurityConfigurerAdapter { @Autowired private JwtDecoder jwtDecoder; @Override public void configure(GrpcSecurity builder) throws Exception { builder.authorizeRequests()<1> .methods(GreeterGrpc.getSayHelloMethod()).hasAnyAuthority("SCOPE_profile")<2> .and() .authenticationProvider(JwtAuthProviderFactory.withAuthorities(jwtDecoder));<3> } } ---- <1> Get hold of authorization configuration object <2> `MethodDefinition` of `sayHello` method is allowed for authenticated users with `SCOPE_profile` authority. <3> Use `JwtAuthenticationProvider` to validate user claim (`BEARER` token) against resource server configured with `spring.security.oauth2.resourceserver.jwt.issuer-uri` property. ==== DIY One is possible to plug in your own bespoke authentication provider by implementing `AuthenticationSchemeSelector` interface. [source,java] ---- @EnableGrpcSecurity public class GrpcSecurityConfiguration extends GrpcSecurityConfigurerAdapter { @Override public void configure(GrpcSecurity builder) throws Exception { builder.authorizeRequests() .anyMethod().authenticated()<1> .and() .authenticationSchemeSelector(new AuthenticationSchemeSelector() { <2> @Override public Optional getAuthScheme(CharSequence authorization) { return new MyAuthenticationObject(); <3> } }) .authenticationProvider(new AuthenticationProvider() { <4> @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { MyAuthenticationObject myAuth= (MyAuthenticationObject)authentication; //validate myAuth return MyValidatedAuthenticationObject(withAuthorities);<5> } @Override public boolean supports(Class authentication) { return MyAuthenticationObject.class.isInstance(authentication); } }); } } ---- <1> Secure all services methods. <2> Register your own `AuthenticationSchemeSelector`. <3> Based on provided authorization header - return `Authentication` object as a claim (not authenticated yet) <4> Register your own `AuthenticationProvider` that supports validation of `MyAuthenticationObject` <5> Validate provided `authentication` and return validated and *authenticated* `Authentication` object <> section explains how to pass custom authorization scheme and claim from GRPC client. === Obtaining Authentication details To obtain `Authentication` object in the implementation of *secured method*, please use below snippet [source,java] ---- final Authentication auth = GrpcSecurity.AUTHENTICATION_CONTEXT_KEY.get(); ---- === Client side configuration support By adding `io.github.lognet:grpc-client-spring-boot-starter` dependency to your *java grpc client* application you can easily configure per-channel or per-call credentials : * Per-channel : [source,java] ---- class MyClient{ public void doWork(){ final AuthClientInterceptor clientInterceptor = new AuthClientInterceptor(<1> AuthHeader.builder() .bearer() .tokenSupplier(this::generateToken)<3> ); Channel authenticatedChannel = ClientInterceptors.intercept( ManagedChannelBuilder.forAddress("host", 6565), clientInterceptor <2> ); // use authenticatedChannel to invoke GRPC service } private ByteBuffer generateToken(){ <3> // generate bearer token against your resource server } } ---- <1> Create client interceptor <2> Intercept channel <3> Provide token generator function (Please refer to link:grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/auth/JwtAuthBaseTest.java[for example].) * Per-call : [source,java] ---- class MyClient{ public void doWork(){ AuthCallCredentials callCredentials = new AuthCallCredentials( <1> AuthHeader.builder().basic("user","pwd".getBytes()) ); final SecuredGreeterGrpc.SecuredGreeterBlockingStub securedFutureStub = SecuredGreeterGrpc.newBlockingStub(ManagedChannelBuilder.forAddress("host", 6565));<2> final String reply = securedFutureStub .withCallCredentials(callCredentials)<3> .sayAuthHello(Empty.getDefaultInstance()).getMessage(); } } ---- <1> Create call credentials with basic scheme <2> Create service stub <3> Attach call credentials to the call `AuthHeader` could also be built with bespoke authorization scheme : [source,java] ---- AuthHeader .builder() .authScheme("myCustomAuthScheme") .tokenSupplier(()->generateMyCustomToken()) ---- == Consul Integration Starting from version `3.3.0`, the starter will auto-register the running grpc server in Consul registry if `org.springframework.cloud:spring-cloud-starter-consul-discovery` is in classpath and `spring.cloud.service-registry.auto-registration.enabled` is *NOT* set to `false`. + The registered service name will be prefixed with `grpc-` ,i.e. `grpc-${spring.application.name}` to not interfere with standard registered web-service name if you choose to run both embedded `Grpc` and `Web` servers. + Setting `spring.cloud.consul.discovery.register-health-check` to true will register GRPC health check service in Consul. Tags could be set by defining `spring.cloud.consul.discovery.tags` property. You can find the test that demonstrates the feature link:grpc-spring-boot-starter-demo/src/test/java/org/lognet/springboot/grpc/ConsulRegistrationTest.java[here]. == Eureka Integration When building production-ready services, the advise is to have separate project for your service(s) gRPC API that holds only proto-generated classes both for server and client side usage. + You will then add this project as `compile` dependency to your `gRPC client` and `gRPC server` projects. To integrate `Eureka` simply follow the great https://spring.io/guides/gs/service-registration-and-discovery/[guide] from Spring. Below are the essential parts of configurations for both server and client projects. === gRPC Server Project * Add eureka starter as dependency of your server project together with generated classes from `proto` files: [source,gradle] .build.gradle ---- dependencies { compile('org.springframework.cloud:spring-cloud-starter-eureka') compile project(":yourProject-api") } ---- * Configure gRPC server to register itself with Eureka. [source,yaml] .bootstrap.yaml ---- spring: application: name: my-service-name <1> ---- <1> Eureka's `ServiceId` by default is the spring application name, provide it before the service registers itself with Eureka. [source,yaml] .application.yaml ---- grpc: port: 6565 <1> eureka: instance: nonSecurePort: ${grpc.port} <2> client: serviceUrl: defaultZone: http://${eureka.host:localhost}:${eureka.port:8761}/eureka/ <3> ---- <1> Specify the port number the gRPC is listening on. <2> Register the eureka service port to be the same as `grpc.port` so client will know where to send the requests to. <3> Specify the registry URL, so the service will register itself with. * Expose the gRPC service as part of Spring Boot Application. [source,java] .EurekaGrpcServiceApp.java ---- @SpringBootApplication @EnableEurekaClient public class EurekaGrpcServiceApp { @GRpcService public static class GreeterService extends GreeterGrpc.GreeterImplBase { @Override public void sayHello(GreeterOuterClass.HelloRequest request, StreamObserver responseObserver) { } } public static void main(String[] args) { SpringApplication.run(DemoApp.class,args); } } ---- === gRPC Client Project * Add eureka starter as dependency of your client project together with generated classes from `proto` files: [source,gradle] .build.gradle ---- dependencies { compile('org.springframework.cloud:spring-cloud-starter-eureka') compile project(":yourProject-api") } ---- * Configure client to find the eureka service registry: [source,yaml] .application.yaml ---- eureka: client: register-with-eureka: false <1> service-url: defaultZone: http://${eureka.host:localhost}:${eureka.port:8761}/eureka/ <2> ---- <1> `false` if this project is not meant to act as a service to another client. <2> Specify the registry URL, so this client will know where to look up the required service. [source,java] .GreeterServiceConsumerApplication.java ---- @EnableEurekaClient @SpringBootApplication public class GreeterServiceConsumerApplication { public static void main(String[] args) { SpringApplication.run(GreeterServiceConsumerApplication.class, args); } } ---- * Use EurekaClient to get the coordinates of gRPC service instance from Eureka and consume the service : [source,java] .GreeterServiceConsumer.java ---- @EnableEurekaClient @Component public class GreeterServiceConsumer { @Autowired private EurekaClient client; public void greet(String name) { final InstanceInfo instanceInfo = client.getNextServerFromEureka("my-service-name", false);<1> final ManagedChannel channel = ManagedChannelBuilder.forAddress(instanceInfo.getIPAddr(), instanceInfo.getPort()) .usePlaintext() .build(); <2> final GreeterServiceGrpc.GreeterServiceFutureStub stub = GreeterServiceGrpc.newFutureStub(channel); <3> stub.greet(name); <4> } } ---- <1> Get the information about the `my-service-name` instance. <2> Build `channel` accordingly. <3> Create stub using the `channel`. <4> Invoke the service. == License Apache 2.0