Securing Java gRPC services with JWT-based authentication
gRPC is an open source, high-performance RPC framework that has several advantages to be used for communication between services,
but unfortunately, in addition to SSL/TLS support, the only authentication mechanism built-in to gRPC is token-based authentication for use with Google services.
In this tutorial, we are going to create a gRPC server and secure it with JWT-based authentication.
1. Maven Dependencies
Our first step is to create a Maven project and add the necessary dependencies:
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.21.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.21.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.21.0</version>
</dependency>
2. gPRC service
Next we need to define the gRPC service using protocol buffers and generate interfaces to be used by the gPRC server and client.
2.1. Service definition
Let’s create a proto file and define a simple service:
2.2. Code generation
For protobuf-based codegen integrated with the Maven build system, we need to put our proto file in the src/main/proto
directory
and use protobuf-maven-plugin
:
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.2</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.6.1:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.19.0:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
As soon as everything is set up, we can execute the following goal to generate code:
mvn compile
3. gPRC server
There are two parts to get our server to do its job.
3.1. Service implementation
We need to extend the generated service class and override the method so that it performs the actual work:
We made the service a private inner class only to reduce the number of classes, but we could have it as a separate class.
3.2. Server startup
Then we need to run a gRPC server to listen for requests from clients and return the service responses:
4. gPRC client
We can start our server, but the only way to call a remote procedure is to use the generated gRPC client. Let’s create a client that will make a request to our server at startup and display the greeting received in the response in the console:
Having started the client, we will see in the console a greeting with the specified name received from the server:
5. Authorization
To create and verify JSON Web Tokens (JWTs), we add the JJWT library to our dependencies:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
We also create a class for constants, some of which will be used by both the server and the client.
5.1. Server security
To protect the server, we need to implement an ServerInterceptor
, which will get the authorization token from the metadata,
verify it and set the client identifier obtained from the claims into the context.
In order not to complicate the code with additional checks (expiration date, issuer and etc.), we will only rely on the signature of the token:
We also will not introduce any restrictions on the client’s identifier, but simply make our service output it to the console when processing the request:
Now, if we restart the server and run our client, we will see an exception in the console with a message about the missing token:
5.2. Client authorization
To authorize our client, we need to send a valid token along with our request.
All gRPC stubs inherit the withCallCredentials(CallCredentials credentials)
method which returns a new stub that uses the given call credentials,
but to use this method we need to implement CallCredentials
that will contain the token in the required format:
Then we add a method to our client to generate a valid token:
Next, we just need to create an instance of the BearerToken
from the generated JWT
and make a stub in the main method of our client to use it:
Now, if we start the client, we should get the expected greeting in the client console and the message with the client’s identifier in the service console:
6. Conclusion
We created a simple gRPC server and protected it with JWT based authentication. We also created a client for this server and implemented its authorization.
Full source code can be found on GitHub.