当存在跨网络边界的 RPC 调用时,往往需要通过 TLS/SSL 对传输通道进行加密,以防止请求和响应消息中的敏感数据泄漏。跨网络边界调用场景主要有三种:

  1. 后端微服务直接开放给端侧,例如手机 App、TV、多屏等,没有统一的 API Gateway/SLB 做安全接入和认证;
  2. 后端微服务直接开放给 DMZ 部署的管理或者运维类 Portal;
  3. 后端微服务直接开放给第三方合作伙伴 / 渠道。

除了跨网络之外,对于一些安全等级要求比较高的业务场景,即便是内网通信,只要跨主机 /VM/ 容器通信,都强制要求对传输通道进行加密。在该场景下,即便只存在内网各模块的 RPC 调用,仍然需要做 SSL/TLS。
目前使用最广的 SSL/TLS 工具 / 类库就是 OpenSSL,它是为网络通信提供安全及数据完整性的一种安全协议,囊括了主要的密码算法、常用的密钥和证书封装管理功能以及 SSL 协议。

gRPC 安全机制

谷歌提供了可扩展的安全认证机制,以满足不同业务场景需求,它提供的授权机制主要有四类:

  1. 通道凭证(Channel credentials):默认提供了基于 HTTP/2 的 TLS,对客户端和服务端交换的所有数据进行加密传输;
  2. 调用凭证(Call credentials):被附加在每次 RPC 调用上,通过 Credentials 将认证信息附加到消息头中,由服务端做授权认证;
  3. 组合凭证(CompositeCallCredentials):将一个频道凭证和一个调用凭证关联起来创建一个新的频道凭证,在这个频道上的每次调用会发送组合的调用凭证来作为授权数据,最典型的场景就是使用 HTTP S 来传输 Access Token;
  4. Google 的 OAuth 2.0:gRPC 内置的谷歌的 OAuth 2.0 认证机制,通过 gRPC 访问 Google API 时,使用 Service Accounts 密钥作为凭证获取授权令牌。

通道凭证

服务端添加SSL支持:

  1. // io.netty:netty-handler
  2. SelfSignedCertificate ssc = new SelfSignedCertificate();
  3. ServerBuilder.forPort(port)
  4. .useTransportSecurity(ssc.certificate(), ssc.privateKey())
  5. .addService(new GreeterImpl())
  6. .build()
  7. .start();

客户端:

  1. ManagedChannel channel = NettyChannelBuilder.forTarget(target)
  2. // Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
  3. // needing certificates.
  4. .sslContext(GrpcSslContexts.forClient()
  5. .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
  6. .trustManager(InsecureTrustManagerFactory.INSTANCE)
  7. .build())
  8. .defaultLoadBalancingPolicy("round_robin")
  9. .intercept(new MyClientInterceptor())
  10. .build();

调用凭证

继承实现CallCredentials

  1. public class AuthenticationCallCredentials extends CallCredentials {
  2. public static final Metadata.Key<String> META_DATA_KEY =
  3. Metadata.Key.of("Authentication", Metadata.ASCII_STRING_MARSHALLER);
  4. private String token;
  5. public AuthenticationCallCredentials(String token) {
  6. this.token = token;
  7. }
  8. @Override
  9. public void applyRequestMetadata(
  10. RequestInfo requestInfo,
  11. Executor executor,
  12. MetadataApplier metadataApplier) {
  13. executor.execute(() -> {
  14. try {
  15. Metadata headers = new Metadata();
  16. headers.put(META_DATA_KEY, "Bearer " + token);
  17. metadataApplier.apply(headers);
  18. } catch (Throwable e) {
  19. metadataApplier.fail(Status.UNAUTHENTICATED.withCause(e));
  20. }
  21. });
  22. }
  23. @Override
  24. public void thisUsesUnstableApi() {
  25. // yes this is unstable :(
  26. }
  27. }

在每次调用服务端方案是带上凭证

  1. GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc
  2. .newBlockingStub(channel);
  3. GreetRequest request = GreetRequest
  4. .newBuilder()
  5. .setName("alice")
  6. .build();
  7. GreetResponse resp = stub
  8. .withCallCredentials(new AuthenticationCallCredentials("token"))
  9. .sayHello(request);

Server端通过拦截器, 检查token

  1. public class MyServerInterceptor implements ServerInterceptor {
  2. private static final Logger logger = Logger.getLogger(MyServerInterceptor.class.getName());
  3. public static final Context.Key<String> TOKEN_KEY =
  4. Context.key("Token");
  5. @Override
  6. public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call,
  7. Metadata headers, ServerCallHandler<ReqT, RespT> next) {
  8. logger.info("MyServerInterceptor....");
  9. String header = headers.get(AuthenticationCallCredentials.AUTHENTICATION_KEY);
  10. if (Strings.isNullOrEmpty(header)) {
  11. call.close(Status.UNAUTHENTICATED.withDescription("No authentication header"), headers);
  12. } else if (!header.startsWith("Bearer ")) {
  13. call.close(Status.UNAUTHENTICATED.withDescription("Unknown authorization type"), headers);
  14. } else {
  15. // 正常情况下会带上必要的用户信息, 通过设置Context, 在具体方法中获取信息
  16. Context ctx = Context.current()
  17. .withValue(TOKEN_KEY, header.substring(7));
  18. return Contexts.interceptCall(ctx, call, headers, next);
  19. }
  20. return new ServerCall.Listener<ReqT>() {
  21. // noop
  22. };
  23. }
  24. }