gRPC实践--加密通信升级版

本文,将续上一个博客未完成的尾巴,对gRPC加密通信进行一个升级。主要是分为:

  1. 使用CA,为server端和client端颁发证书并通信。
  2. 使用token对不同的方法访问进行权限管理。

我们将在上一次的代码中进行重构,参考:

基于CA证书的认证和加密通信

如下图所示,为CA与服务端和客户端的交互过程:

在这里插入图片描述

在上一篇博客中,我们已经为server端颁发了证书,现在,我们需要为client端颁发证书:

## 生成client私钥
$ openssl genrsa -out client.key 2048

## 根据私钥client.key生成证书请求文件client.csr
$ openssl req -new -nodes -key client.key -out client.csr -subj "/C=cn/OU=myclient/O=clientcomp/CN=clientname" -config ./openssl.cfg - extensions v3_req

## 请求CA对证书请求文件签名,生成最终证书文件
$ openssl x509 -req -days 365 -in client.csr -out client.pem -CA ca.crt -CAkey ca.key -CAcreateserial -extfile ./openssl.cfg -extensions v3_req

目录结构

λ tree /F
D:.
│  go.mod
│  go.sum
├─cert  ## CA根证书
│      ca.crt
│      ca.csr
│      ca.key
│      openssl.cfg
├─client ## client证书和代码
│      client.csr
│      client.go
│      client.key
│      client.pem
├─proto
│      helloworld.pb.go
│      helloworld.proto
└─server ## server证书和代码
        server.csr
        server.go
        server.key
        server.pem

server

package main

......

func main() {
	// 从证书相关文件中读取和解析信息,得到证书公钥、密钥对
	certificate, err := tls.LoadX509KeyPair("./server/server.pem", "./server/server.key")
	if err != nil {
		log.Fatal(err)
	}
	// 创建一个新的、空的 CertPool
	certPool := x509.NewCertPool()
	ca, err := ioutil.ReadFile("./cert/ca.crt")
	if err != nil {
		log.Fatal(err)
	}
    // 尝试解析所传入的 PEM 编码的证书。如果解析成功会将其加到 CertPool 中,便于后面的使用
	if ok := certPool.AppendCertsFromPEM(ca); !ok {
		log.Fatal("failed to append certs")
	}
    // credentials.NewTLS:构建基于 TLS 的 TransportCredentials 选项
	creds := credentials.NewTLS(&tls.Config{ // Config 结构用于配置 TLS 客户端或服务器
		Certificates: []tls.Certificate{certificate},
        // tls.RequireAndVerifyClientCert
        // 表示 Server 也会使用 CA 认证的根证书对 Client 端的证书进行校验
		ClientAuth:   tls.RequireAndVerifyClientCert, // NOTE: this is optional!
		ClientCAs:    certPool,
	})
	// 初始化一个gRPC的结构体对象,传入creds
	s := grpc.NewServer(grpc.Creds(creds))
	// 注册服务
	pb.RegisterGreeterServer(s, &server{})
	......
}

有一篇大佬的博客,对这个介绍得更详细:带入gRPC:基于 CA 的 TLS 证书认证

client

package main

.....

func main() {
    // 从证书相关文件中读取和解析信息,得到证书公钥、密钥对
	certificate, err := tls.LoadX509KeyPair("./client/client.pem", "./client/client.key")
	if err != nil {
		log.Fatal(err)
	}
	// 创建一个新的、空的 CertPool
	certPool := x509.NewCertPool()
	ca, err := ioutil.ReadFile("./cert/ca.crt")
	if err != nil {
		log.Fatal(err)
	}
	if ok := certPool.AppendCertsFromPEM(ca); !ok {
		log.Fatal("failed to append ca certs")
	}
	// 在 Client 请求 Server 端时,Client 端会使用根证书和 ServerName 去对 Server 端进行校验项
	creds := credentials.NewTLS(&tls.Config{
		Certificates: []tls.Certificate{certificate},
		ServerName:   "hello.org.haha.com", // NOTE: this is required!
		RootCAs:      certPool,
	})
	// 连接 gRPC 服务器,WithTransportCredentials表示需要证书
	conn, err := grpc.Dial(address, grpc.WithTransportCredentials(creds))
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	......
}

有一篇大佬的博客,对这个介绍得更详细:带入gRPC:基于 CA 的 TLS 证书认证

基于Token的认证和权限控制

gRPC还为每个gRPC方法调用提供了认证支持,这样就基于用户Token对不同的方法访问进行权限管理。

我们将在上一次的代码中进行重构,参考:

目录结构

λ tree /F
D:.
│  go.mod
│  go.sum
├─cert
│      ca.crt
│      ca.csr
│      ca.key
│      myInterface.go
│      openssl.cfg
├─client
│      client.csr
│      client.go
│      client.key
│      client.pem
├─proto
│      helloworld.pb.go
│      helloworld.proto
└─server
        server.csr
        server.go
        server.key
        server.pem

