How do I access request metadata for a java grpc service I am defining?

ghz 1years ago ⋅ 5594 views

Question

For some background, I am attempting to use grpc auth in order to provide security for some services I am defining.

Let's see if I can ask this is a way that makes sense. For my python code, it was pretty easy to implement the server side code.

class TestServiceServer(service_pb2.TestServiceServer):

    def TestHello(self, request, context):

        ## credential metadata for the incoming request
        metadata = context.invocation_metadata()

        ## authenticate the user using the metadata

So, as you can tell, I am able to get the metadata from "context" quite easily. What is harder for me is to do the same thing in java.

public class TestImpl extends TestServiceGrpc.TestServiceImplBase {

    @Override
    public void testHello(TestRequest req, StreamObserver<TestResponse> responseObserver) {

        // How do I get access to similar request metadata here?

        // from the parameter positions, it looks like it should be
        // "responseObserver" but that doesn't seem similar to "context"

    }

}

I'll admit my problem comes from a few directions.

  1. I am not well versed in Java

  2. I heavily used python's "pdb" in order to debug the classes and see what methods are available to me. I don't know of/am not proficient at a similar tool for java.

  3. The documentation seems rather sparse at this point. It shows you how to set up an ssl connection on the server side, but I can't find an example of the server taking a look at request metadata, as I have shown in python.

Could someone please give me an idea of how to do this, or perhaps show me a useful debugging tool for java in the same vein of python's pdb?

EDIT/ANSWER :

I needed to first write a definition implementing the interface ServerInterceptor.

private class TestInterceptor implements ServerInterceptor {
    ....

Then, before actually binding my service and building my server, I needed to do this.

TestImpl service = new TestImpl();
ServerServiceDefinition intercepted = ServerInterceptors.intercept(service, new TestInterceptor());

Now I was able to create the server.

server = NettyServerBuilder.forPort(port)

    // enable tls
    .useTransportSecurity(
        new File(serverCert),
        new File(serverKey)
    )
    .addService(
        intercepted  // had been "new TestImpl()"
    )
    .build();

server.start();

This allowed my ServerInterceptor to actually be called when I fired off a client side request.

[This link](https://github.com/grpc/grpc- java/blob/16c07ba434787f68e256fc50cece1425f421b03e/core/src/test/java/io/grpc/ServerInterceptorsTest.java) was quite helpful in figuring this out.


Answer

Use a ServerInterceptor and then propagate the identity via Context. This allows you to have a central policy for authentication.

The interceptor can retrieve the identity from Metadata headers. It should then validate the identity. The validated identity can then be communicated to the application (i.e., testHello) via io.grpc.Context:

/** Interceptor that validates user's identity. */
class MyAuthInterceptor implements ServerInterceptor {
  public static final Context.Key<Object> USER_IDENTITY
      = Context.key("identity"); // "identity" is just for debugging

  @Override
  public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
      ServerCall<ReqT, RespT> call,
      Metadata headers,
      ServerCallHandler<ReqT, RespT> next) {
    // You need to implement validateIdentity
    Object identity = validateIdentity(headers);
    if (identity == null) { // this is optional, depending on your needs
      // Assume user not authenticated
      call.close(Status.UNAUTENTICATED.withDescription("some more info"),
                 new Metadata());
      return new ServerCall.Listener() {};
    }
    Context context = Context.current().withValue(USER_IDENTITY, identity);
    return Contexts.interceptCall(context, call, headers, next);
  }
}

public class TestImpl extends TestServiceGrpc.TestServiceImplBase {
  @Override
  public void testHello(TestRequest req, StreamObserver<TestResponse> responseObserver) {
    // Access to identity.
    Object identity = MyAuthInterceptor.USER_IDENTITY.get();
    ...
  }
}

// Need to use ServerInterceptors to enable the interceptor
Server server = ServerBuilder.forPort(PORT)
    .addService(ServerInterceptors.intercept(new TestImpl(),
        new MyAuthInterceptor()))
    .build()
    .start();