3. 后端微服务直接开放给第三方合作伙伴 / 渠道。
除了跨网络之外,对于一些安全等级要求比较高的业务场景,即便是内网通信,只要跨主机 /VM/ 容器通信,都强制要求对传输通道进行加密。在该场景下,即便只存在内网各模块的 RPC 调用,仍然需要做 SSL/TLS。
目前使用最广的 SSL/TLS 工具 / 类库就是 OpenSSL,它是为网络通信提供安全及数据完整性的一种安全协议,囊括了主要的密码算法、常用的密钥和证书封装管理功能以及 SSL 协议。
多数 SSL 加密网站是用名为 OpenSSL 的开源软件包,由于这也是互联网应用最广泛的安全传输方法,被网银、在线支付、电商网站、门户网站、电子邮件等重要网站广泛使用。
2. 鉴权:对调用方的权限进行校验,防止越权调用。
事实上,并非所有的 RPC 调用都必须要做认证和鉴权,例如通过 API Gateway 网关接入的流量,已经在网关侧做了鉴权和身份认证,对来自网关的流量 RPC 服务端就不需要重复鉴权。
另外,一些对安全性要求不太高的场景,可以只做认证而不做细粒度的鉴权。
谷歌提供了可扩展的安全认证机制,以满足不同业务场景需求,它提供的授权机制主要有四类:
1. 通道凭证:默认提供了基于 HTTP/2 的 TLS,对客户端和服务端交换的所有数据进行加密传输;4. Google 的 OAuth 2.0:gRPC 内置的谷歌的 OAuth 2.0 认证机制,通过 gRPC 访问 Google API 时,使用 Service Accounts 密钥作为凭证获取授权令牌。
gRPC 基于 HTTP/2 协议,默认会开启 SSL/TLS。gRPC 的 TLS 实现有两种策略:
1. 基于 OpenSSL 的 TLS2. 基于 Jetty ALPN/NPN 的 TLS
对于非安卓的后端 Java 应用,gRPC 强烈推荐使用 OpenSSL,原因如下:netty-tcnative-boringssl-static 的 gradle 配置如下:
//ssl
compile 'io.netty:netty-tcnative-boringssl-static:2.0.8.Final'
服务端配置demo:
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.netty.handler.ssl.util.SelfSignedCertificate;
/**
* @describe GRpcServer default ssl demo
* @author zhikai.chen
* @date 2018年5月7日 下午3:55:10
*/
public class GRpcServerDefaultSSL {
private static final Logger logger = LoggerFactory.getLogger(GRpcServerDefaultSSL.class);
private Server server;
private void start() throws IOException, CertificateException {
/* The port on which the server should run */
int port = 50051;
SelfSignedCertificate ssc = new SelfSignedCertificate();
//底层默认使用netty4.1的nio同步非阻塞模型
server = ServerBuilder.forPort(port).useTransportSecurity(ssc.certificate(), ssc.privateKey())
.addService(new PhoneServiceImp())
.build()
.start();
logger.info("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
System.err.println("*** shutting down gRPC server since JVM is shutting down");
GRpcServerDefaultSSL.this.stop();
System.err.println("*** server shut down");
}
});
}
private void stop() {
if (server != null) {
server.shutdown();
}
}
/**
* Await termination on the main thread since the grpc library uses daemon
* threads.
*/
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}
/**
* Main launches the server from the command line.
* @throws CertificateException
*/
public static void main(String[] args) throws IOException, InterruptedException, CertificateException {
final GRpcServerDefaultSSL server = new GRpcServerDefaultSSL();
server.start();
server.blockUntilShutdown();
}
}
客户端配置demo:
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ylifegroup.protobuf.PhoneServiceGrpc;
import com.ylifegroup.protobuf.Phonebook.AddPhoneToUserRequest;
import com.ylifegroup.protobuf.Phonebook.AddPhoneToUserResponse;
import com.ylifegroup.protobuf.Phonebook.PhoneType;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
import io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.NettyChannelBuilder;
import io.netty.handler.codec.http2.Http2SecurityUtil;
import io.netty.handler.ssl.ApplicationProtocolConfig;
import io.netty.handler.ssl.ApplicationProtocolNames;
import io.netty.handler.ssl.OpenSsl;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.SupportedCipherSuiteFilter;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
/**
* @describe GRpcClient Block demo
* @author zhikai.chen
* @date 2018年5月7日 下午4:00:58
*/
public class GRpcClientBlockSSL {
private static final Logger logger = LoggerFactory.getLogger(GRpcClientBlockSSL.class);
private final ManagedChannel channel;
private final PhoneServiceGrpc.PhoneServiceBlockingStub blockingStub;
/** Construct client connecting to gRPC server at {@code host:port}.
* @throws SSLException */
public GRpcClientBlockSSL(String host, int port) throws SSLException {
this(NettyChannelBuilder.forAddress(host, port).sslContext(
GrpcSslContexts.forClient().
ciphers(Http2SecurityUtil.CIPHERS,
SupportedCipherSuiteFilter.INSTANCE).
trustManager(InsecureTrustManagerFactory.INSTANCE).build()));
}
/** Construct client connecting to gRPC server at {@code host:port}.
* @throws SSLException */
public GRpcClientBlockSSL(String host, int port,boolean value) throws SSLException {
this(NettyChannelBuilder.forAddress(host, port).sslContext(SslContextBuilder.forClient()
.sslProvider(OpenSsl.isAlpnSupported() ? SslProvider.OPENSSL : SslProvider.JDK)
.ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.applicationProtocolConfig(new ApplicationProtocolConfig(
ApplicationProtocolConfig.Protocol.ALPN,
ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE,
ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT,
ApplicationProtocolNames.HTTP_2,
ApplicationProtocolNames.HTTP_1_1))
.build()));
}
/** Construct client for accessing RouteGuide server using the existing channel. */
GRpcClientBlockSSL(ManagedChannelBuilder> channelBuilder) {
channel = channelBuilder.build();
blockingStub = PhoneServiceGrpc.newBlockingStub(channel);
}
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
/** add phone to user. */
public void addPhoneToUser(int uid, PhoneType phoneType, String phoneNubmer) {
logger.info("Will try to add phone to user " + uid);
AddPhoneToUserRequest request = AddPhoneToUserRequest.newBuilder().setUid(uid).setPhoneType(phoneType)
.setPhoneNumber(phoneNubmer).build();
AddPhoneToUserResponse response;
try {
response = blockingStub.addPhoneToUser(request);
} catch (StatusRuntimeException e) {
logger.warn("RPC failed: {0}", e.getStatus());
return;
}
logger.info("Result: " + response.getResult());
}
public static void main(String[] args) throws Exception {
GRpcClientBlockSSL client = new GRpcClientBlockSSL("localhost", 50051);
try {
client.addPhoneToUser(1, PhoneType.WORK, "13888888888");
client = new GRpcClientBlockSSL("localhost", 50051,true);
client.addPhoneToUser(1, PhoneType.WORK, "13888888888");
} finally {
client.shutdown();
}
}
}
运行效果图参考: