当存在跨网络边界的 RPC 调用时,往往需要通过 TLS/SSL 对传输通道进行加密,以防止请求和响应消息中的敏感数据泄漏。跨网络边界调用场景主要有三种:
- 后端微服务直接开放给端侧,例如手机 App、TV、多屏等,没有统一的 API Gateway/SLB 做安全接入和认证;
- 后端微服务直接开放给 DMZ 部署的管理或者运维类 Portal;
- 后端微服务直接开放给第三方合作伙伴 / 渠道。
除了跨网络之外,对于一些安全等级要求比较高的业务场景,即便是内网通信,只要跨主机 /VM/ 容器通信,都强制要求对传输通道进行加密。在该场景下,即便只存在内网各模块的 RPC 调用,仍然需要做 SSL/TLS。
目前使用最广的 SSL/TLS 工具 / 类库就是 OpenSSL,它是为网络通信提供安全及数据完整性的一种安全协议,囊括了主要的密码算法、常用的密钥和证书封装管理功能以及 SSL 协议。
gRPC 安全机制
谷歌提供了可扩展的安全认证机制,以满足不同业务场景需求,它提供的授权机制主要有四类:
- 通道凭证(Channel credentials):默认提供了基于 HTTP/2 的 TLS,对客户端和服务端交换的所有数据进行加密传输;
- 调用凭证(Call credentials):被附加在每次 RPC 调用上,通过 Credentials 将认证信息附加到消息头中,由服务端做授权认证;
- 组合凭证(CompositeCallCredentials):将一个频道凭证和一个调用凭证关联起来创建一个新的频道凭证,在这个频道上的每次调用会发送组合的调用凭证来作为授权数据,最典型的场景就是使用 HTTP S 来传输 Access Token;
- Google 的 OAuth 2.0:gRPC 内置的谷歌的 OAuth 2.0 认证机制,通过 gRPC 访问 Google API 时,使用 Service Accounts 密钥作为凭证获取授权令牌。
通道凭证
服务端添加SSL支持:
// io.netty:netty-handlerSelfSignedCertificate ssc = new SelfSignedCertificate();ServerBuilder.forPort(port).useTransportSecurity(ssc.certificate(), ssc.privateKey()).addService(new GreeterImpl()).build().start();
客户端:
ManagedChannel channel = NettyChannelBuilder.forTarget(target)// Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid// needing certificates..sslContext(GrpcSslContexts.forClient().ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE).trustManager(InsecureTrustManagerFactory.INSTANCE).build()).defaultLoadBalancingPolicy("round_robin").intercept(new MyClientInterceptor()).build();
调用凭证
继承实现CallCredentials类
public class AuthenticationCallCredentials extends CallCredentials {public static final Metadata.Key<String> META_DATA_KEY =Metadata.Key.of("Authentication", Metadata.ASCII_STRING_MARSHALLER);private String token;public AuthenticationCallCredentials(String token) {this.token = token;}@Overridepublic void applyRequestMetadata(RequestInfo requestInfo,Executor executor,MetadataApplier metadataApplier) {executor.execute(() -> {try {Metadata headers = new Metadata();headers.put(META_DATA_KEY, "Bearer " + token);metadataApplier.apply(headers);} catch (Throwable e) {metadataApplier.fail(Status.UNAUTHENTICATED.withCause(e));}});}@Overridepublic void thisUsesUnstableApi() {// yes this is unstable :(}}
在每次调用服务端方案是带上凭证
GreeterGrpc.GreeterBlockingStub stub = GreeterGrpc.newBlockingStub(channel);GreetRequest request = GreetRequest.newBuilder().setName("alice").build();GreetResponse resp = stub.withCallCredentials(new AuthenticationCallCredentials("token")).sayHello(request);
Server端通过拦截器, 检查token
public class MyServerInterceptor implements ServerInterceptor {private static final Logger logger = Logger.getLogger(MyServerInterceptor.class.getName());public static final Context.Key<String> TOKEN_KEY =Context.key("Token");@Overridepublic <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call,Metadata headers, ServerCallHandler<ReqT, RespT> next) {logger.info("MyServerInterceptor....");String header = headers.get(AuthenticationCallCredentials.AUTHENTICATION_KEY);if (Strings.isNullOrEmpty(header)) {call.close(Status.UNAUTHENTICATED.withDescription("No authentication header"), headers);} else if (!header.startsWith("Bearer ")) {call.close(Status.UNAUTHENTICATED.withDescription("Unknown authorization type"), headers);} else {// 正常情况下会带上必要的用户信息, 通过设置Context, 在具体方法中获取信息Context ctx = Context.current().withValue(TOKEN_KEY, header.substring(7));return Contexts.interceptCall(ctx, call, headers, next);}return new ServerCall.Listener<ReqT>() {// noop};}}