要实现对每个gRPC方法进行认证,需要实现grpc.PerRPCCredentials接口,可以看到,该接口的样子为:

在这里插入图片描述

这里,我们增添了一个新的目录cert/myInterface.go,这个文件负责实现需要实现grpc.PerRPCCredentials接口。

Authentication接口

GetRequestMetadata方法中返回认证需要的必要信息。RequireTransportSecurity方法表示是否要求底层使用安全链接。

在真实的环境中建议必须要求底层启用安全的链接,否则认证信息有泄露和被篡改的风险。

我们可以创建一个Authentication类型,用于实现用户名和密码的认证:

package cert

import (
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
)
// 用户Token验证的结构体
type Authentication struct {
	User     string
	Password string
}
// 返回认证需要的必要信息
func (a *Authentication) GetRequestMetadata(context.Context, ...string) (map[string]string, error, ) {
	return map[string]string{"user": a.User, "password": a.Password}, nil
}
// 在GetRequestMetadata方法中,我们返回地认证信息包装login和password两个信息。
// 这里,我们令其为 true,需要证书
func (a *Authentication) RequireTransportSecurity() bool {
	return true
}
// 增加Authentication方法,进行权限判断
func (a *Authentication) Auth(ctx context.Context) error {
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return fmt.Errorf("missing credentials")
	}

	var appId, appKey string
	if val, ok := md["user"]; ok {
		appId = val[0]
	}
	if val, ok := md["password"]; ok {
		appKey = val[0]
	}

	if appId != a.User || appKey != a.Password {
		return grpc.Errorf(codes.Unauthenticated, "invalid token")
	}

	return nil
}

详细地认证工作主要在Authentication.Auth方法中完成。首先通过metadata.FromIncomingContextctx上下文中获取元信息,然后取出相应的认证信息进行认证。如果认证失败,则返回一个codes.Unauthenticated类型地错误。

server

在gRPC服务端的每个方法中通过Authentication类型的 Auth 方法进行身份认证:

package main

......

// server 用于实现 helloworld.GreeterServer接口
type server struct{
	auth *cert.Authentication
}

// SayHello 实现 helloworld.GreeterServer 接口里的方法
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	auth := cert.Authentication{ // 设置密码
		User:     "myName",
		Password: "myPassword",
	}
	s.auth = &auth
	// 在每一个方法中都要进行这样的判断
	if err := s.auth.Auth(ctx); err != nil {
		return nil, err
	}
	log.Printf("Received: %v", in.GetName())
	return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func main() {
	// 引入证书
	certificate, err := tls.LoadX509KeyPair("./server/server.pem", "./server/server.key")
	......
	creds := credentials.NewTLS(&tls.Config{
		Certificates: []tls.Certificate{certificate},
		ClientAuth:   tls.RequireAndVerifyClientCert, // NOTE: this is optional!
		ClientCAs:    certPool,
	})
	......
}

client

然后在每次请求gRPC服务时就可以将Token信息作为参数选项传入。

通过grpc.WithPerRPCCredentials函数将Authentication对象转为grpc.Dial参数。

因为启用了安全链接grpc.WithTransportCredentials(),需要进行安全认证。

package main

......

func main() {
	certificate, err := tls.LoadX509KeyPair("./client/client.pem", "./client/client.key")
	......
	creds := credentials.NewTLS(&tls.Config{
		Certificates: []tls.Certificate{certificate},
		ServerName:   "hello.org.haha.com", // NOTE: this is required!
		RootCAs:      certPool,
	})
	// 从终端读取密码
	auth := cert.Authentication{
		User:     "myName",
		Password: "myPassword",
	}
	//auth.User = os.Args[1]
	//auth.Password = os.Args[2]

	// grpc.WithPerRPCCredentials(&auth),开启token权限
	conn, err := grpc.Dial(address, grpc.WithTransportCredentials(creds),grpc.WithPerRPCCredentials(&auth))
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()

	//初始化gRPC 客户端
	c := pb.NewGreeterClient(conn)

	// 输入参数
	name := defaultName
	if len(os.Args) > 3 { //如果外部没有接收到参数,则使用默认参数
		name = os.Args[3]
	}
	......
}

实验

在这里插入图片描述

总结

在本文中,介绍了如何通过CA证书,为服务端和客户端颁发证书,并且基于此,服务端和客户端如何进行TLS加密通信。

此外,在TLS加密的基础上,对指定的gRPC 服务方法进行token权限限制调用。

