9

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 was quite helpful in figuring this out.

Zack
  • 13,454
  • 24
  • 75
  • 113

1 Answers1

23

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();
Eric Anderson
  • 24,057
  • 5
  • 55
  • 76
  • Where is this 'interceptCall' method used? – Zack Oct 18 '16 at 18:09
  • Also, assuming that you mean my class needs to implement the interface "ServerInterceptor" (please remember java is not my forte), I had to make the ServerCall method public for it to even compile. – Zack Oct 18 '16 at 18:25
  • "interceptCall(ServerCall, Metadata, ServerCallHandler)' in 'getapi.example.TestServer' clashes with 'interceptCall(ServerCall, Metadata, ServerCallHandler)' in 'io.grpc.ServerInterceptor'; attempting to assign weaker access privileges ('package-private'); was 'public'" – Zack Oct 18 '16 at 18:25
  • Ok, this basically led me to the light. There was a little more that I needed to do. I'll edit my question to provide the missing pieces. – Zack Oct 18 '16 at 20:28
  • 1
    I know I already accepted the answer here, but do you think you could explain what "Object identity = MyAuthInterceptor.USER_IDENTITY.get();" does? – Zack Oct 26 '16 at 15:43
  • That's the only part that I don't understand. It seems to be not really serving any purpose in my code. – Zack Oct 26 '16 at 15:44
  • If you only want an authenticated user, then it serves no purpose. This is normal when you just want a login for billing. Often though you want to do _something_ with the user identity. Like check authorization or give each user their own namespace of objects. That's when you would use USER_IDENTITY.get(). – Eric Anderson Oct 28 '16 at 20:06
  • So more for authorization, rather than authentication? – Zack Oct 31 '16 at 20:14
  • Yes, more of authorization, but also for accessing account-specific resources. Consider an email API listing emails in the inbox; it is implicitly scoped to the user. That's very similar to authz, but not equivalent. – Eric Anderson Nov 03 '16 at 19:33