Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions vertx-grpc-server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
<groupId>io.vertx</groupId>
<artifactId>vertx-grpc-common</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-auth-jwt</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
Expand Down
30 changes: 30 additions & 0 deletions vertx-grpc-server/src/main/asciidoc/server.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,36 @@ You can compress response messages by setting the response encoding *prior* befo

Decompression is done transparently by the server when the client send encoded requests.

=== JWT Authentication

Service method handlers can be secured by providing an {@link io.vertx.grpc.server.auth.GrpcAuthenticationHandler} in the {@link io.vertx.grpc.server.GrpcServer#callHandler} method.

For JWT a handler can be created via {@link io.vertx.grpc.server.auth.GrpcJWTAuthenticationHandler#create}.

Add the required dependency to provide JWT via {@link io.vertx.ext.auth.jwt.JWTAuth}.

[source,java]
----
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-auth-jwt</artifactId>
</dependency>
----

Handlers that were registered without the handler will not use authentication.

[source,java]
----
{@link examples.GrpcServerExamples#jwtServerAuthExample}
----

The JWT can be added for the client call via an interceptor in order to provide the token credentials.

[source,java]
----
{@link examples.GrpcServerExamples#jwtClientAuthExample}
----

=== Stub API

The Vert.x gRPC Server can bridge a gRPC service to use with a generated server stub in a more traditional fashion
Expand Down
74 changes: 74 additions & 0 deletions vertx-grpc-server/src/main/java/examples/GreeterGrpc.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,37 @@ examples.HelloReply> getSayHelloMethod() {
return getSayHelloMethod;
}

private static volatile io.grpc.MethodDescriptor<examples.HelloRequest,
examples.HelloReply> getSaySecuredHelloMethod;

@io.grpc.stub.annotations.RpcMethod(
fullMethodName = SERVICE_NAME + '/' + "SaySecuredHello",
requestType = examples.HelloRequest.class,
responseType = examples.HelloReply.class,
methodType = io.grpc.MethodDescriptor.MethodType.UNARY)
public static io.grpc.MethodDescriptor<examples.HelloRequest,
examples.HelloReply> getSaySecuredHelloMethod() {
io.grpc.MethodDescriptor<examples.HelloRequest, examples.HelloReply> getSaySecuredHelloMethod;
if ((getSaySecuredHelloMethod = GreeterGrpc.getSaySecuredHelloMethod) == null) {
synchronized (GreeterGrpc.class) {
if ((getSaySecuredHelloMethod = GreeterGrpc.getSaySecuredHelloMethod) == null) {
GreeterGrpc.getSaySecuredHelloMethod = getSaySecuredHelloMethod =
io.grpc.MethodDescriptor.<examples.HelloRequest, examples.HelloReply>newBuilder()
.setType(io.grpc.MethodDescriptor.MethodType.UNARY)
.setFullMethodName(generateFullMethodName(SERVICE_NAME, "SaySecuredHello"))
.setSampledToLocalTracing(true)
.setRequestMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
examples.HelloRequest.getDefaultInstance()))
.setResponseMarshaller(io.grpc.protobuf.ProtoUtils.marshaller(
examples.HelloReply.getDefaultInstance()))
.setSchemaDescriptor(new GreeterMethodDescriptorSupplier("SaySecuredHello"))
.build();
}
}
}
return getSaySecuredHelloMethod;
}

/**
* Creates a new async stub that supports all call types for the service
*/
Expand Down Expand Up @@ -110,6 +141,13 @@ public void sayHello(examples.HelloRequest request,
io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getSayHelloMethod(), responseObserver);
}

/**
*/
public void saySecuredHello(examples.HelloRequest request,
io.grpc.stub.StreamObserver<examples.HelloReply> responseObserver) {
io.grpc.stub.ServerCalls.asyncUnimplementedUnaryCall(getSaySecuredHelloMethod(), responseObserver);
}

@java.lang.Override public final io.grpc.ServerServiceDefinition bindService() {
return io.grpc.ServerServiceDefinition.builder(getServiceDescriptor())
.addMethod(
Expand All @@ -119,6 +157,13 @@ public void sayHello(examples.HelloRequest request,
examples.HelloRequest,
examples.HelloReply>(
this, METHODID_SAY_HELLO)))
.addMethod(
getSaySecuredHelloMethod(),
io.grpc.stub.ServerCalls.asyncUnaryCall(
new MethodHandlers<
examples.HelloRequest,
examples.HelloReply>(
this, METHODID_SAY_SECURED_HELLO)))
.build();
}
}
Expand Down Expand Up @@ -150,6 +195,14 @@ public void sayHello(examples.HelloRequest request,
io.grpc.stub.ClientCalls.asyncUnaryCall(
getChannel().newCall(getSayHelloMethod(), getCallOptions()), request, responseObserver);
}

