Article directory
- Introduction
- Client interceptor case
- Server-side interceptor case
- Authentication
Introduction
- Users can pass additional information (i.e. HTTP/2 Header information) by accessing or modifying
Metadata
, such as authentication information, TraceId, RequestId, etc.Metadata
stores data in the form of key-value. The key is a string type and the value is a string array type.- The life cycle of
Metadata
is an RPC call
- gRPC can add interception processing in four places
- Interception before client call
- Interception of replies received by the client
- Interception of requests received by the server
- Interception before server reply
Client-side interceptor case
-
Client-side pre-call interceptor
@Slf4j public class ClientPreInterceptor implements ClientInterceptor {<!-- --> @Override public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {<!-- --> final String methodName = method.getFullMethodName(); return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {<!-- --> @Override public void start(Listener<RespT> responseListener, Metadata headers) {<!-- --> log.info("Start by calling {}", methodName); super.start(responseListener, headers); } @Override public void sendMessage(ReqT message) {<!-- --> log.info("Method:{}Send message:{}", methodName, message); super.sendMessage(message); } @Override public void request(int numMessages) {<!-- --> log.info("Method: {} Number of requested messages passed to the listener: {}", methodName, numMessages); super.request(numMessages); } @Override public void halfClose() {<!-- --> log.info("Method: {} client half closed", methodName); super.halfClose(); } }; } }
-
Client post-call interceptor
@Slf4j public class ClientPostInterceptor implements ClientInterceptor {<!-- --> @Override public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {<!-- --> final String methodName = method.getFullMethodName(); return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {<!-- --> @Override public void start(Listener<RespT> responseListener, Metadata headers) {<!-- --> ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT> simpleForwardingClientCallListener = new ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT>(responseListener) {<!-- --> @Override public void onMessage(RespT message) {<!-- --> log.info("The client has received the response message, methodName:{},message:{}", methodName, message); super.onMessage(message); } @Override public void onHeaders(Metadata headers) {<!-- --> log.info("Response header has been received, methodName:{}", methodName); super.onHeaders(headers); } @Override public void onClose(Status status, Metadata trailers) {<!-- --> log.info("Client closes connection, methodName:{},code:{}", methodName, status.getCode()); super.onClose(status, trailers); } @Override public void onReady() {<!-- --> log.info("Client onReady,methodName:{}", methodName); super.onReady(); } }; super.start(simpleForwardingClientCallListener, headers); } }; } }
-
Client passes Metadata
//Put it in the api package (both the server and the client depend on the api package) public class Constant {<!-- --> public static final Context.Key<String> TRACE_ID_CTX_KEY = Context.key("traceId"); public static final Metadata.Key<String> TRACE_ID_METADATA_KEY = Metadata.Key.of("traceId", io.grpc.Metadata.ASCII_STRING_MARSHALLER); } public class TraceIdClientInterceptor implements ClientInterceptor {<!-- --> @Override public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(MethodDescriptor<ReqT, RespT> methodDescriptor, CallOptions callOptions, Channel channel) {<!-- --> return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(channel.newCall(methodDescriptor, callOptions)) {<!-- --> @Override public void start(Listener<RespT> responseListener, Metadata headers) {<!-- --> if (Constant.TRACE_ID_CTX_KEY.get() != null) {<!-- --> headers.put(Constant.TRACE_ID_METADATA_KEY, Constant.TRACE_ID_CTX_KEY.get()); } super.start(responseListener, headers); } }; } }
-
Client interceptor configuration
@Slf4j public class GrpcConsumerInterceptor {<!-- --> public static final String IP = "127.0.0.1"; public static final int PORT = 8081; @Test public void testGlobalInterceptor() {<!-- --> ManagedChannel channel = ManagedChannelBuilder.forAddress(IP, PORT) .usePlaintext()// Enable plain text .intercept( new ClientPreInterceptor() , new ClientPostInterceptor() , new TraceIdClientInterceptor() )//Register global interceptor when creating channel .build(); // Synchronous call HelloServiceGrpc.HelloServiceBlockingStub stub = HelloServiceGrpc.newBlockingStub(channel); HelloResponse helloResponse = stub.hello(HelloRequest.newBuilder() .setFirstName("Jannal") .setLastName("Jan") .build()); log.info("Response received from server:{}", helloResponse); channel.shutdown(); } @Test public void testMethodInterceptor() {<!-- --> ManagedChannel channel = ManagedChannelBuilder.forAddress(IP, PORT) .usePlaintext()// Enable plain text .build(); HelloServiceGrpc.HelloServiceBlockingStub stub = HelloServiceGrpc.newBlockingStub(channel); //Specify the interceptor when calling the method HelloResponse helloResponse = stub.withInterceptors(new ClientPreInterceptor(), new ClientPostInterceptor(), new TraceIdClientInterceptor()) .hello(HelloRequest.newBuilder() .setFirstName("Jannal") .setLastName("Jan") .build()); log.info("Response received from server:{}", helloResponse); channel.shutdown(); } @Test public void testTraceId() {<!-- --> ManagedChannel channel = ManagedChannelBuilder.forAddress(IP, PORT) .usePlaintext()// Enable plain text .build(); Context.current().withValue(Constant.TRACE_ID_CTX_KEY, UUID.randomUUID().toString()).run(() -> {<!-- --> HelloServiceGrpc.HelloServiceBlockingStub stub = HelloServiceGrpc.newBlockingStub(channel); //Specify the interceptor when calling the method HelloResponse helloResponse = stub.withInterceptors(new ClientPreInterceptor(), new ClientPostInterceptor(), new TraceIdClientInterceptor()) .hello(HelloRequest.newBuilder() .setFirstName("Jannal") .setLastName("Jan") .build()); log.info("Response received from server:{}", helloResponse); }); channel.shutdown(); } }
-
Output results
---------------------Interception before client call------------- ----- TraceId passed by the client: 3f393abc-d5ae-4ad0-b24a-aaa4596e8f7f Call cn.jannal.grpc.facade.dto.HelloService/hello to start Method: cn.jannal.grpc.facade.dto.HelloService/hello Number of messages passed to the listener for the request: 2 Method: cn.jannal.grpc.facade.dto.HelloService/hello Send message: firstName: "Jannal" lastName: "Jan" ---------------------Interception after client call--------------------- Method: cn.jannal.grpc.facade.dto.HelloService/hello client half closed Response header has been received, methodName:cn.jannal.grpc.facade.dto.HelloService/hello The client has received the response message, methodName: cn.jannal.grpc.facade.dto.HelloService/hello, message: greeting: "Hello, Jannal Jan" The client closes the connection, methodName: cn.jannal.grpc.facade.dto.HelloService/hello, code: OK
Server-side interceptor case
-
Interception of requests received by the server
/** * The server receives the request interceptor */ @Slf4j public class ServerPreInterceptor implements ServerInterceptor {<!-- --> @Override public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {<!-- --> final String methodName = call.getMethodDescriptor().getFullMethodName(); return new ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(next.startCall(call, headers)) {<!-- --> @Override public void onMessage(ReqT message) {<!-- --> log.info("The server receives the message, methodName:{},message:{}", methodName, message); super.onMessage(message); } @Override public void onHalfClose() {<!-- --> log.info("Server half closed, methodName:{}", methodName); super.onHalfClose(); } @Override public void onCancel() {<!-- --> log.info("Server call was canceled, methodName:{}", methodName); super.onCancel(); } @Override public void onComplete() {<!-- --> log.info("Server call completed, methodName:{}", methodName); super.onComplete(); } @Override public void onReady() {<!-- --> log.info("Server onReady, methodName:{}", methodName); super.onReady(); } }; } }
-
Interception before server reply
@Slf4j public class ServerPostInterceptor implements ServerInterceptor {<!-- --> @Override public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) {<!-- --> final String methodName = call.getMethodDescriptor().getFullMethodName(); ServerCall<ReqT, RespT> newCall = new ForwardingServerCall.SimpleForwardingServerCall<ReqT, RespT>(call) {<!-- --> @Override public void sendMessage(RespT message) {<!-- --> log.info("Server sends message, methodName:{},message:{}", methodName, message); super.sendMessage(message); } @Override public void sendHeaders(Metadata headers) {<!-- --> log.info("Server sends response header, methodName:{}", methodName); super.sendHeaders(headers); } @Override public void close(Status status, Metadata trailers) {<!-- --> log.info("The server closes the connection, methodName:{},code:{}", methodName, status.getCode()); super.close(status, trailers); } }; return next.startCall(newCall, headers); } }
-
Get Metadata from the server
public class TraceIdServerInterceptor implements ServerInterceptor {<!-- --> @Override public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall, Metadata metadata, ServerCallHandler<ReqT, RespT> serverCallHandler) {<!-- --> //Get traceId from metadata and put it in context String traceId = metadata.get(Constant.TRACE_ID_METADATA_KEY); Context ctx = Context.current().withValue(Constant.TRACE_ID_CTX_KEY, traceId); return Contexts.interceptCall(ctx, serverCall, metadata, serverCallHandler); } }
-
Server-side interceptor configuration
@Slf4j public class GrpcProvider {<!-- --> public static void main(String[] args) throws IOException {<!-- --> int port = 8081; ServerBuilder serverBuilder = ServerBuilder .forPort(port) .intercept(new ServerPreInterceptor()) .intercept(new ServerPostInterceptor()) .intercept(new TraceIdServerInterceptor()) .addService(new HelloServiceImpl()); //Specify the interceptor of the Service //.addService(ServerInterceptors.intercept(new HelloServiceImpl(), new ServerPreInterceptor())) Server server = serverBuilder.build(); Runtime.getRuntime().addShutdownHook(new Thread(() -> {<!-- --> if (server != null) {<!-- --> server.shutdown(); } log.info("Server Shutdown!"); })); serverBuilder.intercept(TransmitStatusRuntimeExceptionInterceptor.instance()); server.start(); log.info("Server start port {} !", port); startDaemonAwaitThread(server); } private static void startDaemonAwaitThread(Server server) {<!-- --> Thread awaitThread = new Thread(() -> {<!-- --> try {<!-- --> server.awaitTermination(); } catch (InterruptedException ignore) {<!-- --> } }); awaitThread.setDaemon(false); awaitThread.start(); } }
-
Output results
---------------------Interception of requests received by the server-------------- ------ TraceId passed by the client: 479a91b0-854b-482e-ab27-221d6d1a0e5e Server onReady, methodName:cn.jannal.grpc.facade.dto.HelloService/hello The server receives the message, methodName: cn.jannal.grpc.facade.dto.HelloService/hello, message: firstName: "Jannal" lastName: "Jan" The server is half closed, methodName: cn.jannal.grpc.facade.dto.HelloService/hello ---------------------Request interception before server response--------------------- The server sends a response header, methodName:cn.jannal.grpc.facade.dto.HelloService/hello The server sends a message, methodName: cn.jannal.grpc.facade.dto.HelloService/hello, message: greeting: "Hello, Jannal Jan" The server closes the connection, methodName: cn.jannal.grpc.facade.dto.HelloService/hello, code: OK The server call is completed, methodName:cn.jannal.grpc.facade.dto.HelloService/hello
Identity authentication
-
gRPC has the following authentication mechanisms built in:
- SSL/TLS: gRPC has SSL/TLS integration and facilitates the use of SSL/TLS to authenticate servers and encrypt all data exchanged between client and server. An optional mechanism allows clients to provide certificates for mutual authentication.
- Token-based authentication with Google: gRPC provides a common mechanism for attaching metadata-based credentials to requests and responses. gRPC provides a simple authentication API based on the unified concept of Credentials objects, which can be used when creating an entire gRPC Channel or a single call.
-
The client inherits CallCredentials to implement identity authentication tokens
//Put it in the general api interface package public class Constant {<!-- --> public static final Metadata.Key<String> AUTHORIZATION_METADATA_KEY = Metadata.Key.of("Authorization", io.grpc.Metadata.ASCII_STRING_MARSHALLER); public static final Context.Key<String> TOKEN_CONTEXT_KEY = Context.key("token"); private Constant() {<!-- --> } } public class RequestToken extends CallCredentials {<!-- --> private String value; public RequestToken(String value) {<!-- --> this.value = value; } @Override public void applyRequestMetadata(RequestInfo requestInfo, Executor executor, MetadataApplier metadataApplier) {<!-- --> executor.execute(() -> {<!-- --> try {<!-- --> Metadata headers = new Metadata(); headers.put(Constant.AUTHORIZATION_METADATA_KEY, value); metadataApplier.apply(headers); } catch (Throwable e) {<!-- --> metadataApplier.fail(Status.UNAUTHENTICATED.withCause(e)); } }); } @Override public void thisUsesUnstableApi() {<!-- --> // noop } }
-
Client request carries credentials
/** * Bring your voucher */ @Test public void testCredentials() {<!-- --> ManagedChannel channel = ManagedChannelBuilder.forAddress(IP, PORT) .usePlaintext()// Enable plain text .build(); HelloServiceGrpc.HelloServiceBlockingStub stub = HelloServiceGrpc.newBlockingStub(channel); //Specify the interceptor when calling the method HelloResponse helloResponse = stub .withCallCredentials(new RequestToken("123456")) .hello(HelloRequest.newBuilder() .setFirstName("jannal") .setLastName("Jan") .build()); log.info("Response received from server:{}", helloResponse); channel.shutdown(); }
-
Writing permission interceptors on the server side
@Slf4j public class AuthorizationServerInterceptor implements ServerInterceptor {<!-- --> @Override public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall, Metadata metadata, ServerCallHandler<ReqT, RespT> serverCallHandler) {<!-- --> String value = metadata.get(Constant.AUTHORIZATION_METADATA_KEY); log.info("Token carried by the client: {}", value); Status status; if (value == null || "".equals(value)) {<!-- --> status = Status.UNAUTHENTICATED.withDescription("Authorization token is missing"); } else {<!-- --> try {<!-- --> Context ctx = Context.current().withValue(Constant.TOKEN_CONTEXT_KEY, value); return Contexts.interceptCall(ctx, serverCall, metadata, serverCallHandler); } catch (Exception e) {<!-- --> status = Status.UNAUTHENTICATED.withDescription(e.getMessage()).withCause(e); } } serverCall.close(status, metadata); return new ServerCall.Listener<ReqT>() {<!-- --> // noop }; } }