如何在 Android 中使用 TLS 搭建 gRPC 客户端

对于客户端和服务器之间的交互,gRPC 是一个常见的概念。

如何在 Android 中使用 TLS 搭建 gRPC 客户端
gRPC 客户端-服务器

什么是远程过程调用(RPC)?

RPC 是一种软件通信协议。一个进程可以调用远程系统(如本地系统)上的一个过程(也称为函数或子程序),而无需事先了解远程系统的网络情况。RPC 使用传统的客户端-服务器模式。服务器定义程序,客户端请求远程服务器应用业务逻辑并返回结果。

Rest 与 gRPC:

表征状态转移(REST)是客户端与服务器之间的一种通信协议,通过 HTTP 1.1/HTTPS 1.1 与动词(PUT、POST、GET 等)进行交互。

gRPC 是谷歌推出的远程协议调用(Remote Protocol Call),是一种开源、基于合约、跨平台的通信协议,用于简化服务间通信。

gRPC 利用了 HTTP/2.0 和 TLS 的双向特性。它使用协议缓冲区作为有效载荷管理和序列化机制,类似于 Rest 的 JSON。

与 JSON 不同的是,protocol buffers 还包括三个主要部分:

  1. 一种合约定义语言,即proto3(最新的protocol buffer语言规范)。
  2. 生成的功能代码
  3. 特定语言的运行库

与HTTP/1.1顺序加载资源不同,HTTP/2.0使用TCP连接,单个连接承载许多数据流,因此不存在资源阻塞。在 HTTP/1.1 中,如果资源加载失败,序列将被阻止,我们可能会面临这样的问题。

什么是proto3?

syntax = 'proto3';

package bookStorePackage;

// Book service definition. defines all the rpc methods here
service Book {
    rpc createBook (BookItemReq) returns (BookItem);
    rpc readBook (BookRequest) returns (BookItem);
    rpc readBooks (Empty) returns (BooksList);
}

// inputs / request , e.g. BookItemReq 
// outputs / response BookItem must be defined
// they are defined with a keyword of struct

message BookItemReq {
    string name = 1;
    string description = 2;
    string author = 3;
}

message BookItem {
    int32 id = 1;
    string name = 2;
    string description = 3;
    string author = 4;
}

message BookRequest {
    int32 id = 1;
}

message BooksList {
    repeated BookItem books = 1;
}

message Empty {}

`.proto` 包含 gRPC 的结构,并定义每个调用的过程和请求/响应。使用 protoc 插件将 .proto 文件转换为 proto 缓冲区文件。

protoc --proto_path=src --java_out=build/gen PROTO_PATH

现在让我们进一步了解代码。你可以找到许多不带 TLS 的 gRPC 实现资源,在本文中我们将研究如何在Android 中使用 TLS 制作 gRPC 客户端。

在开始之前,我们需要有一个用任何语言编写的安全 gRPC 服务器。

如何使用 Android Studio?

首先,我们需要在 android 中建立一个新项目。在这个项目中,我们将使用一个空活动。接下来,设置依赖关系并在 build.gradle 中添加以下内容。

plugins {
    id 'com.google.protobuf' version '0.8.18'
}

// this is used to generate proto buf file when you build the project
protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.21.7"
    }
    plugins {
        grpc {
            artifact = 'io.grpc:protoc-gen-grpc-java:1.51.0'
        }
    }
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                java { option 'lite' }
            }
            task.plugins {
                grpc { option 'lite' }
            }
        }
    }
}

dependencies {
    // for TLS based connection we need okhttp
    implementation 'io.grpc:grpc-okhttp:1.51.1'
    implementation 'io.grpc:grpc-protobuf-lite:1.51.1'
    implementation 'io.grpc:grpc-stub:1.51.1'
    // these are used to set up a secure client
    implementation 'com.squareup.okhttp3:okhttp-tls:4.10.0'
    implementation 'com.squareup.okhttp3:okhttp:4.10.0'
    // for implementation of auto generated code
    implementation 'javax.annotation:javax.annotation-api:1.3.2'
}

将 proto 文件添加到我们的项目中:

如何在 Android 中使用 TLS 搭建 gRPC 客户端
Android Studio 中的文件结构部分

我们需要将结构从android更改为project。现在可以在 app/src/main/proto 中添加 proto 文件。我们需要创建一个名为 proto 的目录,并将 .proto 文件放入其中。

如何在 Android 中使用 TLS 搭建 gRPC 客户端
Proto 文件目标 app/src/main/proto

现在,我们需要通过 toolbar > build > rebuild project 来构建的项目,如下图:

如何在 Android 中使用 TLS 搭建 gRPC 客户端
重建项目以获得生成的代码
如何在 Android 中使用 TLS 搭建 gRPC 客户端
自动生成的协议缓冲区文件
如何在 Android 中使用 TLS 搭建 gRPC 客户端
BookGrpc 自动生成的类

有一点很重要,在 Java 中,我们的项目中有一个特定的包名。如何在 proto 文件中指定该名称呢?很简单。我们需要为 java 添加一个选项,以指导编译器(protoc)生成 proto 缓冲文件。

如何在 Android 中使用 TLS 搭建 gRPC 客户端
带有 java 选项的 proto 文件

在 proto 文件中添加该选项后,编译器就会明白,它需要指定我们强制执行的软件包名称。默认情况下,编译器会使用 package 关键字作为包名。