/**
*/
public void saySecuredHello(examples.HelloRequest request,
io.grpc.stub.StreamObserver<examples.HelloReply> responseObserver) {
io.grpc.stub.ClientCalls.asyncUnaryCall(
getChannel().newCall(getSaySecuredHelloMethod(), getCallOptions()), request, responseObserver);
}
}

/**
Expand Down Expand Up @@ -178,6 +231,13 @@ public examples.HelloReply sayHello(examples.HelloRequest request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getSayHelloMethod(), getCallOptions(), request);
}

/**
*/
public examples.HelloReply saySecuredHello(examples.HelloRequest request) {
return io.grpc.stub.ClientCalls.blockingUnaryCall(
getChannel(), getSaySecuredHelloMethod(), getCallOptions(), request);
}
}

/**
Expand Down Expand Up @@ -207,9 +267,18 @@ public com.google.common.util.concurrent.ListenableFuture<examples.HelloReply> s
return io.grpc.stub.ClientCalls.futureUnaryCall(
getChannel().newCall(getSayHelloMethod(), getCallOptions()), request);
}

/**
*/
public com.google.common.util.concurrent.ListenableFuture<examples.HelloReply> saySecuredHello(
examples.HelloRequest request) {
return io.grpc.stub.ClientCalls.futureUnaryCall(
getChannel().newCall(getSaySecuredHelloMethod(), getCallOptions()), request);
}
}

private static final int METHODID_SAY_HELLO = 0;
private static final int METHODID_SAY_SECURED_HELLO = 1;

private static final class MethodHandlers<Req, Resp> implements
io.grpc.stub.ServerCalls.UnaryMethod<Req, Resp>,
Expand All @@ -232,6 +301,10 @@ public void invoke(Req request, io.grpc.stub.StreamObserver<Resp> responseObserv
serviceImpl.sayHello((examples.HelloRequest) request,
(io.grpc.stub.StreamObserver<examples.HelloReply>) responseObserver);
break;
case METHODID_SAY_SECURED_HELLO:
serviceImpl.saySecuredHello((examples.HelloRequest) request,
(io.grpc.stub.StreamObserver<examples.HelloReply>) responseObserver);
break;
default:
throw new AssertionError();
}
Expand Down Expand Up @@ -294,6 +367,7 @@ public static io.grpc.ServiceDescriptor getServiceDescriptor() {
serviceDescriptor = result = io.grpc.ServiceDescriptor.newBuilder(SERVICE_NAME)
.setSchemaDescriptor(new GreeterFileDescriptorSupplier())
.addMethod(getSayHelloMethod())
.addMethod(getSaySecuredHelloMethod())
.build();
}
}
Expand Down
51 changes: 50 additions & 1 deletion vertx-grpc-server/src/main/java/examples/GrpcServerExamples.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
package examples;

import io.grpc.stub.ServerCallStreamObserver;
import com.google.common.net.HttpHeaders;

import examples.GreeterGrpc.GreeterBlockingStub;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;
import io.grpc.stub.StreamObserver;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.docgen.Source;
import io.vertx.ext.auth.KeyStoreOptions;
import io.vertx.ext.auth.User;
import io.vertx.ext.auth.jwt.JWTAuth;
import io.vertx.ext.auth.jwt.JWTAuthOptions;
import io.vertx.grpc.common.GrpcMessage;
import io.vertx.grpc.common.GrpcStatus;
import io.vertx.grpc.common.ServiceName;
import io.vertx.grpc.server.GrpcServer;
import io.vertx.grpc.server.GrpcServerRequest;
import io.vertx.grpc.server.GrpcServerResponse;
import io.vertx.grpc.server.GrpcServiceBridge;
import io.vertx.grpc.server.auth.GrpcAuthenticationHandler;
import io.vertx.grpc.server.auth.GrpcJWTAuthenticationHandler;

@Source
public class GrpcServerExamples {
Expand Down Expand Up @@ -125,6 +137,43 @@ public void responseCompression(GrpcServerResponse<Empty, Item> response) {
response.write(Item.newBuilder().setValue("item-3").build());
}


public void jwtServerAuthExample(Vertx vertx, HttpServerOptions options) {
JWTAuthOptions config = new JWTAuthOptions()
.setKeyStore(new KeyStoreOptions()
.setPath("keystore.jceks")
.setPassword("secret")
.setType("jceks"));

JWTAuth jwtAuth = JWTAuth.create(vertx, config);
GrpcAuthenticationHandler authHandler = GrpcJWTAuthenticationHandler.create(jwtAuth);
GrpcServer server = GrpcServer.server(vertx);

server.callHandler(authHandler, GreeterGrpc.getSayHelloMethod(), request -> {

request.handler(hello -> {
User authenticatedUser = request.user();

GrpcServerResponse<HelloRequest, HelloReply> response = request.response();

HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + hello.getName()
+ " via " + authenticatedUser.subject()).build();

response.end(reply);
});
});
}

