Extending great gRPC library with Auth module. Easy implementation using a simple annotations similar to ones used in Spring Security module.
(Try example project: gRPC example project in Kotlin.)
Simple start consist only from 3 simple steps.
(If you never used gRPC library before, have a look on this basic setup first.)
<dependency>
<groupId>io.github.majusko</groupId>
<artifactId>grpc-jwt-spring-boot-starter</artifactId>
<version>${version}</version>
</dependency>
@Allow
annotation to your service methodAll you need to do is to annotate your method in the service implementation.
@GRpcService
public class ExampleServiceImpl extends ExampleServiceGrpc.ExampleServiceImplBase {
@Allow(roles = GrpcRole.INTERNAL)
public void getExample(GetExample request, StreamObserver<Empty> response) {
//...
}
}
Just autowire already prepared AuthClientInterceptor
bean and intercept your client. It will inject the internal token to every request by default.
@Service
public class ExampleClient {
@Autowired
private AuthClientInterceptor authClientInterceptor;
public void exampleRequest() {
final ManagedChannel channel = ManagedChannelBuilder.forTarget(target).usePlaintext().build();
final Channel interceptedChannel = ClientInterceptors.intercept(channel,authClientInterceptor);
final ExampleServiceBlockingStub stub = ExampleServiceGrpc.newBlockingStub(interceptedChannel);
stub.getExample(GetExample.newBuilder().build());
}
}
Useful only in case you never heard about gRPC library from LogNet. You can find there a nice show case too.
The service definition from .proto file looks like this
service ExampleService {
rpc GetExample (GetExample) returns (Empty) {};
}
message Empty {}
message GetExample {
string ownerField = 1;
}
All you need to do is to annotate your service implementation with GRpcService
@GRpcService
public class ExampleServiceImpl extends ExampleServiceGrpc.ExampleServiceImplBase {
public void getExample(GetExample request, StreamObserver<Empty> response) {
response.onNext(Empty.newBuilder().build());
response.onCompleted();
}
}
You can use application.properties
to override the default configuration.
grpc.jwt.algorithm
-> Algorithm used for signing the JWT token. Default: HmacSHA256
grpc.jwt.secret
-> String used as a secret to sign the JWT token. Default: default
grpc.jwt.expirationSec
-> Number of seconds needed to token becoming expired. Default: 3600
grpc.jwt.algorithm=HmacSHA256
grpc.jwt.secret=secret
grpc.jwt.expirationSec=3600
We know 2 types of annotation: @Allow
and @Expose
@Allow
roles
-> Algorithm used for signing the JWT token. Default: HmacSHA256
ownerField
-> Example: ownerField
. Optional field. Your request will be parsed and if the mentioned field is found, it will compare equality with JWT token subject(e.g.: ownerField). By this comparison, you can be sure that any operation with that field is made by the owner of the token. If the fields don't match and data are owned by another user, specified roles will be checked after.
Example use case of ownerField
: Imagine, you want to list purchased orders of some user.
You might want to reuse the exact same API for back-office and also for that particular user who created the orders.
With ownerField
you can check for the owner and also for some role if owner ownerField in JWT token is different.
@Exposed
environments
List of environments (Spring Profiles) where you can access the gRPC without checking for owner or roles.
Use case: Debug endpoint for the client/front-end development team.@GRpcService
public class ExampleServiceImpl extends ExampleServiceGrpc.ExampleServiceImplBase {
@Allow(ownerField="ownerField", roles = GrpcRole.INTERNAL)
@Exposed(environments={"dev","qa"})
public void getExample(GetExample request, StreamObserver<Empty> response) {
//...
}
}
You will need to generate tokens for your users or clients. You might want to specify special roles for each user and also service method. You can use the JwtService
for simple and performing usage.
@Service
public class SomeClass {
private final static String ADMIN = "admin";
@Autowired
private JwtService jwtService;
public void someMethod() {
final JwtData data = new JwtData("user-id-12345", new HashSet<>(ADMIN));
final String token = jwtService.generate(data);
}
}
We have two types of usages for client.
AuthClientInterceptor
to your service.ClientInterceptors
class.@Service
public class SomeClass {
@Autowired
private AuthClientInterceptor authClientInterceptor;
public void customTokenRequest() {
final ManagedChannel channel = ManagedChannelBuilder.forTarget(target).usePlaintext().build();
final Channel interceptedChannel = ClientInterceptors.intercept(channel,authClientInterceptor);
final ExampleServiceBlockingStub stub = ExampleServiceGrpc.newBlockingStub(interceptedChannel);
final Empty response = stub.getExample(GetExample.newBuilder().setUserId("user-id-jr834fh").build());
}
}
JwtService
to gRPC header with GrpcHeader.AUTHORIZATION
@Service
public class SomeClass {
public void customTokenRequest() {
final Metadata header = new Metadata();
header.put(GrpcHeader.AUTHORIZATION, "jwt-token-r348hf34hf43f93");
final ManagedChannel channel = ManagedChannelBuilder.forTarget(target).usePlaintext().build();
final ExampleServiceBlockingStub stub = ExampleServiceGrpc.newBlockingStub(channel);
final ExampleServiceBlockingStub stubWithHeaders = MetadataUtils.attachHeaders(stub, header);
final Empty response = stub.getExample(GetExample.newBuilder().setUserId("user-id-jr834fh").build());
}
}
The library is fully covered with integration tests which are also very useful as a usage example.
GrpcJwtSpringBootStarterApplicationTest
All contributors are welcome. If you never contributed to the open-source, start with reading the Github Flow.