现在,让我们深入研究如何设置客户端,以便通过安全连接与 gRPC 服务器通信。我们需要将签名证书放在应用程序 res 文件夹中的 raw 类别中:

如何在 Android 中使用 TLS 搭建 gRPC 客户端
添加签名证书的位置

放置证书后,只需使用以下代码和 viola 即可轻松与我们的安全服务器进行通信。

        try{
            Resources res = getResources();
            InputStream instream = res.openRawResource(R.raw.ca_cert);
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            Certificate ca = cf.generateCertificate(instream);
            KeyStore kStore = KeyStore.getInstance(KeyStore.getDefaultType());
            kStore.load(null, null);
            kStore.setCertificateEntry("ca", ca);
            TrustManagerFactory tmf = TrustManagerFactory
                    .getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmf.init(kStore);
            TrustManager[]  trustManagers = tmf.getTrustManagers();
            if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
                throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
            }
            SSLContext context = SSLContext.getInstance("TLS");
            context.init(null, tmf.getTrustManagers(), null);
            SSLSocketFactory sslSocketFactory = context.getSocketFactory();
            
            final ManagedChannel channel = OkHttpChannelBuilder
                    .forAddress("IP Address",PORT)
                    .useTransportSecurity()
                    .overrideAuthority("add same DNS / IP:PORT entry here")
                    .sslSocketFactory(sslSocketFactory)
                    .build();
            // BookGrpc is an auto generated class from the proto we defined.
            BookGrpc.BookBlockingStub stub = BookGrpc.newBlockingStub(channel);
            // create a request object
            BookItemReq requestData = BookItemReq
                    .newBuilder()
                    .setName("AnyName")
                    .setAuthor("AnyAuthor")
                    .setDescription("Any Description")
                    .build();
            BookItem response = stub.createBook(requestData);
            // handle the response
            System.out.println(response);
            channel.shutdown();
        } catch (Exception e){
            e.printStackTrace();
        }

使用所提供的示例代码,您可以与安全的 gRPC 服务器进行通信。

代码的作用是什么?

我们知道,要建立安全连接,我们需要一套证书来确保通信是加密的。我们需要在 Android 应用程序中添加签名证书。

接下来,我们使用以下几行代码读取证书。

Resources res = getResources();
InputStream instream = res.openRawResource(R.raw.ca_cert);

首先,我们获取资源实例,然后使用函数 openRawResource 读取证书。接下来,我们从文件中生成证书,并将其添加为受信任证书。使用受信任证书,我们初始化 SSL 上下文实例,以初始化 SSL 套接字因子。SSL 套接字工厂是安全套接字的一个因素。

加密通道处理来自服务器的请求和响应。我们使用了 OkHttpChannelBuilder,它支持使用 SSLSocketFactor 在客户端和服务器之间提供安全通信介质。

如何自行获取证书?

您可以使用以下脚本生成证书:

rm *.pem
rm *.srl
rm *.cnf

# 1. Generate CA's private key and self-signed certificate
openssl req -x509 -newkey rsa:4096 -days 365 -nodes -keyout ca-key.pem -out ca-cert.pem -subj "/C=FR/ST=Occitanie/L=Toulouse/O=Test Org/OU=Test/CN=*.test/emailAddress=test@gmail.com"

echo "CA's self-signed certificate"
openssl x509 -in ca-cert.pem -noout -text

# 2. Generate web server's private key and certificate signing request (CSR)
openssl req -newkey rsa:4096 -nodes -keyout server-key.pem -out server-req.pem -subj "/C=FR/ST=Ile de France/L=Paris/O=Server TLS/OU=Server/CN=*.tls/emailAddress=tls@gmail.com"

# Remember that when we develop on localhost, It’s important to add the IP:0.0.0.0 as an Subject Alternative Name (SAN) extension to the certificate.
echo "subjectAltName=DNS:*.tls,DNS:example.com,IP:0.0.0.0" > server-ext.cnf
# Or you can use localhost DNS and grpc.ssl_target_name_override variable
# echo "subjectAltName=DNS:localhost" > server-ext.cnf

# 3. Use CA's private key to sign web server's CSR and get back the signed certificate
openssl x509 -req -in server-req.pem -days 60 -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile server-ext.cnf

echo "Server's signed certificate"
openssl x509 -in server-cert.pem -noout -text

# 4. Generate client's private key and certificate signing request (CSR)
openssl req -newkey rsa:4096 -nodes -keyout client-key.pem -out client-req.pem -subj "/C=FR/ST=Alsace/L=Strasbourg/O=PC Client/OU=Computer/CN=*.client.com/emailAddress=client@gmail.com"

# Remember that when we develop on localhost, It’s important to add the IP:0.0.0.0 as an Subject Alternative Name (SAN) extension to the certificate.
echo "subjectAltName=DNS:eaple.com,IP:0.0.0.0" > client-ext.cnf

# 5. Use CA's private key to sign client's CSR and get back the signed certificate
openssl x509 -req -in client-req.pem -days 60 -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -extfile client-ext.cnf

echo "Client's signed certificate"
openssl x509 -in client-cert.pem -noout -text

以上完整代码地址:https://github.com/AbdullahJanKhan/android-grpc-tls

作者:Abdullah Jan Khan

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论