自此,我们的gRPC 通信相对来说,已经很安全了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
go实战微服务分布式系统(distributed system)是建立在网络之上的软件系统。正是因为软件的特性,所以分布式系统具有高度的内聚性和透明性。因此,网络和分布式系统之间的区别更多的在于高层软件(特别是操作系统),而不是硬件。在一个分布式系统中,一组独立的计算机展现给用户的是一个统一的整体,就好像是一个系统似的。系统拥有多种通用的物理和逻辑资源,可以动态的分配任务,分散的物理和逻辑资源通过计算机网络实现信息交换。系统中存在一个以全局的方式管理计算机资源的分布式操作系统。通常,对用户来说,分布式系统只有一个模型或范型。在操作系统之上有一层软件中间件(middleware)负责实现这个模型。一个著名的分布式系统的例子是万维网(World Wide Web),在万维网中,所有的一切看起来就好像是一个文档(Web页面)一样。 [1] 在计算机网络中,这种统一性、模型以及其中的软件都不存在。用户看到的是实际的机器,计算机网络并没有使这些机器看起来是统一的。如果这些机器有不同的硬件或者不同的操作系统,那么,这些差异对于用户来说都是完全可见的。如果一个用户希望在一台远程机器上运行一个程序,那么,他必须登陆到远程机器上,然后在那台机器上运行该程序。 [1] 分布式系统和计算机网络系统的共同点是:多数分布式系统是建立在计算机网络之上的,所以分布式系统与计算机网络在物理结构上是基本相同的。 [1] 他们的区别在于:分布式操作系统的设计思想和网络操作系统是不同的,这决定了他们在结构、工作方式和功能上也不同。网络操作系统要求网络用户在使用网络资源时首先必须了解网络资源,网络用户必须知道网络中各个计算机的功能与配置、软件资源、网络文件结构等情况,在网络中如果用户要读一个共享文件时,用户必须知道这个文件放在哪一台计算机的哪一个目录下;分布式操作系统是以全局方式管理系统资源的,它可以为用户任意调度网络资源,并且调度过程是“透明”的。当用户提交一个作业时,分布式操作系统能够根据需要在系统中选择最合适的处理器,将用户的作业提交到该处理程序,在处理器完成作业后,将结果传给用户。在这个过程中,用户并不会意识到有多个处理器的存在,这个系统就像是一个处理器一样。 [1] 内聚性是指每一个数据库分布节点高度自治,有本地的数据库管理系统。透明性是指每一个数据库分布节点对用户的应用来说都是透明的,看不出是本地还是远程。在分布式数据库系统中,用户感觉不到数据是分布的,即用户不须知道关系是否分割、有无副本、数据存于哪个站点以及事务在哪个站点上执行等。  什么是微服务?维基上对其定义为:一种软件开发技术- 面向服务的体系结构(SOA)架构样式的一种变体,将应用程序构造为一组松散耦合的服务。在微服务体系结构中,服务是细粒度的,协议是轻量级的。微服务(或微服务架构)是一种云原生架构方法,其中单个应用程序由许多松散耦合且可独立部署的较小组件或服务组成。这些服务通常● 有自己的堆栈,包括数据库和数据模型;● 通过REST API,事件流和消息代理的组合相互通信;● 和它们是按业务能力组织的,分隔服务的线通常称为有界上下文。尽管有关微服务的许多讨论都围绕体系结构定义和特征展开,但它们的价值可以通过相当简单的业务和组织收益更普遍地理解:● 可以更轻松地更新代码。● 团队可以为不同的组件使用不同的堆栈。● 组件可以彼此独立地进行缩放,从而减少了因必须缩放整个应用程序而产生的浪费和成本,因为单个功能可能面临过多的负载。 
grpc-server-spring-boot-starter是一个基于Spring Boot框架的gRPC服务器的启动器。gRPC(Google Remote Procedure Call)是一种高性能的远程过程调用框架,它使用Protocol Buffers作为接口定义语言,并支持多种编程语言。 grpc-server-spring-boot-starter提供了一系列简化配置和集成的功能,使得在Spring Boot应用中启动和配置gRPC服务器变得更加容易。它提供了自动装配的功能,可以根据应用的配置自动创建和启动gRPC服务器。用户只需要在配置文件中设置相应的参数,如服务器的端口号、TLS证书等,即可完成服务器的启动配置。 在使用grpc-server-spring-boot-starter时,用户可以方便地定义服务接口和实现类。通过使用gRPC的接口定义语言(protobuf)定义接口,并生成对应的Java代码。然后,用户只需要在实现类中实现相应的接口方法即可。 在服务器启动后,grpc-server-spring-boot-starter会根据定义的接口和实现类,自动创建相应的gRPC服务,并将其注册到服务器中。当客户端发起远程调用时,服务器会根据接口定义和方法参数,将请求转发给对应的实现类,并返回执行结果给客户端。 grpc-server-spring-boot-starter还支持对gRPC服务器进行拦截器的配置。拦截器可以在请求和响应的过程中拦截和修改消息,用于实现日志记录、鉴权、性能监控等功能。 总之,grpc-server-spring-boot-starter简化了在Spring Boot应用中使用gRPC的配置和集成过程,使得开发者可以更加便捷地构建和部署gRPC服务器。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值