public void jwtClientAuthExample(String token, int port) {

ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", port).usePlaintext().build();

Metadata header = new Metadata();
header.put(Metadata.Key.of(HttpHeaders.AUTHORIZATION, Metadata.ASCII_STRING_MARSHALLER), "Bearer " + token);
GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel)
.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(header));
}

public void stubExample(Vertx vertx, HttpServerOptions options) {

GrpcServer grpcServer = GrpcServer.server(vertx);
Expand Down
10 changes: 6 additions & 4 deletions vertx-grpc-server/src/main/java/examples/HelloWorldProto.java

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.vertx.grpc.server;

import io.vertx.core.http.HttpClientResponse;
import io.vertx.grpc.common.GrpcStatus;

public class GrpcException extends RuntimeException {

private static final long serialVersionUID = -7838327176604697641L;

private GrpcStatus status;

private HttpClientResponse httpResponse;

public GrpcException(String msg, GrpcStatus status,
HttpClientResponse httpResponse) {
super(msg);
this.status = status;
this.httpResponse = httpResponse;
}

public GrpcException(GrpcStatus status) {
this.status = status;
}

public GrpcException(GrpcStatus status, Throwable err) {
super(err);
this.status = status;
}

public GrpcException(GrpcStatus status, String msg) {
super(msg);
this.status = status;
}

public GrpcStatus status() {
return status;
}

public HttpClientResponse response() {
return httpResponse;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.grpc.server.auth.GrpcAuthenticationHandler;
import io.vertx.grpc.server.impl.GrpcServerImpl;

/**
Expand All @@ -43,7 +44,8 @@ public interface GrpcServer extends Handler<HttpServerRequest> {

/**
* Create a blank gRPC server
*
*
* @param vertx Vert.x instance
* @return the created server
*/
static GrpcServer server(Vertx vertx) {
Expand All @@ -68,4 +70,16 @@ static GrpcServer server(Vertx vertx) {
@GenIgnore(GenIgnore.PERMITTED_TYPE)
<Req, Resp> GrpcServer callHandler(MethodDescriptor<Req, Resp> methodDesc, Handler<GrpcServerRequest<Req, Resp>> handler);

/**
* Set a service method call handler that handles any call call made to the server for the {@link MethodDescriptor} service method and that uses the provided authentication handler to authenticate the method call.
*
* @param authHandler Authentication handler used to authenticate the method call
* @param methodDesc gRPC service method that will be handled
* @param handler the service method call handler
*
* @return a reference to this, so the API can be used fluently
*/
@GenIgnore(GenIgnore.PERMITTED_TYPE)
<Req, Resp> GrpcServer callHandler(GrpcAuthenticationHandler authHandler, MethodDescriptor<Req, Resp> methodDesc, Handler<GrpcServerRequest<Req, Resp>> handler);

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import io.vertx.codegen.annotations.VertxGen;
import io.vertx.core.Handler;
import io.vertx.core.http.HttpConnection;
import io.vertx.ext.auth.User;
import io.vertx.grpc.common.GrpcError;
import io.vertx.grpc.common.GrpcMessage;
import io.vertx.grpc.common.GrpcReadStream;
Expand Down Expand Up @@ -78,4 +79,9 @@ public interface GrpcServerRequest<Req, Resp> extends GrpcReadStream<Req> {
* @return the underlying HTTP connection
*/
HttpConnection connection();

User user();

GrpcServerRequest<Req, Resp> setUser(User user);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need this I think

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the setter from the interface but the getter is required. Otherwise a user of the API is unable to access the authenticated user. At this point there is no "Context" object for gRPC so I put the user in the request.


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.vertx.grpc.server.auth;

import io.vertx.codegen.annotations.VertxGen;
import io.vertx.core.Future;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.ext.auth.User;
import io.vertx.grpc.server.GrpcServer;

/**
* Authentication handler for {@link GrpcServer}.
*/
@VertxGen
@FunctionalInterface
public interface GrpcAuthenticationHandler {

/**
* Authenticate the provided request and return the authenticated user.
*
* @param httpRequest Request to authenticate
* @param requireAuthentication Whether the handler should fail when no authentication is present
* @return
*/
Future<User> authenticate(HttpServerRequest httpRequest, boolean requireAuthentication);

}
Loading